/* WebAssembly Example 7 - SSTV decoder Copyright 2019 Ahmet Inan */ #include "quirks.hh" #include "atan2.hh" #include "complex.hh" #include "const.hh" #include "ema.hh" #include "sma.hh" #include "fmd.hh" #include "phasor.hh" #include "trigger.hh" #include "example.hh" const int INPUT_MAX = 16384; float input[INPUT_MAX]; typedef DSP::Complex complex_type; DSP::EMACascade dat_lowpass; DSP::Phasor dat_phasor; DSP::FMD1 dat_demod; DSP::EMACascade cnt_lowpass; DSP::Phasor cnt_phasor; DSP::FMD1 cnt_demod; DSP::SchmittTrigger schmitt_trigger; DSP::RisingEdgeTrigger rising_edge; DSP::FallingEdgeTrigger falling_edge; DSP::SMA1 samples_avg; DSP::SMA1 offset_avg; const int SSTV_WIDTH = 512; const int SSTV_HEIGHT = 1024; const int SSTV_LENGTH = SSTV_WIDTH * SSTV_HEIGHT; int pixels[SSTV_LENGTH]; float line[SSTV_WIDTH]; int sync_holdoff = 0; int sync_timeout = 0; int line_holdoff = 0; int line_timeout = 0; int line_tolerance = 0; int sample_rate = 0; int samples_count = 0; int samples_last = 1; int samples_per_line = 0; int current_position = 0; const float offset_tolerance = 0.001f; float phase_offset = 0.f; void _start() { for (int i = 0; i < SSTV_LENGTH; ++i) pixels[i] = 0xff000000; } int gray(float v) { v = min(max(v, 0.f), 1.f); v = sqrt(v); int V = nearbyint(255.f * v); return 0xff000000|(0x00010101*V); } void new_line() { for (int j = 0; j < SSTV_HEIGHT-1; ++j) for (int i = 0; i < SSTV_WIDTH; ++i) pixels[SSTV_WIDTH*j+i] = pixels[SSTV_WIDTH*(j+1)+i]; for (int i = 0; i < SSTV_WIDTH; ++i) pixels[SSTV_WIDTH*(SSTV_HEIGHT-1)+i] = gray(0.5f + 0.5f * line[i]); } void process_input(int samples) { for (int i = 0; i < samples; ++i) { complex_type cnt = cnt_lowpass(input[i] * cnt_phasor()); complex_type dat = dat_lowpass(input[i] * dat_phasor()); float cnt_power = norm(cnt); float dat_power = norm(dat); float cnt_phase = cnt_demod(cnt); float dat_phase = dat_demod(dat); float level = (cnt_power - dat_power) / max(cnt_power, dat_power); bool hard = schmitt_trigger(level); if (1) { float avg = offset_avg(cnt_phase); float dev = offset_avg.abs_dev(); if (hard && abs(cnt_phase) < 0.8f && dev < offset_tolerance) phase_offset = avg; } ++samples_count; if (rising_edge(hard)) { samples_last = samples_count; samples_count = 0; } if (falling_edge(hard) && sync_holdoff < samples_count && samples_count < sync_timeout) { int avg = samples_avg(samples_last); int dev = samples_avg.abs_dev(); if (dev < line_tolerance && line_holdoff < avg && avg < line_timeout) { samples_per_line = avg; current_position = samples_count; } } int pos = (current_position * SSTV_WIDTH) / samples_per_line; line[pos] = dat_phase; if (++current_position >= samples_per_line) { current_position = 0; new_line(); } } } void change_rate(int rate) { sample_rate = rate; dat_lowpass.cutoff(400, sample_rate); dat_phasor.omega(-1900, sample_rate); dat_demod.bandwidth(800.f / sample_rate); cnt_lowpass.cutoff(100, sample_rate); cnt_phasor.omega(-1200, sample_rate); cnt_demod.bandwidth(200.f / sample_rate); samples_per_line = 0.15f * sample_rate; sync_holdoff = 0.0035f * sample_rate; sync_timeout = 0.0205f * sample_rate; line_holdoff = 0.05f * sample_rate; line_timeout = 1.1f * sample_rate; line_tolerance = 0.0005f * sample_rate; } int input_length() { return INPUT_MAX; } float *input_pointer() { return input; } int freq_offset() { return nearbyint(100.f * phase_offset); } int sstv_length() { return SSTV_LENGTH; } int sstv_width() { return SSTV_WIDTH; } int sstv_height() { return SSTV_HEIGHT; } int *sstv_pointer() { return pixels; }