diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 63913048..0d2ca15c 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -34,7 +34,7 @@ //TODO: Cap Wav viewer position //TODO: Adapt wav viewer position step -//TODO: Optimize wav viewer refresh +//TODO: Use unit_auto_scale //TODO: Remove make_bistream from encoders.cpp, too complex, stinks. bitstream_append should be enough. //TODO: Continue work on proc_afskrx_corr, see python script (it works !) //TODO: Super simple text file viewer @@ -59,10 +59,7 @@ Continuous (Fox-oring) 60s transmit, 240s space (Classic 1/5 min) 60s transmit, 360s space (Classic 1/7 min) */ -//TODO: Use TransmitterView in TEDI/LCR, Numbers, ... //TODO: FreqMan: Remove and rename categories -//TODO: Wav visualizer -//TODO: File browser view ? //TODO: Mousejack ? //TODO: Move frequencykeypad from ui_receiver to ui_widget (used everywhere) //TODO: ADS-B draw trajectory + GPS coordinates + scale, and playback diff --git a/firmware/application/string_format.cpp b/firmware/application/string_format.cpp index 2ad90b1f..f1213d12 100644 --- a/firmware/application/string_format.cpp +++ b/firmware/application/string_format.cpp @@ -170,3 +170,31 @@ std::string to_string_timestamp(const rtc::RTC& value) { to_string_dec_uint(value.minute(), 2, '0') + to_string_dec_uint(value.second(), 2, '0'); } + +std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision) { + const uint32_t powers_of_ten[5] = { 1, 10, 100, 1000, 10000 }; + std::string string { "" }; + uint32_t prefix_index = base_nano; + double integer_part; + double fractional_part; + + precision = std::min((uint32_t)4, precision); + + while (n > 1000) { + n /= 1000.0; + prefix_index++; + } + + fractional_part = modf(n, &integer_part) * powers_of_ten[precision]; + if (fractional_part < 0) + fractional_part = -fractional_part; + + string = to_string_dec_int(integer_part); + if (precision) + string += '.' + to_string_dec_uint(fractional_part, precision, '0'); + + if (prefix_index != 3) + string += unit_prefix[prefix_index]; + + return string; +} diff --git a/firmware/application/string_format.hpp b/firmware/application/string_format.hpp index 8af71b15..40183167 100644 --- a/firmware/application/string_format.hpp +++ b/firmware/application/string_format.hpp @@ -35,6 +35,8 @@ enum TimeFormat { HM = 2 }; +const char unit_prefix[7] { 'n', 'u', 'm', 0, 'k', 'M', 'G' }; + // TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp... std::string to_string_bin(const uint32_t n, const uint8_t l = 0); std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = 0); @@ -47,4 +49,6 @@ std::string to_string_short_freq(const uint64_t f); std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format = YMDHMS); std::string to_string_timestamp(const rtc::RTC& value); +std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision); + #endif/*__STRING_FORMAT_H__*/ diff --git a/firmware/application/ui_afsk_rx.cpp b/firmware/application/ui_afsk_rx.cpp index 2bc75929..4b8fc3e7 100644 --- a/firmware/application/ui_afsk_rx.cpp +++ b/firmware/application/ui_afsk_rx.cpp @@ -62,11 +62,18 @@ AFSKRxView::AFSKRxView(NavigationView& nav) { &field_frequency, &text_debug, &button_modem_setup, + &record_view, &console }); + // DEBUG + record_view.on_error = [&nav](std::string message) { + nav.display_modal("Error", message); + }; + record_view.set_sampling_rate(24000); + // Auto-configure modem for LCR RX (will be removed later) - update_freq(462713300); // 162950000 + update_freq(467225500); // 462713300 auto def_bell202 = &modem_defs[0]; persistent_memory::set_modem_baudrate(def_bell202->baudrate); serial_format_t serial_format; @@ -82,7 +89,6 @@ AFSKRxView::AFSKRxView(NavigationView& nav) { update_freq(f); }; field_frequency.on_edit = [this, &nav]() { - // TODO: Provide separate modal method/scheme? auto new_view = nav.push(receiver_model.tuning_frequency()); new_view->on_changed = [this](rf::Frequency f) { update_freq(f); @@ -106,6 +112,7 @@ AFSKRxView::AFSKRxView(NavigationView& nav) { receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(1750000); + receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); receiver_model.enable(); } @@ -114,6 +121,7 @@ void AFSKRxView::on_data(uint32_t value, bool is_data) { std::string str_byte = ""; if (is_data) { + // Colorize differently after message splits str_console += (char)((console_color & 3) + 9); //value = deframe_word(value); @@ -122,7 +130,7 @@ void AFSKRxView::on_data(uint32_t value, bool is_data) { value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4); // EFGHABCD value = ((value & 0xCC) >> 2) | ((value & 0x33) << 2); // GHEFCDAB value = ((value & 0xAA) >> 1) | ((value & 0x55) << 1); // HGFEDCBA - value &= 0x7F; + value &= 0x7F; // Ignore parity, which is the MSB now if ((value >= 32) && (value < 127)) { str_console += (char)value; // Printable @@ -139,6 +147,7 @@ void AFSKRxView::on_data(uint32_t value, bool is_data) { if (logger) str_log += str_byte; if ((value != 0x7F) && (prev_value == 0x7F)) { + // Message split console.writeln(""); console_color++; diff --git a/firmware/application/ui_afsk_rx.hpp b/firmware/application/ui_afsk_rx.hpp index 9881b32b..86f0f28f 100644 --- a/firmware/application/ui_afsk_rx.hpp +++ b/firmware/application/ui_afsk_rx.hpp @@ -26,6 +26,7 @@ #include "ui.hpp" #include "ui_navigation.hpp" #include "ui_receiver.hpp" +#include "ui_record_view.hpp" // DEBUG #include "log_file.hpp" #include "utility.hpp" @@ -91,8 +92,14 @@ private: "Modem setup" }; + // DEBUG + RecordView record_view { + { 0 * 8, 3 * 16, 30 * 8, 1 * 16 }, + u"AFS_????", RecordView::FileType::WAV, 4096, 4 + }; + Console console { - { 0, 3 * 16, 240, 240 } + { 0, 4 * 16, 240, 240 } }; void update_freq(rf::Frequency f); diff --git a/firmware/application/ui_encoders.cpp b/firmware/application/ui_encoders.cpp index b985d8b7..9ac1e3b5 100644 --- a/firmware/application/ui_encoders.cpp +++ b/firmware/application/ui_encoders.cpp @@ -128,12 +128,8 @@ void EncodersConfigView::on_show() { void EncodersConfigView::draw_waveform() { size_t length = frame_fragments.length(); - for (size_t n = 0; n < length; n++) { - if (frame_fragments[n] == '0') - waveform_buffer[n] = 0; - else - waveform_buffer[n] = 1; - } + for (size_t n = 0; n < length; n++) + waveform_buffer[n] = (frame_fragments[n] == '0') ? 0 : 1; waveform.set_length(length); waveform.set_dirty(); diff --git a/firmware/application/ui_encoders.hpp b/firmware/application/ui_encoders.hpp index 4684db35..43165220 100644 --- a/firmware/application/ui_encoders.hpp +++ b/firmware/application/ui_encoders.hpp @@ -55,7 +55,7 @@ private: //uint8_t scan_count; //double scan_progress; //unsigned int scan_index; - int8_t waveform_buffer[512]; + int16_t waveform_buffer[512]; const encoder_def_t * encoder_def { }; //uint8_t enc_type = 0; diff --git a/firmware/application/ui_view_wav.cpp b/firmware/application/ui_view_wav.cpp index 4719053e..8e403c0e 100644 --- a/firmware/application/ui_view_wav.cpp +++ b/firmware/application/ui_view_wav.cpp @@ -32,29 +32,34 @@ namespace ui { void ViewWavView::update_scale(int32_t new_scale) { scale = new_scale; ns_per_pixel = (1000000000UL / wav_reader->sample_rate()) * scale; - field_pos_samples.set_step(scale); refresh_waveform(); + refresh_measurements(); } void ViewWavView::refresh_waveform() { - int16_t sample; - for (size_t i = 0; i < 240; i++) { wav_reader->data_seek(position + (i * scale)); - wav_reader->read(&sample, 2); - - waveform_buffer[i] = sample >> 8; + wav_reader->read(&waveform_buffer[i], sizeof(int16_t)); } + waveform.set_dirty(); + + // Window + uint64_t w_start = (position * 240) / wav_reader->sample_count(); + uint64_t w_width = (scale * 240) / (wav_reader->sample_count() / 240); + display.fill_rectangle({ 0, 10 * 16 + 1, 240, 16 }, Color::black()); + display.fill_rectangle({ (Coord)w_start, 21 * 8, (Dim)w_width + 1, 8 }, Color::white()); + display.draw_line({ 0, 10 * 16 + 1 }, { (Coord)w_start, 21 * 8 }, Color::white()); + display.draw_line({ 239, 10 * 16 + 1 }, { (Coord)(w_start + w_width), 21 * 8 }, Color::white()); +} + +void ViewWavView::refresh_measurements() { uint64_t span_ns = ns_per_pixel * abs(field_cursor_b.value() - field_cursor_a.value()); + if (span_ns) - text_delta.set(to_string_dec_uint(span_ns / 1000) + "us (" + to_string_dec_uint(1000000000UL / span_ns) + "Hz)"); + text_delta.set(unit_auto_scale(span_ns, 0, 3) + "s (" + to_string_dec_uint(1000000000UL / span_ns) + "Hz)"); else text_delta.set("0us ?Hz"); - - //waveform.set_dirty(); - - set_dirty(); } void ViewWavView::paint(Painter& painter) { @@ -62,22 +67,9 @@ void ViewWavView::paint(Painter& painter) { painter.draw_hline({ 0, 6 * 16 - 1 }, 240, Color::grey()); painter.draw_hline({ 0, 10 * 16 }, 240, Color::grey()); - // 0~127 to 0~15 color index + // Overall amplitude view, 0~127 to 0~255 color index for (size_t i = 0; i < 240; i++) - painter.draw_vline({ (Coord)i, 11 * 16 }, 8, amplitude_colors[amplitude_buffer[i] >> 3]); - - // Window - uint64_t w_start = (position * 240) / wav_reader->sample_count(); - uint64_t w_width = (scale * 240) / (wav_reader->sample_count() / 240); - painter.fill_rectangle({ 0, 10 * 16 + 1, 240, 16 }, Color::black()); - painter.fill_rectangle({ (Coord)w_start, 21 * 8, (Dim)w_width + 1, 8 }, Color::white()); - display.draw_line({ 0, 10 * 16 + 1 }, { (Coord)w_start, 21 * 8 }, Color::white()); - display.draw_line({ 239, 10 * 16 + 1 }, { (Coord)(w_start + w_width), 21 * 8 }, Color::white()); - - // Cursors - painter.fill_rectangle({ 0, 6 * 16 - 8, 240, 7 }, Color::black()); - painter.draw_vline({ (Coord)field_cursor_a.value(), 11 * 8 }, 7, Color::cyan()); - painter.draw_vline({ (Coord)field_cursor_b.value(), 11 * 8 }, 7, Color::magenta()); + painter.draw_vline({ (Coord)i, 11 * 16 }, 8, spectrum_rgb2_lut[amplitude_buffer[i] << 1]); } void ViewWavView::on_pos_changed() { @@ -101,7 +93,7 @@ void ViewWavView::load_wav(std::filesystem::path file_path) { text_filename.set(file_path.filename().string()); auto ms_duration = wav_reader->ms_duration(); - text_duration.set(to_string_dec_uint(ms_duration / 1000) + "s" + to_string_dec_uint(ms_duration % 1000) + "ms"); + text_duration.set(unit_auto_scale(ms_duration, 2, 3) + "s"); wav_reader->rewind(); @@ -117,13 +109,7 @@ void ViewWavView::load_wav(std::filesystem::path file_path) { for (size_t s = 0; s < subsampling_factor; s++) { wav_reader->data_seek(((i * subsampling_factor) + s) * skip); wav_reader->read(&sample, 2); - - if (sample < 0) - sample = -sample; - - sample >>= 8; - - average += sample; + average += (abs(sample) >> 8); } amplitude_buffer[i] = average / subsampling_factor; @@ -135,18 +121,10 @@ void ViewWavView::load_wav(std::filesystem::path file_path) { void ViewWavView::reset_controls() { field_scale.set_value(1); - field_scale.on_change = [this](int32_t value) { - update_scale(value); - }; - field_pos_seconds.set_value(0); - field_pos_seconds.on_change = [this](int32_t) { - on_pos_changed(); - }; field_pos_samples.set_value(0); - field_pos_samples.on_change = [this](int32_t) { - on_pos_changed(); - }; + field_cursor_a.set_value(0); + field_cursor_b.set_value(0); } ViewWavView::ViewWavView( @@ -175,19 +153,30 @@ ViewWavView::ViewWavView( auto open_view = nav.push(); open_view->on_changed = [this](std::filesystem::path file_path) { load_wav(file_path); + field_pos_seconds.focus(); }; }; - reset_controls(); + field_scale.on_change = [this](int32_t value) { + update_scale(value); + }; + field_pos_seconds.on_change = [this](int32_t) { + on_pos_changed(); + }; + field_pos_samples.on_change = [this](int32_t) { + on_pos_changed(); + }; - field_cursor_a.set_value(0); - field_cursor_a.on_change = [this](int32_t) { - refresh_waveform(); + field_cursor_a.on_change = [this](int32_t v) { + waveform.set_cursor(0, v); + refresh_measurements(); }; - field_cursor_b.set_value(0); - field_cursor_b.on_change = [this](int32_t) { - refresh_waveform(); + field_cursor_b.on_change = [this](int32_t v) { + waveform.set_cursor(1, v); + refresh_measurements(); }; + + reset_controls(); } void ViewWavView::focus() { diff --git a/firmware/application/ui_view_wav.hpp b/firmware/application/ui_view_wav.hpp index bd9c5525..3a67ed2f 100644 --- a/firmware/application/ui_view_wav.hpp +++ b/firmware/application/ui_view_wav.hpp @@ -23,6 +23,7 @@ #include "ui.hpp" #include "ui_navigation.hpp" #include "io_wave.hpp" +#include "spectrum_color_lut.hpp" namespace ui { @@ -41,32 +42,14 @@ private: void update_scale(int32_t new_scale); void refresh_waveform(); + void refresh_measurements(); void on_pos_changed(); void load_wav(std::filesystem::path file_path); void reset_controls(); - - const Color amplitude_colors[16] = { - { 0x00, 0x3F, 0xB0 }, - { 0x00, 0x6D, 0xB5 }, - { 0x00, 0x9C, 0xBA }, - { 0x00, 0xBF, 0xB0 }, - { 0x00, 0xC5, 0x86 }, - { 0x00, 0xCA, 0x5A }, - { 0x00, 0xCF, 0x2A }, - { 0x06, 0xD4, 0x00 }, - { 0x3A, 0xDA, 0x00 }, - { 0x71, 0xDF, 0x00 }, - { 0xAA, 0xE4, 0x00 }, - { 0xE6, 0xE9, 0x00 }, - { 0xEF, 0xB9, 0x00 }, - { 0xF4, 0x83, 0x00 }, - { 0xF9, 0x4B, 0x00 }, - { 0xFF, 0x0F, 0x00 } - }; std::unique_ptr wav_reader { }; - int8_t waveform_buffer[240] { }; + int16_t waveform_buffer[240] { }; uint8_t amplitude_buffer[240] { }; int32_t scale { 1 }; uint64_t ns_per_pixel { }; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index d8babd5a..3c9bc96a 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -1465,7 +1465,7 @@ int32_t SymField::clip_value(const uint32_t index, const uint32_t value) { Waveform::Waveform( Rect parent_rect, - int8_t * data, + int16_t * data, uint32_t length, uint32_t offset, bool digital, @@ -1480,6 +1480,16 @@ Waveform::Waveform( //set_focusable(false); } +void Waveform::set_cursor(const uint32_t i, const int16_t position) { + if (i < 2) { + if (position != cursors[i]) { + cursors[i] = position; + set_dirty(); + } + show_cursors = true; + } +} + void Waveform::set_offset(const uint32_t new_offset) { if (new_offset != offset_) { offset_ = new_offset; @@ -1495,28 +1505,26 @@ void Waveform::set_length(const uint32_t new_length) { } void Waveform::paint(Painter& painter) { - uint32_t n, point_count; + size_t n; Coord y, y_offset = screen_rect().location().y(); Coord prev_x = screen_rect().location().x(), prev_y; float x, x_inc; Dim h = screen_rect().size().height(); - int8_t * data_start = data_ + offset_; + const float y_scale = (float)(h - 1) / 65536.0; + int16_t * data_start = data_ + offset_; // Clear painter.fill_rectangle(screen_rect(), Color::black()); + if (!length_) return; + x_inc = (float)screen_rect().size().width() / length_; - point_count = length_; - const float y_scale = (float)(h - 1) / 256; // TODO: Make variable - - if (!point_count) return; - if (digital_) { // Digital waveform: each value is an horizontal line x = 0; h--; - for (n = 0; n < point_count; n++) { + for (n = 0; n < length_; n++) { y = *(data_start++) ? h : 0; if (n) { @@ -1532,9 +1540,9 @@ void Waveform::paint(Painter& painter) { } else { // Analog waveform: each value is a point's Y coordinate x = prev_x + x_inc; - h = h / 2; + h /= 2; prev_y = y_offset + h - (*(data_start++) * y_scale); - for (n = 1; n < point_count; n++) { + for (n = 1; n < length_; n++) { y = y_offset + h - (*(data_start++) * y_scale); display.draw_line( {prev_x, prev_y}, {(Coord)x, y}, color_); @@ -1543,6 +1551,17 @@ void Waveform::paint(Painter& painter) { x += x_inc; } } + + // Cursors + if (show_cursors) { + for (n = 0; n < 2; n++) { + painter.draw_vline( + Point(std::min(screen_rect().size().width(), (int)cursors[n]), y_offset), + screen_rect().size().height(), + cursor_colors[n] + ); + } + } } diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 995db50e..7874d448 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -598,7 +598,7 @@ private: class Waveform : public Widget { public: - Waveform(Rect parent_rect, int8_t * data, uint32_t length, uint32_t offset, bool digital, Color color); + Waveform(Rect parent_rect, int16_t * data, uint32_t length, uint32_t offset, bool digital, Color color); Waveform(const Waveform&) = delete; Waveform(Waveform&&) = delete; @@ -607,15 +607,20 @@ public: void set_offset(const uint32_t new_offset); void set_length(const uint32_t new_length); + void set_cursor(const uint32_t i, const int16_t position); void paint(Painter& painter) override; private: - int8_t * data_; + const Color cursor_colors[2] = { Color::cyan(), Color::magenta() }; + + int16_t * data_; uint32_t length_; uint32_t offset_; bool digital_ { false }; Color color_; + int16_t cursors[2] { }; + bool show_cursors { false }; }; class VuMeter : public Widget {