From 496aff5a7954111ddf3b64d1dde664164f344e40 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sun, 20 Aug 2017 07:43:41 +0200 Subject: 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" --- .gitignore | 2 + configure.ac | 39 ++- src/Makefile.am | 2 +- src/common/Makefile.am | 11 +- src/common/img.c | 388 ++++++++++++++++++++++ src/common/img.h | 7 + src/tv/Makefile.am | 40 +++ src/tv/bas.c | 284 ++++++++++++++++ src/tv/bas.h | 25 ++ src/tv/channels.c | 94 ++++++ src/tv/channels.h | 4 + src/tv/font.c | 877 +++++++++++++++++++++++++++++++++++++++++++++++++ src/tv/font.h | 3 + src/tv/fubk.c | 511 ++++++++++++++++++++++++++++ src/tv/fubk.h | 3 + src/tv/image.c | 67 ++++ src/tv/image.h | 3 + src/tv/main.c | 492 +++++++++++++++++++++++++++ src/tv/tv_modulate.c | 35 ++ src/tv/tv_modulate.h | 3 + src/tv/vcr.c | 183 +++++++++++ src/tv/vcr.h | 3 + 22 files changed, 3071 insertions(+), 5 deletions(-) create mode 100755 src/common/img.c create mode 100644 src/common/img.h create mode 100644 src/tv/Makefile.am create mode 100644 src/tv/bas.c create mode 100644 src/tv/bas.h create mode 100644 src/tv/channels.c create mode 100644 src/tv/channels.h create mode 100644 src/tv/font.c create mode 100644 src/tv/font.h create mode 100644 src/tv/fubk.c create mode 100644 src/tv/fubk.h create mode 100644 src/tv/image.c create mode 100644 src/tv/image.h create mode 100644 src/tv/main.c create mode 100644 src/tv/tv_modulate.c create mode 100644 src/tv/tv_modulate.h create mode 100644 src/tv/vcr.c create mode 100644 src/tv/vcr.h diff --git a/.gitignore b/.gitignore index f28881e..08e9348 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ compile m4 src/common/libcommon.a src/common/libmobile.a +src/common/libimage.a src/anetz/anetz src/bnetz/bnetz src/cnetz/cnetz @@ -31,6 +32,7 @@ src/amps/amps src/tacs/tacs src/jtacs/jtacs src/r2000/radiocom2000 +src/tv/osmotv sim/cnetz_sim src/test/test_filter src/test/test_compandor diff --git a/configure.ac b/configure.ac index 2f27783..1ffd2ec 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,39 @@ AC_CHECK_LIB([pthread], [main]) 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 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"]) @@ -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_SOAPY, test "x$with_soapy" == "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_soapy" == "xyes"],[AC_MSG_NOTICE( Compiling with SoapySDR support )], [AC_MSG_NOTICE( SoapySDR 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 supported )]) +AS_IF([test "x$somethingmagick" == "xyes"],[AC_MSG_NOTICE( Compiling with ImageMagick )],[AC_MSG_NOTICE( ImageMagick not supported )]) AC_OUTPUT( src/common/Makefile @@ -50,6 +84,7 @@ AC_OUTPUT( src/tacs/Makefile src/jtacs/Makefile src/r2000/Makefile + src/tv/Makefile src/test/Makefile src/Makefile sim/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 695df9e..a248673 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,3 +1,3 @@ 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 diff --git a/src/common/Makefile.am b/src/common/Makefile.am index e69bb57..e5afdc7 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -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 = \ sample.c \ @@ -31,6 +31,9 @@ libmobile_a_SOURCES = \ display_status.c \ main_mobile.c +libimage_a_SOURCES = \ + img.c + if HAVE_SDR AM_CPPFLAGS += -DHAVE_SDR @@ -55,3 +58,7 @@ libcommon_a_SOURCES += \ soapy.c endif +if ENABLE_MAGICK +AM_CPPFLAGS += -DWITH_MAGICK +endif + diff --git a/src/common/img.c b/src/common/img.c new file mode 100755 index 0000000..8a7f5a4 --- /dev/null +++ b/src/common/img.c @@ -0,0 +1,388 @@ +#include +#include +#include +#include "img.h" + +int save_depth = 16; + +#ifdef WITH_MAGICK +#include + +/* 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; + } + } +} + + diff --git a/src/common/img.h b/src/common/img.h new file mode 100644 index 0000000..4541cdf --- /dev/null +++ b/src/common/img.h @@ -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); diff --git a/src/tv/Makefile.am b/src/tv/Makefile.am new file mode 100644 index 0000000..ffee6fd --- /dev/null +++ b/src/tv/Makefile.am @@ -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 + diff --git a/src/tv/bas.c b/src/tv/bas.c new file mode 100644 index 0000000..fb8c70b --- /dev/null +++ b/src/tv/bas.c @@ -0,0 +1,284 @@ +/* generate a BAS signal + * + * (C) 2017 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#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; +} + diff --git a/src/tv/bas.h b/src/tv/bas.h new file mode 100644 index 0000000..42090a6 --- /dev/null +++ b/src/tv/bas.h @@ -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); + diff --git a/src/tv/channels.c b/src/tv/channels.c new file mode 100644 index 0000000..ff155a7 --- /dev/null +++ b/src/tv/channels.c @@ -0,0 +1,94 @@ +#include + +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); + } +} diff --git a/src/tv/channels.h b/src/tv/channels.h new file mode 100644 index 0000000..84d37bc --- /dev/null +++ b/src/tv/channels.h @@ -0,0 +1,4 @@ + +double get_tv_video_frequency(int channel); +void list_tv_channels(void); + diff --git a/src/tv/font.c b/src/tv/font.c new file mode 100644 index 0000000..505cef8 --- /dev/null +++ b/src/tv/font.c @@ -0,0 +1,877 @@ +#include +#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); +} + diff --git a/src/tv/font.h b/src/tv/font.h new file mode 100644 index 0000000..528c700 --- /dev/null +++ b/src/tv/font.h @@ -0,0 +1,3 @@ + +const uint8_t *get_font(char c); + diff --git a/src/tv/fubk.c b/src/tv/fubk.c new file mode 100644 index 0000000..56213e9 --- /dev/null +++ b/src/tv/fubk.c @@ -0,0 +1,511 @@ +/* FUBK test image generator + * + * (C) 2017 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#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; +} diff --git a/src/tv/fubk.h b/src/tv/fubk.h new file mode 100644 index 0000000..e816662 --- /dev/null +++ b/src/tv/fubk.h @@ -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); + diff --git a/src/tv/image.c b/src/tv/image.c new file mode 100644 index 0000000..85ca9f5 --- /dev/null +++ b/src/tv/image.c @@ -0,0 +1,67 @@ +/* Pixle image / PAL conversion + * + * (C) 2017 by Andreas Eversberg + * 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 . + */ + +#include +#include +#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; +} diff --git a/src/tv/image.h b/src/tv/image.h new file mode 100644 index 0000000..1c87984 --- /dev/null +++ b/src/tv/image.h @@ -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); + diff --git a/src/tv/main.c b/src/tv/main.c new file mode 100644 index 0000000..c8a3e78 --- /dev/null +++ b/src/tv/main.c @@ -0,0 +1,492 @@ +/* main function + * + * (C) 2017 by Andreas Eversberg + * 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 . + */ + +enum paging_signal; + +#include +#include +#include +#include +#include +#include +#include +#include +#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 | -c \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 Transmit given image file\n"); + printf(" Use 4:3 image with 574 lines for best result.\n"); + printf("\ngeneral options:\n"); + printf(" -f --frequency \n"); + printf(" Give frequency in Hertz.\n"); + printf(" -c --channel \n"); + printf(" Or give channel number.\n"); + printf(" Use 'list' to get a channel list.\n"); + printf(" -r --samplerate \n"); + printf(" Give sample rate in Hertz.\n"); + printf(" -w --wave-file \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 | 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 \"\"\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; +} + diff --git a/src/tv/tv_modulate.c b/src/tv/tv_modulate.c new file mode 100644 index 0000000..ac93ece --- /dev/null +++ b/src/tv/tv_modulate.c @@ -0,0 +1,35 @@ +/* television modulator + * + * (C) 2017 by Andreas Eversberg + * 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 . + */ + +#include +#include +#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; + } +} diff --git a/src/tv/tv_modulate.h b/src/tv/tv_modulate.h new file mode 100644 index 0000000..462007e --- /dev/null +++ b/src/tv/tv_modulate.h @@ -0,0 +1,3 @@ + +void tv_modulate(float *buff, int count, sample_t *bas, double amplitude); + diff --git a/src/tv/vcr.c b/src/tv/vcr.c new file mode 100644 index 0000000..cfbe84d --- /dev/null +++ b/src/tv/vcr.c @@ -0,0 +1,183 @@ +/* VCR test image generator + * + * (C) 2017 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#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; +} diff --git a/src/tv/vcr.h b/src/tv/vcr.h new file mode 100644 index 0000000..88ca821 --- /dev/null +++ b/src/tv/vcr.h @@ -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); + -- cgit v1.2.3