aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--configure.ac39
-rw-r--r--src/Makefile.am2
-rw-r--r--src/common/Makefile.am11
-rwxr-xr-xsrc/common/img.c388
-rw-r--r--src/common/img.h7
-rw-r--r--src/tv/Makefile.am40
-rw-r--r--src/tv/bas.c284
-rw-r--r--src/tv/bas.h25
-rw-r--r--src/tv/channels.c94
-rw-r--r--src/tv/channels.h4
-rw-r--r--src/tv/font.c877
-rw-r--r--src/tv/font.h3
-rw-r--r--src/tv/fubk.c511
-rw-r--r--src/tv/fubk.h3
-rw-r--r--src/tv/image.c67
-rw-r--r--src/tv/image.h3
-rw-r--r--src/tv/main.c492
-rw-r--r--src/tv/tv_modulate.c35
-rw-r--r--src/tv/tv_modulate.h3
-rw-r--r--src/tv/vcr.c183
-rw-r--r--src/tv/vcr.h3
22 files changed, 3071 insertions, 5 deletions
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 <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;
+ }
+ }
+}
+
+
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 <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;
+}
+
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 <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);
+ }
+}
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 <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);
+}
+
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 <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;
+}
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 <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;
+}
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 <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;
+}
+
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 <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;
+ }
+}
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 <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;
+}
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);
+