/* FUBK test image generator * * (C) 2017 by Andreas Eversberg * All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include "../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; }