Add implementation of analog TV signal generator (PAL so far)

Quick and dirty Howto:
make && tv/osmotv --sdr-soapy --sdr-tx-gain 60  -r 15000000 -c 21 tx-fubk --sdr-tune-args "OFFSET=-3000000"
This commit is contained in:
Andreas Eversberg 2017-08-20 07:43:41 +02:00
parent a52c89800c
commit 496aff5a79
22 changed files with 3071 additions and 5 deletions

2
.gitignore vendored
View File

@ -22,6 +22,7 @@ compile
m4 m4
src/common/libcommon.a src/common/libcommon.a
src/common/libmobile.a src/common/libmobile.a
src/common/libimage.a
src/anetz/anetz src/anetz/anetz
src/bnetz/bnetz src/bnetz/bnetz
src/cnetz/cnetz src/cnetz/cnetz
@ -31,6 +32,7 @@ src/amps/amps
src/tacs/tacs src/tacs/tacs
src/jtacs/jtacs src/jtacs/jtacs
src/r2000/radiocom2000 src/r2000/radiocom2000
src/tv/osmotv
sim/cnetz_sim sim/cnetz_sim
src/test/test_filter src/test/test_filter
src/test/test_compandor src/test/test_compandor

View File

@ -29,6 +29,39 @@ AC_CHECK_LIB([pthread], [main])
PKG_CHECK_MODULES(ALSA, alsa >= 1.0) PKG_CHECK_MODULES(ALSA, alsa >= 1.0)
# disabled due to problems with api compatibilty with imagemagick
#AC_ARG_ENABLE(graphicsmagick,
# [AS_HELP_STRING(
# [--disable-graphicsmagick],
# [Disable building graphicsmagick]
# )],
# [enable_graphicsmagick=$enableval], [enable_graphicsmagick="yes"])
#if test x"$enable_graphicsmagick" = x"yes"
#then
# PKG_CHECK_MODULES(GRAPHICSMAGICK, GraphicsMagick >= 1.3.16, , enable_graphicsmagick=no)
#fi
#if test x"$enable_graphicsmagick" = x"yes"
#then
# somethingmagick=yes
#fi
AC_ARG_ENABLE(imagemagick,
[AS_HELP_STRING(
[--disable-imagemagick],
[Disable building imagemagick]
)],
[enable_imagemagick=$enableval], [enable_imagemagick="yes"])
if test x"$enable_imagemagick" = x"yes"
then
PKG_CHECK_MODULES(IMAGEMAGICK, ImageMagick >= 6.0.0, , enable_imagemagick=no)
fi
if test x"$enable_imagemagick" = x"yes"
then
somethingmagick=yes
fi
AM_CONDITIONAL(ENABLE_MAGICK, test x"$somethingmagick" = x"yes")
with_sdr=no with_sdr=no
AC_ARG_WITH([uhd], [AS_HELP_STRING([--with-uhd], [compile with UHD driver @<:@default=check@:>@]) ], [], [with_uhd="check"]) AC_ARG_WITH([uhd], [AS_HELP_STRING([--with-uhd], [compile with UHD driver @<:@default=check@:>@]) ], [], [with_uhd="check"])
AC_ARG_WITH([soapy], [AS_HELP_STRING([--with-soapy], [compile with SoapySDR driver @<:@default=check@:>@]) ], [], [with_soapy="check"]) AC_ARG_WITH([soapy], [AS_HELP_STRING([--with-soapy], [compile with SoapySDR driver @<:@default=check@:>@]) ], [], [with_soapy="check"])
@ -37,8 +70,9 @@ AS_IF([test "x$with_soapy" != xno], [PKG_CHECK_MODULES(SOAPY, SoapySDR >= 0.6.0,
AM_CONDITIONAL(HAVE_UHD, test "x$with_uhd" == "xyes" ) AM_CONDITIONAL(HAVE_UHD, test "x$with_uhd" == "xyes" )
AM_CONDITIONAL(HAVE_SOAPY, test "x$with_soapy" == "xyes" ) AM_CONDITIONAL(HAVE_SOAPY, test "x$with_soapy" == "xyes" )
AM_CONDITIONAL(HAVE_SDR, test "x$with_sdr" == "xyes" ) AM_CONDITIONAL(HAVE_SDR, test "x$with_sdr" == "xyes" )
AS_IF([test "x$with_uhd" == "xyes"],[AC_MSG_NOTICE( Compiling with UHD SDR support )], [AC_MSG_NOTICE( UHD SDR not support )]) AS_IF([test "x$with_uhd" == "xyes"],[AC_MSG_NOTICE( Compiling with UHD SDR support )], [AC_MSG_NOTICE( UHD SDR not supported )])
AS_IF([test "x$with_soapy" == "xyes"],[AC_MSG_NOTICE( Compiling with SoapySDR support )], [AC_MSG_NOTICE( SoapySDR not support )]) AS_IF([test "x$with_soapy" == "xyes"],[AC_MSG_NOTICE( Compiling with SoapySDR support )], [AC_MSG_NOTICE( SoapySDR not supported )])
AS_IF([test "x$somethingmagick" == "xyes"],[AC_MSG_NOTICE( Compiling with ImageMagick )],[AC_MSG_NOTICE( ImageMagick not supported )])
AC_OUTPUT( AC_OUTPUT(
src/common/Makefile src/common/Makefile
@ -50,6 +84,7 @@ AC_OUTPUT(
src/tacs/Makefile src/tacs/Makefile
src/jtacs/Makefile src/jtacs/Makefile
src/r2000/Makefile src/r2000/Makefile
src/tv/Makefile
src/test/Makefile src/test/Makefile
src/Makefile src/Makefile
sim/Makefile sim/Makefile

View File

@ -1,3 +1,3 @@
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
SUBDIRS = common anetz bnetz cnetz nmt amps tacs jtacs r2000 test SUBDIRS = common anetz bnetz cnetz nmt amps tacs jtacs r2000 tv test

View File

@ -1,6 +1,6 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) $(UHD_CFLAGS) $(SOAPY_CFLAGS) AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) $(GRAPHICSMAGICK_CFLAGS) $(IMAGEMAGICK_CFLAGS) $(UHD_CFLAGS) $(SOAPY_CFLAGS)
noinst_LIBRARIES = libcommon.a libmobile.a noinst_LIBRARIES = libcommon.a libmobile.a libimage.a
libcommon_a_SOURCES = \ libcommon_a_SOURCES = \
sample.c \ sample.c \
@ -31,6 +31,9 @@ libmobile_a_SOURCES = \
display_status.c \ display_status.c \
main_mobile.c main_mobile.c
libimage_a_SOURCES = \
img.c
if HAVE_SDR if HAVE_SDR
AM_CPPFLAGS += -DHAVE_SDR AM_CPPFLAGS += -DHAVE_SDR
@ -55,3 +58,7 @@ libcommon_a_SOURCES += \
soapy.c soapy.c
endif endif
if ENABLE_MAGICK
AM_CPPFLAGS += -DWITH_MAGICK
endif

388
src/common/img.c Executable file
View File

@ -0,0 +1,388 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "img.h"
int save_depth = 16;
#ifdef WITH_MAGICK
#include <magick/api.h>
/* load given image to memory. return short RGB values */
unsigned short *load_img(int *width, int *height, const char *filename, int index)
{
Image *image = NULL;
ImageInfo *imageinfo = NULL;
ExceptionInfo exception;
unsigned short *img = NULL;
MagickCoreGenesis(NULL, MagickFalse);
// InitializeMagick(NULL);
imageinfo = CloneImageInfo(0);
GetExceptionInfo(&exception);
sprintf(imageinfo->filename, filename, index);
image = ReadImage(imageinfo, &exception);
if (!image) {
// printf("failed to read image '%s' via *magick\n", filename);
goto exit;
}
*width = image->columns;
*height = image->rows;
img = (unsigned short *)malloc((*width) * (*height) * 3 * 2);
if (!img) {
printf("%s:failed to allocate image data\n", __func__);
goto exit;
}
ExportImagePixels(image, 0, 0, *width, *height, "RGB", ShortPixel, img, NULL);
// DispatchImage(image, 0, 0, *width, *height, "RGB", ShortPixel, img, NULL);
exit:
if (image)
DestroyImage(image);
if (imageinfo)
DestroyImageInfo(imageinfo);
MagickCoreTerminus();
// DestroyMagick();
return img;
}
/* save given image */
int save_img(unsigned short *img, int width, int height, int alpha, const char *filename, int index)
{
int rc = -1;
Image *image = NULL;
ImageInfo *imageinfo = NULL;
ExceptionInfo exception;
MagickCoreGenesis(NULL, MagickFalse);
// InitializeMagick(NULL);
imageinfo = CloneImageInfo(0);
GetExceptionInfo(&exception);
imageinfo->quality = 100;
if (strlen(filename) >= 4 && !strcmp(filename + strlen(filename) - 4, ".png"))
imageinfo->quality = 1;
image=ConstituteImage(width, height, (alpha)?"RGBA":"RGB", ShortPixel, img, &exception);
if (!image) {
printf("%s:failed to prepare to write image\n", __func__);
goto exit;
}
/* store as 16 bit, if lib and format supports it */
image->depth = save_depth;
sprintf(image->filename, filename, index); /* ACHTUNG: nicht imageinfo!!! */
if (!WriteImage(imageinfo, image)) {
printf("%s:failed to write image\n", __func__);
goto exit;
}
rc = 0;
exit:
if (image)
DestroyImage(image);
if (imageinfo)
DestroyImageInfo(imageinfo);
MagickCoreTerminus();
// DestroyMagick();
return rc;
}
#else
/* load given image to memory. return short RGB values */
unsigned short *load_img(int *width, int *height, const char *filename, int index)
{
FILE *fp = NULL;
unsigned short *img = NULL;
char line[256];
int words, i;
sprintf(line, filename, index);
// printf("reading image: %s\n", line);
fp = fopen(line, "r");
if (!fp) {
// printf("failed to read ppm image '%s'\n", filename);
goto exit;
}
again1:
if (!fgets(line, sizeof(line), fp)) {
printf("%s:failed to read image depth\n", __func__);
goto exit;
}
line[sizeof(line)-1] = '\0';
if (line[0]) line[strlen(line)-1] = '\0';
if (line[0] == '#')
goto again1;
if (!!strcmp(line, "P6")) {
printf("%s:expecting image depth 'P6'\n", __func__);
goto exit;
}
again2:
if (!fgets(line, sizeof(line), fp)) {
printf("%s:failed to read image size\n", __func__);
goto exit;
}
line[sizeof(line)-1] = '\0';
if (line[0]) line[strlen(line)-1] = '\0';
if (line[0] == '#')
goto again2;
sscanf(line, "%d %d", width, height);
// printf("Image size: w=%d h=%d\n", *width, *height);
again3:
if (!fgets(line, sizeof(line), fp)) {
printf("%s:failed to read line '255' or '65535'\n", __func__);
goto exit;
}
line[sizeof(line)-1] = '\0';
if (line[0]) line[strlen(line)-1] = '\0';
if (line[0] == '#')
goto again3;
if (!strcmp(line, "255")) {
words = 1;
} else
if (!strcmp(line, "65535")) {
words = 2;
} else {
printf("%s:expecting line '255' or '65535'\n", __func__);
goto exit;
}
img = (unsigned short *)malloc((*width) * (*height) * 3 * 2);
if (!img) {
printf("%s:failed to allocate image data\n", __func__);
goto exit;
}
if (fread(img, (*width) * (*height) * 3 * words, 1, fp) != 1) {
printf("%s:failed to read image data\n", __func__);
goto exit;
}
/* char to short (255 -> 65535) */
if (words == 1) {
unsigned char *from = (unsigned char *)img, c;
for (i = (*width) * (*height) * 3 - 1; i >= 0; i--) {
c = from[i];
img[i] = (c << 8) | c;
}
} else {
/* correct byte order */
unsigned short v;
unsigned char *from = (unsigned char *)img;
for (i = 0; i < (*width) * (*height) * 3; i++) {
v = ((*from++) << 8);
v |= (*from++);
img[i] = v;
}
}
exit:
if (fp)
fclose(fp);
return img;
}
/* save given image */
int save_img(unsigned short *img, int width, int height, int alpha, const char *filename, int index)
{
FILE *fp = NULL;
int rc = -1;
char line[256];
int i;
unsigned short v;
unsigned char *to;
if (alpha) {
printf("%s:cannot save alpha component with PPM support only\n", __func__);
alpha = 0;
goto exit;
}
sprintf(line, filename, index);
// printf("writing image: %s\n", line);
fp = fopen(line, "w");
if (!fp) {
printf("%s:failed to write image\n", __func__);
goto exit;
}
fprintf(fp, "P6\n%d %d\n65535\n", width, height);
/* correct byte order, write and restore byte order */
to = (unsigned char *)img;
for (i = 0; i < width * height * 3; i++) {
v = img[i];
if (i/100*i == i) { printf("%04x ", v); }
(*to++) = v >> 8;
(*to++) = v;
}
rc = fwrite(img, width * height * 3 * 2, 1, fp);
to = (unsigned char *)img;
for (i = 0; i < width * height * 3; i++) {
v = (*to++) << 8;
v |= (*to++);
img[i] = v;
}
if (rc != 1) {
printf("%s:failed to write image data\n", __func__);
goto exit;
}
rc = 0;
exit:
if (fp)
fclose(fp);
return rc;
}
#endif
int save_img_array(double *array, int width, int height, int alpha, const char *filename, int index)
{
int rc = -1;
unsigned short *img = NULL;
int components;
#ifndef WITH_MAGICK
if (alpha) {
printf("%s:warning, cannot save alpha component with PPM support only\n", __func__);
alpha = 0;
}
#endif
components = (alpha) ? 4 : 3;
img = (unsigned short *)malloc(width * height * components * 2);
if (!img) {
printf("%s:failed to allocate image data\n", __func__);
goto exit;
}
array2img_short(array, width, height, img, width, height, alpha);
save_img(img, width, height, alpha, filename, index);
rc = 0;
exit:
if (img)
free(img);
return rc;
}
/* convert an image to a three dimensional array of double
* the size is: width, height, 3
*/
void img2array_short(unsigned short *img, int iw, int ih, double *array, int aw, int ah)
{
int x, y;
int channel;
double r, g, b;
channel = aw * ah;
for (y = 0; y < ih; y++) {
for (x = 0; x < iw; x++) {
r = img[(x+iw*y)*3] / 65535.0F;
g = img[(x+iw*y)*3+1] / 65535.0F;
b = img[(x+iw*y)*3+2] / 65535.0F;
array[x+aw*y] = r;
array[x+aw*y+channel] = g;
array[x+aw*y+channel+channel] = b;
}
}
}
/* convert a three dimensional array of double to an image
* the size is: width, height, 3
*/
void array2img_short(double *array, int aw, int ah, unsigned short *img, int iw, int ih, int alpha)
{
int x, y, c;
int channel, components;
double r, g, b, a;
channel = aw * ah;
components = (alpha) ? 4 : 3;
for (y = 0; y < ih; y++) {
for (x = 0; x < iw; x++) {
r = array[x+aw*y];
c = (r * 65535.0F + 0.5F);
if (c < 0)
c = 0;
else if (c > 65535)
c = 65535;
img[(x+iw*y)*components] = c;
g = array[x+aw*y+channel];
c = (g * 65535.0F + 0.5F);
if (c < 0)
c = 0;
else if (c > 65535)
c = 65535;
img[(x+iw*y)*components+1] = c;
b = array[x+aw*y+channel+channel];
c = (b * 65535.0F + 0.5F);
if (c < 0)
c = 0;
else if (c > 65535)
c = 65535;
img[(x+iw*y)*components+2] = c;
if (alpha) {
a = array[x+aw*y+channel+channel+channel];
c = (a * 65535.0F + 0.5F);
if (c < 0)
c = 0;
else if (c > 65535)
c = 65535;
img[(x+iw*y)*components+3] = c;
}
}
}
}
/*
* scale down image in img_buffer by calculating average
*/
void scale_img(unsigned short *img, int width, int height, int scale)
{
int w, h, i, j, x, y;
int r, g, b;
if (scale == 1)
return;
w = width / scale;
h = height / scale;
for (i = 0; i < h; i++) {
for (j = 0; j < w; j++) {
r = g = b = 0;
for (y = 0; y < scale; y++) {
for (x = 0; x < scale; x++) {
r += img[((i*scale+y) * width + j*scale+x) * 3 + 0];
g += img[((i*scale+y) * width + j*scale+x) * 3 + 1];
b += img[((i*scale+y) * width + j*scale+x) * 3 + 2];
}
}
img[(i * w + j)*3 + 0] = r / scale / scale;
img[(i * w + j)*3 + 1] = g / scale / scale;
img[(i * w + j)*3 + 2] = b / scale / scale;
}
}
}

7
src/common/img.h Normal file
View File

@ -0,0 +1,7 @@
extern int save_depth;
unsigned short *load_img(int *width, int *height, const char *filename, int index);
int save_img(unsigned short *img, int width, int height, int alpha, const char *filename, int index);
int save_img_array(double *array, int width, int height, int alpha, const char *filename, int index);
void img2array_short(unsigned short *img, int iw, int ih, double *array, int aw, int ah);
void array2img_short(double *array, int aw, int ah, unsigned short *img, int iw, int ih, int alpha);
void scale_img(unsigned short *img, int width, int height, int scale);

40
src/tv/Makefile.am Normal file
View File

@ -0,0 +1,40 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) -fstack-check
bin_PROGRAMS = \
osmotv
osmotv_SOURCES = \
bas.c \
fubk.c \
font.c \
vcr.c \
image.c \
tv_modulate.c \
channels.c \
main.c
osmotv_LDADD = \
$(COMMON_LA) \
$(top_builddir)/src/common/libimage.a \
$(top_builddir)/src/common/libcommon.a \
$(ALSA_LIBS) \
$(UHD_LIBS) \
$(SOAPY_LIBS) \
$(GRAPHICSMAGICK_LIBS) $(IMAGEMAGICK_LIBS) \
-lm
if HAVE_SDR
AM_CPPFLAGS += -DHAVE_SDR
endif
if HAVE_UHD
AM_CPPFLAGS += -DHAVE_UHD
endif
if HAVE_SOAPY
AM_CPPFLAGS += -DHAVE_SOAPY
endif
if ENABLE_MAGICK
AM_CPPFLAGS += -DWITH_MAGICK
endif

284
src/tv/bas.c Normal file
View File

@ -0,0 +1,284 @@
/* generate a BAS signal
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdint.h>
#include <math.h>
#include "../common/sample.h"
#include "../common/iir_filter.h"
#include "bas.h"
#include "vcr.h"
#include "fubk.h"
#include "image.h"
#define WHITE_LEVEL 1.0
#define BLACK_LEVEL 0.32
#define PORCH_LEVEL 0.3
#define SYNC_LEVEL 0.0
#define H_SYNC_START 0.0000015
#define H_SYNC_STOP 0.0000062
#define H_LINE_START 0.000012
#define H_LINE_END 0.000064
#define H_SYNC2_START (H_SYNC_START + H_LINE_END/2.0)
#define H_SYNC2_STOP (H_SYNC_STOP + H_LINE_END/2.0)
#define V_SYNC_STOP (H_SYNC2_START - (H_SYNC_STOP - H_SYNC_START))
#define V_SYNC2_STOP (H_SYNC_START - (H_SYNC_STOP - H_SYNC_START) + H_LINE_END) // wraps, so we substract H_LINE_END
#define SYNC_RAMP 0.0000003
#define IMAGE_RAMP 0.0000002
#define H_CBURST_START 0.0000068
#define H_CBURST_STOP 0.0000094
#define COLOR_CARRIER 4433618.75
#define COLOR_OFFSET 0.0000004
#define BURST_AMPLITUDE 0.15
#define COLOR_FILTER_ITER 1
void bas_init(bas_t *bas, double samplerate, enum bas_type type, int fbas, double circle_radius, int color_bar, int grid_only, const char *station_id, unsigned short *img, int width, int height)
{
memset(bas, 0, sizeof(*bas));
bas->samplerate = samplerate;
bas->type = type;
bas->fbas = fbas;
bas->v_polarity = 1;
bas->circle_radius = circle_radius;
bas->color_bar = color_bar;
bas->grid_only = grid_only;
bas->station_id = station_id;
bas->img = img;
bas->img_width = width;
bas->img_height = height;
/* filter color signal */
iir_lowpass_init(&bas->lp_u, 1300000.0, samplerate, COLOR_FILTER_ITER);
iir_lowpass_init(&bas->lp_v, 1300000.0, samplerate, COLOR_FILTER_ITER);
/* filter final FBAS, so we prevent from beeing in the audio carrier spectrum */
iir_lowpass_init(&bas->lp_y, 4500000.0, samplerate, COLOR_FILTER_ITER);
}
static inline double ramp(double x)
{
return 0.5 - 0.5 * cos(x * M_PI);
}
int bas_generate(bas_t *bas, sample_t *sample)
{
double step = 1.0 / bas->samplerate;
int total_i = 0, i, c, line, middlefield_line;
double x = 0, render_start, render_end;
int have_image;
sample_t color_u[(int)(bas->samplerate / 15625.0) + 10];
sample_t color_v[(int)(bas->samplerate / 15625.0) + 10];
double _sin, _cos;
double color_step = COLOR_CARRIER / bas->samplerate * 2 * M_PI;
/* the offset is specified by delaying Y signal by 0.4 uS. */
// additianlly we compensate the delay caused by the color filter, that is 2 samples per iteration */
int color_offset = (int)(bas->samplerate * COLOR_OFFSET); // + 2 * COLOR_FILTER_ITER;
for (line = 0; line < 625; line++) {
/* reset color */
memset(color_u, 0, sizeof(color_u));
memset(color_v, 0, sizeof(color_v));
/* render image interlaced */
have_image = 1;
/* switch off to have black image */
#if 1
if (line >= 24-1 && line <= 310-1)
middlefield_line = (line - (24-1)) * 2 + 1;
else if (line >= 336-1 && line <= 622-1)
middlefield_line = (line - (336-1)) * 2;
else
have_image = 0;
if (have_image) {
switch (bas->type) {
case BAS_FUBK:
/* render FUBK test image */
fubk_gen_line(sample, x, bas->samplerate, color_u, color_v, bas->v_polarity, H_LINE_START, H_LINE_END, middlefield_line, bas->circle_radius, bas->color_bar, bas->grid_only, bas->station_id);
break;
case BAS_IMAGE: {
/* 574 lines of image are to be rendered */
int img_line = middlefield_line - (574 - bas->img_height) / 2;
if (img_line >= 0 && img_line < bas->img_height) {
/* render image data */
image_gen_line(sample, x, bas->samplerate, color_u, color_v, bas->v_polarity, H_LINE_START, H_LINE_END, bas->img + bas->img_width * img_line * 3, bas->img_width);
}
}
break;
case BAS_VCR:
/* render VCR test image */
vcr_gen_line(sample, x, bas->samplerate, color_u, color_v, bas->v_polarity, H_LINE_START, H_LINE_END, middlefield_line / 2);
break;
}
}
#endif
i = 0;
/* porch before sync */
render_start = H_SYNC_START - SYNC_RAMP / 2;
while (x < render_start) {
sample[i++] = PORCH_LEVEL;
x += step;
}
/* ramp to sync level */
render_end = render_start + SYNC_RAMP;
while (x < render_end) {
sample[i++] = ramp((x - render_start) / SYNC_RAMP) * (SYNC_LEVEL - PORCH_LEVEL) + PORCH_LEVEL;
x += step;
}
/* sync (long sync for vertical blank) */
if (line <= 3-1 || line == 314-1 || line == 315-1)
render_start = V_SYNC_STOP - SYNC_RAMP / 2;
else
render_start = H_SYNC_STOP - SYNC_RAMP / 2;
while (x < render_start) {
sample[i++] = SYNC_LEVEL;
x += step;
}
/* ramp to porch level */
render_end = render_start + SYNC_RAMP;
while (x < render_end) {
sample[i++] = ramp((x - render_start) / SYNC_RAMP) * (PORCH_LEVEL - SYNC_LEVEL) + SYNC_LEVEL;
x += step;
}
if (have_image) {
/* porch after sync, before color burst */
render_start = H_CBURST_START;
while (x < render_start) {
sample[i++] = PORCH_LEVEL;
x += step;
}
/* porch after sync, color burst */
render_start = H_CBURST_STOP;
while (x < render_start) {
/* shift color burst to the right, it is shifted back when modulating */
color_u[i+color_offset] = -0.5 * BURST_AMPLITUDE; /* - 180 degrees */
color_v[i+color_offset] = 0.5 * BURST_AMPLITUDE * (double)bas->v_polarity; /* +- 90 degrees */
sample[i++] = PORCH_LEVEL;
x += step;
}
/* porch after sync, after color burst */
render_start = H_LINE_START;
while (x < render_start) {
sample[i++] = PORCH_LEVEL;
x += step;
}
/* ramp to image */
render_end = render_start + IMAGE_RAMP;
while (x < render_end) {
/* scale level of image to range of BAS signal */
sample[i] = sample[i] * (WHITE_LEVEL - BLACK_LEVEL) + BLACK_LEVEL;
/* ramp from porch level to image level */
sample[i] = ramp((x - render_start) / IMAGE_RAMP) * (sample[i] - PORCH_LEVEL) + PORCH_LEVEL;
i++;
x += step;
}
/* image */
render_start = H_LINE_END - IMAGE_RAMP;
while (x < render_start) {
/* scale level of image to range of BAS signal */
sample[i] = sample[i] * (WHITE_LEVEL - BLACK_LEVEL) + BLACK_LEVEL;
i++;
x += step;
}
/* ramp to porch level */
render_end = H_LINE_END;
while (x < render_end) {
/* scale level of image to range of BAS signal */
sample[i] = sample[i] * (WHITE_LEVEL - BLACK_LEVEL) + BLACK_LEVEL;
/* ramp from image level to porch level */
sample[i] = ramp((x - render_start) / IMAGE_RAMP) * (PORCH_LEVEL - sample[i]) + sample[i];
i++;
x += step;
}
} else {
/* draw porch to second sync */
if (line <= 5-1 || (line >= 311-1 && line <= 317-1) || line >= 623-1) {
/* porch before sync */
render_start = H_SYNC2_START - SYNC_RAMP / 2;
while (x < render_start) {
sample[i++] = PORCH_LEVEL;
x += step;
}
/* ramp to sync level */
render_end = render_start + SYNC_RAMP;
while (x < render_end) {
sample[i++] = ramp((x - render_start) / SYNC_RAMP) * (SYNC_LEVEL - PORCH_LEVEL) + PORCH_LEVEL;
x += step;
}
/* sync (long sync for vertical blank) */
if (line <= 2-1 || line == 313-1 || line == 314-1 || line == 315-1)
render_start = V_SYNC2_STOP - SYNC_RAMP / 2;
else
render_start = H_SYNC2_STOP - SYNC_RAMP / 2;
while (x < render_start) {
sample[i++] = SYNC_LEVEL;
x += step;
}
/* ramp to porch level */
render_end = render_start + SYNC_RAMP;
while (x < render_end) {
sample[i++] = ramp((x - render_start) / SYNC_RAMP) * (PORCH_LEVEL - SYNC_LEVEL) + SYNC_LEVEL;
x += step;
}
}
/* porch to end of line */
render_end = H_LINE_END;
while (x < render_end) {
sample[i++] = PORCH_LEVEL;
x += step;
}
}
if (bas->fbas) {
/* filter color carrier */
iir_process(&bas->lp_u, color_u, i);
iir_process(&bas->lp_v, color_v, i);
/* modulate color to sample */
bas->color_phase = fmod(bas->color_phase + color_step * (double)color_offset, 2.0 * M_PI);
for (c = color_offset; c < i; c++) {
bas->color_phase += color_step;
if (bas->color_phase >= 2.0 * M_PI)
bas->color_phase -= 2.0 * M_PI;
_sin = sin(bas->color_phase);
_cos = cos(bas->color_phase);
sample[c-color_offset] += color_u[c] * _cos - color_v[c] * _sin;
sample[c-color_offset] += color_u[c] * _sin + color_v[c] * _cos;
// puts(debug_amplitude(sample[c-color_offset]));
}
/* filter bas signal */
iir_process(&bas->lp_y, sample, i);
}
/* flip polarity of V signal */
bas->v_polarity = -bas->v_polarity;
/* increment sample buffer to next line */
sample += i;
/* return x */
x -= H_LINE_END;
/* sum total i */
total_i += i;
}
return total_i;
}

25
src/tv/bas.h Normal file
View File

@ -0,0 +1,25 @@
enum bas_type {
BAS_FUBK,
BAS_VCR,
BAS_IMAGE,
};
typedef struct bas {
double samplerate;
enum bas_type type;
int fbas; /* if color shall be added */
double circle_radius; /* radius of circle in grid units */
int color_bar; /* show only color bar on all lines */
int grid_only; /* show only the grid */
const char *station_id; /* text to display as station id */
double color_phase; /* current phase of color carrier */
int v_polarity; /* polarity of V color vector */
unsigned short *img; /* image data, if it should be used */
int img_width, img_height; /* size of image */
iir_filter_t lp_y, lp_u, lp_v; /* low pass filters */
} bas_t;
void bas_init(bas_t *bas, double samplerate, enum bas_type type, int fbas, double circle_radius, int color_bar, int grid_only, const char *station_id, unsigned short *img, int width, int height);
int bas_generate(bas_t *bas, sample_t *sample);

94
src/tv/channels.c Normal file
View File

@ -0,0 +1,94 @@
#include <stdio.h>
static struct tv_channels {
int channel;
double video_mhz;
double audio_mhz;
} tv_channels[] = {
{ 1, 41.25, 46.75 },
{ 2, 48.25, 53.75 },
{ 3, 55.25, 60.75 },
{ 4, 62.25, 67.75 },
{ 5, 175.25, 180.75 },
{ 6, 182.25, 187.75 },
{ 7, 189.25, 194.75 },
{ 8, 196.25, 201.75 },
{ 9, 203.25, 208.75 },
{ 10, 210.25, 215.75 },
{ 11, 217.25, 222.75 },
{ 12, 224.25, 229.75 },
{ 21, 471.25, 476.75 },
{ 22, 479.25, 484.75 },
{ 23, 487.25, 492.75 },
{ 24, 495.25, 500.75 },
{ 25, 503.25, 508.75 },
{ 26, 511.25, 516.75 },
{ 27, 519.25, 524.75 },
{ 28, 527.25, 532.75 },
{ 29, 535.25, 540.75 },
{ 30, 543.25, 548.75 },
{ 31, 551.25, 556.75 },
{ 32, 559.25, 564.75 },
{ 33, 567.25, 572.75 },
{ 34, 575.25, 580.75 },
{ 35, 583.25, 588.75 },
{ 36, 591.25, 596.75 },
{ 37, 599.25, 604.75 },
{ 38, 607.25, 612.75 },
{ 39, 615.25, 620.75 },
{ 40, 623.25, 628.75 },
{ 41, 631.25, 636.75 },
{ 42, 639.25, 644.75 },
{ 43, 647.25, 652.75 },
{ 44, 655.25, 660.75 },
{ 45, 663.25, 668.75 },
{ 46, 671.25, 676.75 },
{ 47, 679.25, 684.75 },
{ 48, 687.25, 692.75 },
{ 49, 695.25, 700.75 },
{ 50, 703.25, 708.75 },
{ 51, 711.25, 716.75 },
{ 52, 719.25, 724.75 },
{ 53, 727.25, 732.75 },
{ 54, 735.25, 740.75 },
{ 55, 743.25, 748.75 },
{ 56, 751.25, 756.75 },
{ 57, 759.25, 764.75 },
{ 58, 767.25, 772.75 },
{ 59, 775.25, 780.75 },
{ 60, 783.25, 788.75 },
{ 61, 791.25, 796.75 },
{ 62, 799.25, 804.75 },
{ 63, 807.25, 812.75 },
{ 64, 815.25, 820.75 },
{ 65, 823.25, 828.75 },
{ 66, 831.25, 836.75 },
{ 67, 839.25, 844.75 },
{ 68, 847.25, 852.75 },
{ 69, 855.25, 860.75 },
{ 0, 0, 0, }
};
double get_tv_video_frequency(int channel)
{
int i;
for (i = 0; tv_channels[i].channel; i++) {
if (tv_channels[i].channel == channel)
return tv_channels[i].video_mhz * 1e6;
}
return 0.0;
}
void list_tv_channels(void)
{
int i;
printf("List of TV channels in MHz:\n\n");
printf("Channel Video Audio\n");
printf("------------------------\n");
for (i = 0; tv_channels[i].channel; i++) {
printf("%d\t%.2f\t%.2f\n", tv_channels[i].channel, tv_channels[i].video_mhz, tv_channels[i].audio_mhz);
}
}

4
src/tv/channels.h Normal file
View File

@ -0,0 +1,4 @@
double get_tv_video_frequency(int channel);
void list_tv_channels(void);

877
src/tv/font.c Normal file
View File

@ -0,0 +1,877 @@
#include <stdint.h>
#include "font.h"
static const uint8_t font_c64_data[] = {
/* --- new character ' ' (32) */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character '!' (33) */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x00, /* ........ */
0x00, /* ........ */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character '"' (34) */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character '#' (35) */
0x66, /* .##..##. */
0x66, /* .##..##. */
0xff, /* ######## */
0x66, /* .##..##. */
0xff, /* ######## */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character '$' (36) */
0x18, /* ...##... */
0x3e, /* ..#####. */
0x60, /* .##..... */
0x3c, /* ..####.. */
0x06, /* .....##. */
0x7c, /* .#####.. */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character '%' (37) */
0x62, /* .##...#. */
0x66, /* .##..##. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x30, /* ..##.... */
0x66, /* .##..##. */
0x46, /* .#...##. */
0x00, /* ........ */
/* --- new character '&' (38) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x38, /* ..###... */
0x67, /* .##..### */
0x66, /* .##..##. */
0x3f, /* ..###### */
0x00, /* ........ */
/* --- new character ''' (39) */
0x06, /* .....##. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character '(' (40) */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x30, /* ..##.... */
0x30, /* ..##.... */
0x30, /* ..##.... */
0x18, /* ...##... */
0x0c, /* ....##.. */
0x00, /* ........ */
/* --- new character ')' (41) */
0x30, /* ..##.... */
0x18, /* ...##... */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x30, /* ..##.... */
0x00, /* ........ */
/* --- new character '*' (42) */
0x00, /* ........ */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0xff, /* ######## */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character '+' (43) */
0x00, /* ........ */
0x18, /* ...##... */
0x18, /* ...##... */
0x7e, /* .######. */
0x18, /* ...##... */
0x18, /* ...##... */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character ',' (44) */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x18, /* ...##... */
0x18, /* ...##... */
0x30, /* ..##.... */
/* --- new character '-' (45) */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x7e, /* .######. */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character '.' (46) */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x18, /* ...##... */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character '/' (47) */
0x00, /* ........ */
0x03, /* ......## */
0x06, /* .....##. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x30, /* ..##.... */
0x60, /* .##..... */
0x00, /* ........ */
/* --- new character '0' (48) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x6e, /* .##.###. */
0x76, /* .###.##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character '1' (49) */
0x18, /* ...##... */
0x18, /* ...##... */
0x38, /* ..###... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x7e, /* .######. */
0x00, /* ........ */
/* --- new character '2' (50) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x06, /* .....##. */
0x0c, /* ....##.. */
0x30, /* ..##.... */
0x60, /* .##..... */
0x7e, /* .######. */
0x00, /* ........ */
/* --- new character '3' (51) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x06, /* .....##. */
0x1c, /* ...###.. */
0x06, /* .....##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character '4' (52) */
0x06, /* .....##. */
0x0e, /* ....###. */
0x1e, /* ...####. */
0x66, /* .##..##. */
0x7f, /* .####### */
0x06, /* .....##. */
0x06, /* .....##. */
0x00, /* ........ */
/* --- new character '5' (53) */
0x7e, /* .######. */
0x60, /* .##..... */
0x7c, /* .#####.. */
0x06, /* .....##. */
0x06, /* .....##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character '6' (54) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x60, /* .##..... */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character '7' (55) */
0x7e, /* .######. */
0x66, /* .##..##. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character '8' (56) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character '9' (57) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3e, /* ..#####. */
0x06, /* .....##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character ':' (58) */
0x00, /* ........ */
0x00, /* ........ */
0x18, /* ...##... */
0x00, /* ........ */
0x00, /* ........ */
0x18, /* ...##... */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character ';' (59) */
0x00, /* ........ */
0x00, /* ........ */
0x18, /* ...##... */
0x00, /* ........ */
0x00, /* ........ */
0x18, /* ...##... */
0x18, /* ...##... */
0x30, /* ..##.... */
/* --- new character '<' (60) */
0x0e, /* ....###. */
0x18, /* ...##... */
0x30, /* ..##.... */
0x60, /* .##..... */
0x30, /* ..##.... */
0x18, /* ...##... */
0x0e, /* ....###. */
0x00, /* ........ */
/* --- new character '=' (61) */
0x00, /* ........ */
0x00, /* ........ */
0x7e, /* .######. */
0x00, /* ........ */
0x7e, /* .######. */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character '>' (62) */
0x70, /* .###.... */
0x18, /* ...##... */
0x0c, /* ....##.. */
0x06, /* .....##. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x70, /* .###.... */
0x00, /* ........ */
/* --- new character '?' (63) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x06, /* .....##. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x00, /* ........ */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character '@' (64) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x6e, /* .##.###. */
0x6e, /* .##.###. */
0x60, /* .##..... */
0x62, /* .##...#. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'A' (65) */
0x18, /* ...##... */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x7e, /* .######. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'B' (66) */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x7c, /* .#####.. */
0x00, /* ........ */
/* --- new character 'C' (67) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'D' (68) */
0x78, /* .####... */
0x6c, /* .##.##.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x6c, /* .##.##.. */
0x78, /* .####... */
0x00, /* ........ */
/* --- new character 'E' (69) */
0x7e, /* .######. */
0x60, /* .##..... */
0x60, /* .##..... */
0x78, /* .####... */
0x60, /* .##..... */
0x60, /* .##..... */
0x7e, /* .######. */
0x00, /* ........ */
/* --- new character 'F' (70) */
0x7e, /* .######. */
0x60, /* .##..... */
0x60, /* .##..... */
0x78, /* .####... */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x00, /* ........ */
/* --- new character 'G' (71) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x60, /* .##..... */
0x6e, /* .##.###. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'H' (72) */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x7e, /* .######. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'I' (73) */
0x3c, /* ..####.. */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'J' (74) */
0x1e, /* ...####. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x6c, /* .##.##.. */
0x38, /* ..###... */
0x00, /* ........ */
/* --- new character 'K' (75) */
0x66, /* .##..##. */
0x6c, /* .##.##.. */
0x78, /* .####... */
0x70, /* .###.... */
0x78, /* .####... */
0x6c, /* .##.##.. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'L' (76) */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x7e, /* .######. */
0x00, /* ........ */
/* --- new character 'M' (77) */
0x63, /* .##...## */
0x77, /* .###.### */
0x7f, /* .####### */
0x6b, /* .##.#.## */
0x63, /* .##...## */
0x63, /* .##...## */
0x63, /* .##...## */
0x00, /* ........ */
/* --- new character 'N' (78) */
0x66, /* .##..##. */
0x76, /* .###.##. */
0x7e, /* .######. */
0x7e, /* .######. */
0x6e, /* .##.###. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'O' (79) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'P' (80) */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x7c, /* .#####.. */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x00, /* ........ */
/* --- new character 'Q' (81) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x0e, /* ....###. */
0x00, /* ........ */
/* --- new character 'R' (82) */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x7c, /* .#####.. */
0x78, /* .####... */
0x6c, /* .##.##.. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'S' (83) */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x60, /* .##..... */
0x3c, /* ..####.. */
0x06, /* .....##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'T' (84) */
0x7e, /* .######. */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character 'U' (85) */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'V' (86) */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character 'W' (87) */
0x63, /* .##...## */
0x63, /* .##...## */
0x63, /* .##...## */
0x6b, /* .##.#.## */
0x7f, /* .####### */
0x77, /* .###.### */
0x63, /* .##...## */
0x00, /* ........ */
/* --- new character 'X' (88) */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x18, /* ...##... */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'Y' (89) */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character 'Z' (90) */
0x7e, /* .######. */
0x06, /* .....##. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x30, /* ..##.... */
0x60, /* .##..... */
0x7e, /* .######. */
0x00, /* ........ */
/* --- new character '[' (91) */
0x3c, /* ..####.. */
0x30, /* ..##.... */
0x30, /* ..##.... */
0x30, /* ..##.... */
0x30, /* ..##.... */
0x30, /* ..##.... */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character '\' (92) */
0x00, /* ........ */
0xc0, /* ##...... */
0x60, /* .##..... */
0x30, /* ..##.... */
0x18, /* ...##... */
0x0c, /* ....##.. */
0x06, /* .....##. */
0x00, /* ........ */
/* --- new character ']' (93) */
0x3c, /* ..####.. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character '^' (94) */
0x18, /* ...##... */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character '_' (95) */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0xff, /* ######## */
/* --- new character '`' (96) */
0x60, /* .##..... */
0x30, /* ..##.... */
0x18, /* ...##... */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character 'a' (97) */
0x00, /* ........ */
0x00, /* ........ */
0x3c, /* ..####.. */
0x06, /* .....##. */
0x3e, /* ..#####. */
0x66, /* .##..##. */
0x3e, /* ..#####. */
0x00, /* ........ */
/* --- new character 'b' (98) */
0x00, /* ........ */
0x60, /* .##..... */
0x60, /* .##..... */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x7c, /* .#####.. */
0x00, /* ........ */
/* --- new character 'c' (99) */
0x00, /* ........ */
0x00, /* ........ */
0x3c, /* ..####.. */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'd' (100) */
0x00, /* ........ */
0x06, /* .....##. */
0x06, /* .....##. */
0x3e, /* ..#####. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3e, /* ..#####. */
0x00, /* ........ */
/* --- new character 'e' (101) */
0x00, /* ........ */
0x00, /* ........ */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x7e, /* .######. */
0x60, /* .##..... */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'f' (102) */
0x00, /* ........ */
0x0e, /* ....###. */
0x18, /* ...##... */
0x3e, /* ..#####. */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character 'g' (103) */
0x00, /* ........ */
0x00, /* ........ */
0x3e, /* ..#####. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3e, /* ..#####. */
0x06, /* .....##. */
0x7c, /* .#####.. */
/* --- new character 'h' (104) */
0x00, /* ........ */
0x60, /* .##..... */
0x60, /* .##..... */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'i' (105) */
0x00, /* ........ */
0x18, /* ...##... */
0x00, /* ........ */
0x38, /* ..###... */
0x18, /* ...##... */
0x18, /* ...##... */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'j' (106) */
0x00, /* ........ */
0x06, /* .....##. */
0x00, /* ........ */
0x06, /* .....##. */
0x06, /* .....##. */
0x06, /* .....##. */
0x06, /* .....##. */
0x3c, /* ..####.. */
/* --- new character 'k' (107) */
0x00, /* ........ */
0x60, /* .##..... */
0x60, /* .##..... */
0x6c, /* .##.##.. */
0x78, /* .####... */
0x6c, /* .##.##.. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'l' (108) */
0x00, /* ........ */
0x38, /* ..###... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'm' (109) */
0x00, /* ........ */
0x00, /* ........ */
0x66, /* .##..##. */
0x7f, /* .####### */
0x7f, /* .####### */
0x6b, /* .##.#.## */
0x63, /* .##...## */
0x00, /* ........ */
/* --- new character 'n' (110) */
0x00, /* ........ */
0x00, /* ........ */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'o' (111) */
0x00, /* ........ */
0x00, /* ........ */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x00, /* ........ */
/* --- new character 'p' (112) */
0x00, /* ........ */
0x00, /* ........ */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x7c, /* .#####.. */
0x60, /* .##..... */
0x60, /* .##..... */
/* --- new character 'q' (113) */
0x00, /* ........ */
0x00, /* ........ */
0x3e, /* ..#####. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3e, /* ..#####. */
0x06, /* .....##. */
0x06, /* .....##. */
/* --- new character 'r' (114) */
0x00, /* ........ */
0x00, /* ........ */
0x7c, /* .#####.. */
0x66, /* .##..##. */
0x60, /* .##..... */
0x60, /* .##..... */
0x60, /* .##..... */
0x00, /* ........ */
/* --- new character 's' (115) */
0x00, /* ........ */
0x00, /* ........ */
0x3e, /* ..#####. */
0x60, /* .##..... */
0x3c, /* ..####.. */
0x06, /* .....##. */
0x7c, /* .#####.. */
0x00, /* ........ */
/* --- new character 't' (116) */
0x00, /* ........ */
0x18, /* ...##... */
0x7e, /* .######. */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x0e, /* ....###. */
0x00, /* ........ */
/* --- new character 'u' (117) */
0x00, /* ........ */
0x00, /* ........ */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3e, /* ..#####. */
0x00, /* ........ */
/* --- new character 'v' (118) */
0x00, /* ........ */
0x00, /* ........ */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character 'w' (119) */
0x00, /* ........ */
0x00, /* ........ */
0x63, /* .##...## */
0x6b, /* .##.#.## */
0x7f, /* .####### */
0x3e, /* ..#####. */
0x36, /* ..##.##. */
0x00, /* ........ */
/* --- new character 'x' (120) */
0x00, /* ........ */
0x00, /* ........ */
0x66, /* .##..##. */
0x3c, /* ..####.. */
0x18, /* ...##... */
0x3c, /* ..####.. */
0x66, /* .##..##. */
0x00, /* ........ */
/* --- new character 'y' (121) */
0x00, /* ........ */
0x00, /* ........ */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x66, /* .##..##. */
0x3e, /* ..#####. */
0x0c, /* ....##.. */
0x78, /* .####... */
/* --- new character 'z' (122) */
0x00, /* ........ */
0x00, /* ........ */
0x7e, /* .######. */
0x0c, /* ....##.. */
0x18, /* ...##... */
0x30, /* ..##.... */
0x7e, /* .######. */
0x00, /* ........ */
/* --- new character '{' (123) */
0x1c, /* ...###.. */
0x30, /* ..##.... */
0x30, /* ..##.... */
0x60, /* .##..... */
0x30, /* ..##.... */
0x30, /* ..##.... */
0x1c, /* ...###.. */
0x00, /* ........ */
/* --- new character '|' (124) */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x18, /* ...##... */
0x00, /* ........ */
/* --- new character '}' (125) */
0x38, /* ..###... */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x06, /* .....##. */
0x0c, /* ....##.. */
0x0c, /* ....##.. */
0x38, /* ..###... */
0x00, /* ........ */
/* --- new character '~' (126) */
0x33, /* ..##..## */
0x7e, /* .######. */
0xcc, /* ##..##.. */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
/* --- new character '' (127) */
0x00, /* ........ */
0x08, /* ....#... */
0x0c, /* ....##.. */
0xfe, /* #######. */
0xfe, /* #######. */
0x0c, /* ....##.. */
0x08, /* ....#... */
0x00, /* ........ */
};
const uint8_t *get_font(char c)
{
if (c < 32) /* implies c > 127 */
c = 32;
return font_c64_data + 8 * (int)(c - 32);
}

3
src/tv/font.h Normal file
View File

@ -0,0 +1,3 @@
const uint8_t *get_font(char c);

511
src/tv/fubk.c Normal file
View File

@ -0,0 +1,511 @@
/* FUBK test image generator
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include "../common/sample.h"
#include "fubk.h"
#include "font.h"
#define GRID_LINES 4
#define GRID_HEIGHT 40
#define CENTER_LINE (287 - 2)
#define GRID_WIDTH 0.0000027
#define RAMP_WIDTH 0.0000002
#define GRID_LEVEL 1.0
#define FIELD_LEVEL 0.25
#define CIRCLE_LEVEL 1.0
#define CIRCLE_CENTER (287 - 1)
#define CIRCLE_HEIGTH 3
struct color_bar {
double amplitude, phase;
} color_bar[8] = {
{0.0, 0.0},
{0.336, 167.1},
{0.474, 283.5},
{0.443, 240.7},
{0.443, 60.7},
{0.474, 103.5},
{0.336, 347.1},
{0.0, 0.0},
};
struct multi_burst {
double width; /* how whide is this portion */
double level; /* level of this portion */
double frequency; /* frequency of burst or zero */
double V; /* amplitude of V */
} multi_burst[10] = {
{ 3.9, 1.0, 0.0, 0.0 }, /* white */
{ 0.7, 0.5, 0.0, 0.0 },
{ 5.0, 0.5, 1000000.0, 0.0 }, /* 1 MHz */
{ 0.7, 0.5, 0.0, 0.0 },
{ 5.0, 0.5, 2000000.0, 0.0 }, /* 2 MHz */
{ 2.0, 0.5, 0.0, 0.0 },
{ 5.0, 0.5, 3000000.0, 0.0 }, /* 3 MHz */
{ 0.7, 0.5, 0.0, 0.0 },
{ 7.9, 0.5, 0.0, 0.5 }, /* Color burst */
{ 1.5, 0.5, 0.0, 0.0 },
/* note that the color burst ist shifted left by 0.4uS when delaying Y.
* this makes color burst appear left of the center.
* we don't compensate this here, since i have seen this shift in real generators. */
};
/* create cosine ramp, so that the bandwidth is not higher than 2 * width of the ramp(0..1) */
static inline double ramp(double x)
{
return 0.5 - 0.5 * cos(x * M_PI);
}
static double mittelfeld(sample_t *sample, double samplerate, int *_i, double *_x, double render_start, double step, sample_t *color_u, sample_t *color_v, int v_polarity, int line, const char *station_id)
{
double render_end, x = *_x, vline_x = *_x, vline_start = render_start;
int i = *_i, vline_i = *_i, b;
double amplitude, phase, phase_step, Y, U, V, colorphase;
int position = line / GRID_HEIGHT;
int char_line;
uint8_t bits;
int bit, last_bit, c;
switch (position) {
case 0:
case 1:
case 2:
/* color bars */
for (b = 0; b < 8; b++) {
Y = (1.0 - (double)b / 7.0) * 0.75;
amplitude = color_bar[b].amplitude;
if (v_polarity < 0)
colorphase = (360.0 - color_bar[b].phase) / 180.0 * M_PI;
else
colorphase = color_bar[b].phase / 180.0 * M_PI;
U = cos(colorphase) * amplitude / 2.0;
V = sin(colorphase) * amplitude / 2.0;
render_end = render_start + GRID_WIDTH * 1.5;
while (x < render_end) {
color_u[i] = U;
color_v[i] = V;
sample[i++] = Y;
x += step;
}
render_start = render_end;
}
break;
case 3:
case 4:
/* gray steps */
for (b = 0; b < 5; b++) {
Y = (double)b / 4.0;
render_end = render_start + GRID_WIDTH * 2.4;
while (x < render_end) {
sample[i++] = Y;
x += step;
}
render_start = render_end;
}
break;
case 5:
/* station ID */
/* white bar before text */
render_end = render_start + GRID_WIDTH * 2.4;
while (x < render_end) {
sample[i++] = 1.0;
x += step;
}
render_start = render_end;
/* black bar before text (exclude one ramp width) */
render_end = render_start + GRID_WIDTH * 2.4 / 54.0 - RAMP_WIDTH / 2.0;
while (x < render_end) {
sample[i++] = 0.0;
x += step;
}
render_start = render_end;
last_bit = 0;
/* text */
/* 12 chars, plus one extra bit */
for (c = 0; c < 13; c++) {
char_line = ((line % GRID_HEIGHT) + 1) / 4 - 2;
if (char_line < 0 || char_line > 7 || c == 12)
bits = 0x00;
else
bits = get_font(station_id[c])[char_line];
for (b = 0; b < 8; b++) {
bit = ((bits << b) & 128);
if (!last_bit && !bit) {
/* keep black */
render_end = render_start + RAMP_WIDTH;
while (x < render_end) {
sample[i++] = 0.0;
x += step;
}
render_start = render_end;
}
if (last_bit && bit) {
/* keep white */
render_end = render_start + RAMP_WIDTH;
while (x < render_end) {
sample[i++] = 1.0;
x += step;
}
render_start = render_end;
}
if (!last_bit && bit) {
/* ramp to white */
render_end = render_start + RAMP_WIDTH;
while (x < render_end) {
sample[i++] = 1.0 - ramp((render_end - x) / RAMP_WIDTH);
x += step;
}
render_start = render_end;
}
if (last_bit && !bit) {
/* ramp to black */
render_end = render_start + RAMP_WIDTH;
while (x < render_end) {
sample[i++] = ramp((render_end - x) / RAMP_WIDTH);
x += step;
}
render_start = render_end;
}
last_bit = bit;
/* after extra bit after 12 chars, stop rendering */
if (c == 12)
break;
}
}
/* black bar after text */
render_end = render_start + GRID_WIDTH * 2.4 / 54.0 - RAMP_WIDTH / 2.0;
while (x < render_end) {
sample[i++] = 0.0;
x += step;
}
render_start = render_end;
/* white bar after text */
render_end = render_start + GRID_WIDTH * 2.4;
while (x < render_end) {
sample[i++] = 1.0;
x += step;
}
render_start = render_end;
break;
case 6:
/* multi-burst */
for (b = 0; b < 10; b++) {
Y = multi_burst[b].level;
V = multi_burst[b].V;
phase_step = multi_burst[b].frequency * 2.0 * M_PI / samplerate;
phase = 0.0;
render_end = render_start + multi_burst[b].width / 1e6;
while (x < render_end) {
if (v_polarity < 0)
colorphase = (360.0 - 145.9) / 180.0 * M_PI;
else
colorphase = 145.9 / 180.0 * M_PI;
color_u[i] = cos(colorphase) * V / 2.0;
color_v[i] = sin(colorphase) * V / 2.0;
sample[i++] = Y + sin(phase) / 2.0;
phase += phase_step;
x += step;
}
render_start = render_end;
}
break;
case 7:
/* white bar with black pulse triangle */
/* white bar */
render_end = render_start + GRID_WIDTH * 6 - 0.0000005 - RAMP_WIDTH;
while (x < render_end) {
sample[i++] = 1.0;
x += step;
}
/* ramp to triangle */
render_end += RAMP_WIDTH;
while (x < render_end) {
sample[i++] = ramp((render_end - x) / RAMP_WIDTH);
x += step;
}
/* triangle */
render_end += 0.000001 * (1.0 - ((double)(line % GRID_HEIGHT) / (double)GRID_HEIGHT));
while (x < render_end) {
sample[i++] = 0.0;
x += step;
}
/* ramp from triangle */
render_end += RAMP_WIDTH;
while (x < render_end) {
sample[i++] = 1.0 - ramp((render_end - x) / RAMP_WIDTH);
x += step;
}
/* white bar */
render_end = render_start + GRID_WIDTH * 12;
while (x < render_end) {
sample[i++] = 1.0;
x += step;
}
render_start = render_end;
break;
case 8:
/* sawtooth +-V and uncolored field */
render_end = render_start + GRID_WIDTH * 8;
while (x < render_end) {
Y = (render_end - x) / (render_end - render_start) / 2;
if (Y > 0.375) Y = 0.375;
color_v[i] = (double)v_polarity * Y;
sample[i++] = Y;
x += step;
}
uncolored:
/* uncolored +V field */
render_end = render_start + GRID_WIDTH * 10;
while (x < render_end) {
color_v[i] = 0.375;
sample[i++] = 0.375;
x += step;
}
/* uncolored +-U field */
render_end = render_start + GRID_WIDTH * 12;
while (x < render_end) {
color_u[i] = (double)v_polarity * 0.375;
sample[i++] = 0.375;
x += step;
}
render_start = render_end;
break;
case 9:
/* sawtooth +U and uncolored field */
render_end = render_start + GRID_WIDTH * 8;
while (x < render_end) {
Y = (render_end - x) / (render_end - render_start) / 2;
if (Y > 0.375) Y = 0.375;
color_u[i] = Y;
sample[i++] = Y;
x += step;
}
goto uncolored;
}
/* draw vertical line */
if (position >= 3 && position <= 6) {
render_end = vline_start + GRID_WIDTH * 6 - RAMP_WIDTH;
while (vline_x < render_end) {
vline_i++;
vline_x += step;
}
render_end += RAMP_WIDTH;
while (vline_x < render_end) {
sample[vline_i] = ramp((render_end - vline_x) / RAMP_WIDTH) * (sample[vline_i] - GRID_LEVEL) + GRID_LEVEL;
vline_i++;
vline_x += step;
}
render_end += RAMP_WIDTH;
while (vline_x < render_end) {
sample[vline_i] = ramp((render_end - vline_x) / RAMP_WIDTH) * (GRID_LEVEL - sample[vline_i]) + sample[vline_i];
vline_i++;
vline_x += step;
}
}
*_x = x;
*_i = i;
return render_start;
}
static void draw_line(sample_t *sample, sample_t *color_u, sample_t *color_v, int i, double step, double x, double line_end, double x_start, double x_stop)
{
double render_start, render_end;
/* go to ramp start */
render_start = x_start - RAMP_WIDTH;
while (x < render_start && x < line_end) {
i++;
x += step;
}
/* ramp up to line */
render_end = render_start + RAMP_WIDTH;
while (x < render_end && x < line_end) {
color_u[i] = color_v[i] = 0;
sample[i] = ramp((x - render_start) / RAMP_WIDTH) * (CIRCLE_LEVEL - sample[i]) + sample[i];
i++;
x += step;
}
/* draw line */
render_start = x_stop;
while (x < render_start && x < line_end) {
color_u[i] = color_v[i] = 0;
sample[i++] = CIRCLE_LEVEL;
x += step;
}
/* ramp down from line */
render_end = render_start + RAMP_WIDTH;
while (x < render_end && x < line_end) {
color_u[i] = color_v[i] = 0;
sample[i] = ramp((x - render_start) / RAMP_WIDTH) * (sample[i] - CIRCLE_LEVEL) + CIRCLE_LEVEL;
i++;
x += step;
}
}
static void kreislinie(sample_t *sample, sample_t *color_u, sample_t *color_v, int i, double step, double x, double line_end, int line, double circle_radius)
{
double dist_center = abs(CIRCLE_CENTER - line), y, oc, ic;
double center_x = (line_end - x) / 2.0 + x;
/* no radius, no circle */
if (circle_radius < 0.1)
return;
/* check if we are above or below outer circle */
y = dist_center / ((double)GRID_HEIGHT * circle_radius);
if (y > 1.0)
return;
/* calc outer circle */
oc = sqrt(2*(y+1) - (y+1)*(y+1)) * GRID_WIDTH * circle_radius + (double)(line & 1)/40000000.0;
/* check if we are above or below inner circle */
y = dist_center / ((double)GRID_HEIGHT * circle_radius - CIRCLE_HEIGTH);
if (y > 1.0) {
/* draw outer circle only */
draw_line(sample, color_u, color_v, i, step, x, line_end, center_x - oc, center_x + oc);
return;
}
/* calc inner circle */
ic = sqrt(2*(y+1) - (y+1)*(y+1)) * GRID_WIDTH * circle_radius;
/* draw both circles */
draw_line(sample, color_u, color_v, i, step, x, line_end, center_x - oc, center_x - ic);
draw_line(sample, color_u, color_v, i, step, x, line_end, center_x + ic, center_x + oc);
}
/* render test image line starting with x and end with LINE_LENGTH
*
* sample: pointer to samples, starting at x = 0
* samplerate: is the sample rate in hertz
* x: at what x to start rendering
* line: line no. to render
*/
int fubk_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double line_start, double line_end, int line, double radius, int color_bar, int grid_only, const char *station_id)
{
double initial_x;
double step = 1.0 / samplerate;
int i = 0, initial_i;
double render_start, render_end, center_x;
int grid_count = 0;
int in_mittelfeld;
/* skip x to line_start */
while (x < line_start && x < line_end) {
i++;
x += step;
}
if (x >= line_end)
return i;
initial_i = i;
initial_x = x;
/* calculate phase for ramp start of center line */
center_x = (line_end - line_start) / 2.0 + line_start;
/* check if we are are rendering middle field and set line number */
if (grid_only)
in_mittelfeld = -1;
else if (color_bar)
in_mittelfeld = 0;
else {
in_mittelfeld = line - (CENTER_LINE - GRID_HEIGHT*5) -GRID_LINES/2;
if (in_mittelfeld >= GRID_HEIGHT*10)
in_mittelfeld = -1;
}
/* calculate position in grid:
* get the distance below the center line (line - CENTER_LINE)
* then be sure not to get negative value: add a multiple of the grid period
* then use modulo to get the distance below the grid line */
if (((line - CENTER_LINE + GRID_HEIGHT*20) % GRID_HEIGHT) < GRID_LINES) {
if (in_mittelfeld < 0)
goto no_mittelfeld;
/* special case where the center line is always drawn */
if (in_mittelfeld >= 198 && in_mittelfeld <= 201)
goto no_mittelfeld;
/* grid line before middle field */
render_end = center_x - GRID_WIDTH*6.0;
while (x < render_end && x < line_end) {
sample[i++] = GRID_LEVEL;
x += step;
}
if (x >= line_end)
return i;
render_start = render_end;
/* middle field */
render_start = mittelfeld(sample, samplerate, &i, &x, render_start, step, color_u, color_v, v_polarity, in_mittelfeld, station_id);
no_mittelfeld:
/* grid line after middle field */
while (x < line_end) {
sample[i++] = GRID_LEVEL;
x += step;
}
} else {
while (1) {
/* calculate position for next ramp:
* get the distance to center (center_x - x - RAMP_WIDTH)
* then be sure not to get negative value: add a multiple of the grid period
* then use fmod to get the next ramp start */
render_start = fmod(center_x - x - RAMP_WIDTH + GRID_WIDTH*20.0, GRID_WIDTH) + x;
/* draw background field up to grid start */
while (x < render_start && x < line_end) {
sample[i++] = FIELD_LEVEL;
x += step;
}
if (x >= line_end)
break;
/* ramp up to grid level */
render_end = render_start + RAMP_WIDTH;
while (x < render_end && x < line_end) {
sample[i++] = ramp((x - render_start) / RAMP_WIDTH) * (GRID_LEVEL - FIELD_LEVEL) + FIELD_LEVEL;
x += step;
}
if (x >= line_end)
break;
render_start = render_end;
/* middle field */
if (in_mittelfeld >= 0) {
if (++grid_count == 4)
render_start = mittelfeld(sample, samplerate, &i, &x, render_start, step, color_u, color_v, v_polarity, in_mittelfeld, station_id);
}
/* ramp down to field level */
render_end = render_start + RAMP_WIDTH;
while (x < render_end && x < line_end) {
sample[i++] = ramp((x - render_start) / RAMP_WIDTH) * (FIELD_LEVEL - GRID_LEVEL) + GRID_LEVEL;
x += step;
}
if (x >= line_end)
break;
}
}
kreislinie(sample, color_u, color_v, initial_i, step, initial_x, line_end, line, radius);
return i;
}

