513 lines
14 KiB
C
513 lines
14 KiB
C
/* 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 "../libsample/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.00000015
|
|
#define TEXT_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
|
|
|
|
static 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},
|
|
};
|
|
|
|
static 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;
|
|
V = sin(colorphase) * amplitude;
|
|
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 + TEXT_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 + TEXT_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 + TEXT_WIDTH;
|
|
while (x < render_end) {
|
|
sample[i++] = 1.0 - ramp((render_end - x) / TEXT_WIDTH);
|
|
x += step;
|
|
}
|
|
render_start = render_end;
|
|
}
|
|
if (last_bit && !bit) {
|
|
/* ramp to black */
|
|
render_end = render_start + TEXT_WIDTH;
|
|
while (x < render_end) {
|
|
sample[i++] = ramp((render_end - x) / TEXT_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;
|
|
color_v[i] = sin(colorphase) * V;
|
|
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;
|
|
}
|