3
src/tv/fubk.h Normal file
View File

@ -0,0 +1,3 @@
int fubk_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double frame_start, double line_end, int line, double circle_radius, int color_bar, int grid_only, const char *station_id);

67
src/tv/image.c Normal file
View File

@ -0,0 +1,67 @@
/* Pixle image / PAL conversion
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include "../common/sample.h"
#include "image.h"
/* render image line starting with x and end with LINE_LENGTH
*
* sample: pointer to samples, starting at x = 0
* samplerate: is the sample rate in hertz
* x: at what x to start rendering
* line: line no. to render
*/
int image_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double line_start, double line_end, unsigned short *img, int width)
{
double img_x = 0;
double step = 1.0 / samplerate;
double img_step = (double)width / (samplerate * (line_end - line_start));
int i = 0;
double R, G, B, Y, U, V;
/* skip x to line_start */
while (x < line_start && x < line_end) {
i++;
x += step;
}
if (x >= line_end)
return i;
/* draw pixle into image */
while (x < line_end) {
R = (double)(img[(int)img_x*3+0]) / 65535.0;
G = (double)(img[(int)img_x*3+1]) / 65535.0;
B = (double)(img[(int)img_x*3+2]) / 65535.0;
Y = 0.299 * R + 0.587 * G + 0.114 * B;
U = 0.492 * (B - Y);
V = 0.877 * (R - Y);
sample[i] = Y;
color_u[i] = U;
color_v[i] = V * (double)v_polarity;
i++;
x += step;
img_x += img_step;
if ((int)img_x == width)
break;
}
return i;
}

3
src/tv/image.h Normal file
View File

@ -0,0 +1,3 @@
int image_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double line_start, double line_end, unsigned short *img, int width);

492
src/tv/main.c Normal file
View File

@ -0,0 +1,492 @@
/* main function
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
enum paging_signal;
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <signal.h>
#include <math.h>
#include "../common/sample.h"
#include "../common/iir_filter.h"
#include "../common/fm_modulation.h"
#include "../common/wave.h"
#include "../common/img.h"
#include "../common/debug.h"
#ifdef HAVE_SDR
#include "../common/sdr_config.h"
#include "../common/sdr.h"
#endif
#include "bas.h"
#include "tv_modulate.h"
#include "channels.h"
int use_sdr = 0;
int num_kanal = 1; /* only one channel used for debugging */
void clear_console_text() {}
void print_console_text() {}
void display_status_limit_scroll() {}
static double __attribute__((__unused__)) modulation = 0.7; /* level of modulation for I/Q amplitudes */
static double frequency = 0.0;
static int fbas = 1;
static int tone = 1;
static double circle_radius = 6.7;
static int color_bar = 0;
static int grid_only = 0;
static const char *station_id = "Jolly Roger";
static int __attribute__((__unused__)) latency = 30;
static double samplerate = 10e6;
static const char *wave_file = NULL;
/* global variable to quit main loop */
int quit = 0;
void sighandler(int sigset)
{
if (sigset == SIGHUP)
return;
if (sigset == SIGPIPE)
return;
// clear_console_text();
printf("Signal received: %d\n", sigset);
quit = 1;
}
void print_help(const char *arg0)
{
printf("Usage: %s -f <frequency> | -c <channel> <command>\n", arg0);
/* - - */
printf("\ncommand:\n");
printf(" tx-fubk Transmit FUBK test image (German PAL image)\n");
printf(" tx-vcr Transmit VCR calibration pattern\n");
printf(" tx-img <image> Transmit given image file\n");
printf(" Use 4:3 image with 574 lines for best result.\n");
printf("\ngeneral options:\n");
printf(" -f --frequency <frequency>\n");
printf(" Give frequency in Hertz.\n");
printf(" -c --channel <channel>\n");
printf(" Or give channel number.\n");
printf(" Use 'list' to get a channel list.\n");
printf(" -r --samplerate <frequency>\n");
printf(" Give sample rate in Hertz.\n");
printf(" -w --wave-file <filename>\n");
printf(" Output to wave file instead of SDR\n");
printf("\nsignal options:\n");
printf(" -F --fbas 1 | 0\n");
printf(" Turn color on or off. (default = %d)\n", fbas);
printf(" -T --tone 1 | 0\n");
printf(" Turn tone on or off. (default = %d)\n", tone);
printf("\nFUBK options:\n");
printf(" -R --circle-radius <radius> | 0\n");
printf(" Use circle radius or 0 for off. (default = %.1f)\n", circle_radius);
printf(" -C --color-bar 1 | 0\n");
printf(" 1 == Color bar on, 0 = Show complete middle field. (default = %d)\n", color_bar);
printf(" For clean scope signal, also turn off circle by setting radius to 0.\n");
printf(" -G --grid-only 1 | 0\n");
printf(" 1 == Show only the grid of the test image. (default = %d)\n", grid_only);
printf(" -I --sation-id \"<text>\"\n");
printf(" Give exactly 12 characters to display as Station ID.\n");
printf(" (default = \"%s\")\n", station_id);
#ifdef HAVE_SDR
sdr_config_print_help();
#endif
}
static struct option long_options_common[] = {
{"help", 0, 0, 'h'},
{"frequency", 1, 0, 'f'},
{"channel", 1, 0, 'c'},
{"samplerate", 1, 0, 'r'},
{"wave-file", 1, 0, 'w'},
{"fbas", 1, 0, 'F'},
{"tone", 1, 0, 'T'},
{"circle-radius", 1, 0, 'R'},
{"color-bar", 1, 0, 'C'},
{"grid-only", 1, 0, 'G'},
{"station-id", 1, 0, 'I'},
{0, 0, 0, 0}
};
static const char *optstring_common = "hf:c:r:w:F:T:R:C:G:I:";
struct option *long_options;
char *optstring;
static void check_duplicate_option(int num, struct option *option)
{
int i;
for (i = 0; i < num; i++) {
if (long_options[i].val == option->val) {
fprintf(stderr, "Duplicate option %d. Please fix!\n", option->val);
abort();
}
}
}
void set_options_common(void)
{
int i = 0, j;
long_options = calloc(sizeof(*long_options), 256);
for (j = 0; long_options_common[i].name; i++, j++) {
check_duplicate_option(i, &long_options_common[j]);
memcpy(&long_options[i], &long_options_common[j], sizeof(*long_options));
}
#ifdef HAVE_SDR
for (j = 0; sdr_config_long_options[j].name; i++, j++) {
check_duplicate_option(i, &sdr_config_long_options[j]);
memcpy(&long_options[i], &sdr_config_long_options[j], sizeof(*long_options));
}
#endif
optstring = calloc(256, 2);
strcpy(optstring, optstring_common);
#ifdef HAVE_SDR
strcat(optstring, sdr_config_optstring);
#endif
}
static int handle_options(int argc, char **argv)
{
int skip_args = 0;
#ifdef HAVE_SDR
int rc;
#endif
set_options_common();
while (1) {
int option_index = 0, c;
c = getopt_long(argc, argv, optstring, long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_help(argv[0]);
exit(0);
case 'f':
frequency = atof(optarg);
skip_args += 2;
break;
case 'c':
if (!strcmp(optarg, "list")) {
list_tv_channels();
exit(0);
}
frequency = get_tv_video_frequency(atoi(optarg));
if (frequency == 0.0) {
fprintf(stderr, "Given channel number unknown, use \"-c list\" to get a list.\n");
exit(0);
}
skip_args += 2;
break;
case 'r':
samplerate = atof(optarg);
skip_args += 2;
break;
case 'w':
wave_file = strdup(optarg);
skip_args += 2;
break;
case 'F':
fbas = atoi(optarg);
skip_args += 2;
break;
case 'T':
tone = atoi(optarg);
skip_args += 2;
break;
case 'R':
circle_radius = atof(optarg);
skip_args += 2;
break;
case 'C':
color_bar = atoi(optarg);
skip_args += 2;
break;
case 'G':
grid_only = atoi(optarg);
skip_args += 2;
break;
case 'I':
station_id = strdup(optarg);
if (strlen(station_id) != 12) {
fprintf(stderr, "Given station ID must be exactly 12 charaters long. (Use spaces to fill it.)\n");
exit(0);
}
skip_args += 2;
break;
default:
#ifdef HAVE_SDR
rc = sdr_config_opt_switch(c, &skip_args);
if (rc < 0)
exit(0);
#endif
break;
}
}
return skip_args;
}
static void tx_bas(sample_t *sample_bas, __attribute__((__unused__)) sample_t *sample_tone, uint8_t *power_tone, int samples)
{
/* catch signals */
signal(SIGINT, sighandler);
signal(SIGHUP, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, sighandler);
if (wave_file) {
wave_rec_t rec;
int rc;
sample_t *buffers[1];
rc = wave_create_record(&rec, wave_file, samplerate, 1, 1.0);
if (rc < 0) {
// FIXME cleanup
exit(0);
}
buffers[0] = sample_bas;
wave_write(&rec, buffers, samples);
wave_destroy_record(&rec);
} else {
#ifdef HAVE_SDR
float *buff = NULL;
void *sdr = NULL;
int latspl = samplerate * latency / 1000;
float *sendbuff;
sendbuff = calloc(latspl * 2, sizeof(*sendbuff));
if (!sendbuff) {
fprintf(stderr, "No mem!\n");
goto error;
}
/* modulate */
buff = calloc(samples + 10.0, sizeof(sample_t) * 2);
if (!buff) {
fprintf(stderr, "No mem!\n");
goto error;
}
tv_modulate(buff, samples, sample_bas, modulation);
if (sample_tone) {
/* bandwidth is 2*(deviation + 2*f(sig)) = 2 * (50 + 2*15) = 160khz */
fm_mod_t mod;
fm_mod_init(&mod, samplerate, 5500000.0, modulation * 0.1);
mod.state = MOD_STATE_ON; /* do not ramp up */
fm_modulate_complex(&mod, sample_tone, power_tone, samples, buff);
}
double tx_frequencies[1], rx_frequencies[1];
tx_frequencies[0] = frequency;
rx_frequencies[0] = frequency;
sdr = sdr_open(NULL, tx_frequencies, rx_frequencies, 1, 0.0, samplerate, latspl, 0.0, 0.0);
if (!sdr)
goto error;
sdr_start(sdr);
int pos = 0, max = samples * 2;
int s, ss, tosend;
while (!quit) {
usleep(1000);
sdr_read(sdr, (void *)sendbuff, latspl, 0);
tosend = sdr_get_tosend(sdr, latspl);
if (tosend > latspl / 10)
tosend = latspl / 10;
if (tosend == 0) {
continue;
}
for (s = 0, ss = 0; s < tosend; s++) {
sendbuff[ss++] = buff[pos++];
sendbuff[ss++] = buff[pos++];
if (pos == max) {
pos = 0;
}
}
sdr_write(sdr, (void *)sendbuff, NULL, tosend, NULL, NULL, 0);
}
error:
free(sendbuff);
free(buff);
if (sdr)
sdr_close(sdr);
#endif
}
/* reset signals */
signal(SIGINT, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
}
static int tx_test_picture(enum bas_type type)
{
bas_t bas;
sample_t *test_bas = NULL;
sample_t *test_tone = NULL;
uint8_t *test_power = NULL;
int i;
int ret = -1;
int count;
/* test image, add some samples in case of overflow due to rounding errors */
test_bas = calloc(samplerate / 25.0 * 4.0 + 10.0, sizeof(sample_t));
if (!test_bas) {
fprintf(stderr, "No mem!\n");
goto error;
}
bas_init(&bas, samplerate, type, fbas, circle_radius, color_bar, grid_only, station_id, NULL, 0, 0);
count = bas_generate(&bas, test_bas);
count += bas_generate(&bas, test_bas + count);
count += bas_generate(&bas, test_bas + count);
count += bas_generate(&bas, test_bas + count);
if (tone) {
/* for more about audio modulation on tv, see: http://elektroniktutor.de/geraetetechnik/fston.html */
test_tone = calloc(count, sizeof(sample_t));
if (!test_tone) {
fprintf(stderr, "No mem!\n");
goto error;
}
test_power = calloc(count, sizeof(uint8_t));
if (!test_power) {
fprintf(stderr, "No mem!\n");
goto error;
}
/* emphasis 50us, but 1000Hz does not change level */
for (i = 0; i < count; i++)
test_tone[i] = sin((double)i * 2.0 * M_PI * 1000.0 / samplerate) * 50000;
memset(test_power, 1, count);
}
tx_bas(test_bas, test_tone, test_power, count);
ret = 0;
error:
free(test_bas);
free(test_tone);
free(test_power);
return ret;
}
static int tx_img(const char *filename)
{
unsigned short *img = NULL;
int width, height;
bas_t bas;
sample_t *img_bas = NULL;
int ret = -1;
int count;
img = load_img(&width, &height, filename, 0);
if (!img) {
fprintf(stderr, "Failed to load grey image '%s'\n", filename);
return -1;
}
/* test image, add some samples in case of overflow due to rounding errors */
img_bas = calloc(samplerate / 25.0 * 4.0 + 10.0, sizeof(sample_t));
if (!img_bas) {
fprintf(stderr, "No mem!\n");
goto error;
}
bas_init(&bas, samplerate, BAS_IMAGE, fbas, circle_radius, color_bar, grid_only, NULL, img, width, height);
count = bas_generate(&bas, img_bas);
count += bas_generate(&bas, img_bas + count);
count += bas_generate(&bas, img_bas + count);
count += bas_generate(&bas, img_bas + count);
tx_bas(img_bas, NULL, NULL, count);
ret = 0;
error:
free(img_bas);
free(img);
return ret;
}
int main(int argc, char *argv[])
{
int skip_args;
int __attribute__((__unused__)) rc;
const char *arg0 = argv[0];
debuglevel = 0;
#ifdef HAVE_SDR
sdr_config_init();
#endif
skip_args = handle_options(argc, argv);
argc -= skip_args + 1;
argv += skip_args + 1;
if (frequency == 0.0 && !wave_file) {
print_help(arg0);
exit(0);
}
if (!wave_file) {
#ifdef HAVE_SDR
rc = sdr_configure(samplerate);
if (rc < 0)
return rc;
#endif
}
if (argc < 1) {
fprintf(stderr, "Expecting command, see help!\n");
exit(0);
} else if (!strcmp(argv[0], "tx-fubk")) {
tx_test_picture(BAS_FUBK);
} else if (!strcmp(argv[0], "tx-vcr")) {
tx_test_picture(BAS_VCR);
} else if (!strcmp(argv[0], "tx-img")) {
if (argc < 2) {
fprintf(stderr, "Expecting image file, see help!\n");
exit(0);
}
tx_img(argv[1]);
} else {
fprintf(stderr, "Unknown command '%s', see help!\n", argv[0]);
exit(0);
}
return 0;
}

35
src/tv/tv_modulate.c Normal file
View File

@ -0,0 +1,35 @@
/* television modulator
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdint.h>
#include "../common/sample.h"
#include "tv_modulate.h"
#define WHITE_MODULATION 0.1
void tv_modulate(float *buff, int count, sample_t *bas, double amplitude)
{
int i, ss = 0;
for (i = 0; i < count; i++) {
buff[ss++] = ((1.0 - bas[i]) * (1.0 - WHITE_MODULATION) + WHITE_MODULATION) * amplitude;
buff[ss++] = 0;
}
}

3
src/tv/tv_modulate.h Normal file
View File

@ -0,0 +1,3 @@
void tv_modulate(float *buff, int count, sample_t *bas, double amplitude);

183
src/tv/vcr.c Normal file
View File

@ -0,0 +1,183 @@
/* VCR test image generator
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include "../common/sample.h"
#include "vcr.h"
/* test ID of calibration part:
*
* 1. line: ID of 48 Bits in 52 uS
* 2. line: 50% Gray
* 3. line: 50% Gray +- 25% deviation, frequency 0 Hz
* 4. line: 50% Gray +- 12.5% deviation, frequency 0 Hz
* 5. and 6. line: as line 3 and 4, but frequnency 0.2 MHz
* each next two lines as above, but increments frequency by 0.2
* 63. and 64. line: the incement reaches 6 MHz
*/
#define TEST_ID "VHS V1"
/* create cosine ramp, so that the bandwidth is not higher than 2 * width of the ramp(0..1) */
static inline double ramp(double x)
{
return 0.5 - 0.5 * cos(x * M_PI);
}
int vcr_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double line_start, double line_end, int line)
{
double step = 1.0 / samplerate;
int i = 0;
double Y, Y2, U, V, frequency, colorphase, saturation;
double render_end, n, width;
int b;
/* skip x to line_start */
while (x < line_start && x < line_end) {
i++;
x += step;
}
if (x >= line_end)
return i;
/* select test pattern */
switch (line / 32) {
case 0:
case 1:
/* frequency test
*
* show frequencies from 0.5 MHz to 3.5 MHz */
frequency = (double)(line & 63) / 63.0 * 3000000.0 + 500000.0;
while (x < line_end) {
Y = 1.0 - (line_end - x) / (line_end - line_start);
sample[i++] = 0.5 + Y * 0.5 * sin(x * frequency * 2 * M_PI);
x += step;
}
break;
case 2:
/* level test
*
* show levels from 0 to 100% luminance */
Y = (double)(line & 31) / 31.0;
while (x < line_end) {
sample[i++] = Y;
x += step;
}
break;
case 3:
/* edge test
*
* show edges with 0.5 MHz to 3.5 MHz frequency */
for (n = 0.0; n < 0.99; n += 0.1) {
frequency = (double)(line & 31) / 31.0 * 3000000.0 + 500000.0;
width = 1.0 / frequency / 2.0; /* half wave length */
/* low level before ramping up */
Y = 0.5 - (n + 0.1) / 2.0;
Y2 = 1.0 - Y;
render_end = line_start + (line_end - line_start) / 40.0 * (40.0 * n + 1) - width / 2.0;
while (x < render_end) {
sample[i++] = Y;
x += step;
}
/* ramp up */
render_end += width;
while (x < render_end) {
sample[i++] = ramp((render_end - x) / width) * (Y - Y2) + Y2;
x += step;
}
/* high level before ramping down */
render_end = line_start + (line_end - line_start) / 40.0 * (40.0 * n + 3) - width / 2.0;
while (x < render_end) {
sample[i++] = Y2;
x += step;
}
/* ramp down */
render_end += width;
while (x < render_end) {
sample[i++] = ramp((render_end - x) / width) * (Y2 - Y) + Y;
x += step;
}
/* low level after ramping down */
render_end = line_start + (line_end - line_start) / 40.0 * (40.0 * n + 4);
while (x < render_end) {
sample[i++] = Y;
x += step;
}
}
break;
case 4:
case 5:
/* color test
*
* show color from 0 to 100% saturation */
Y = (1.0 - 5.0 / 7.0) * 0.75;
saturation = (double)(line & 63) / 63.0;
if (v_polarity < 0)
colorphase = (360.0 - 103.5) / 180.0 * M_PI;
else
colorphase = 103.5 / 180.0 * M_PI;
U = cos(colorphase) * saturation * 0.474 / 2.0;
V = sin(colorphase) * saturation * 0.474 / 2.0;
while (x < line_end) {
sample[i++] = Y;
color_u[i] = U;
color_v[i] = V;
x += step;
}
break;
case 6:
case 7:
/* calibration signal
*
* this signal is used to calibrate the frequency response of the de-emphasis
*/
if ((line & 63) == 0) {
/* generate identification line to be detected by the decoder */
for (b = 0; b < 48; b++) {
render_end = line_start + (line_end - line_start) / 48.0 * (double)(b + 1);
Y = (TEST_ID[b / 8] >> (b & 7)) & 1;
while (x < render_end) {
sample[i++] = Y;
x += step;
}
}
} else if ((line & 63) == 1) {
/* generate zero level (50% brightness) */
while (x < line_end) {
sample[i++] = 0.5;
x += step;
}
} else {
/* each pair of lines: upper uses 50% deviation, lower uses 25% deviation */
if ((line & 1) == 0)
Y = 0.5;
else
Y = 0.25;
/* frequency from 0 - 6 MHz in 31 steps (each 0.2 MHz) */
frequency = (double)(((line & 63) - 2) / 2) * 200000.0;
while (x < line_end) {
sample[i++] = 0.5 + Y * 0.5 * cos(x * frequency * 2 * M_PI);
x += step;
}
}
}
return i;
}

3
src/tv/vcr.h Normal file
View File

@ -0,0 +1,3 @@
int vcr_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double line_start, double line_end, int line);