diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index fd41cc63f..0ca186ad3 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -281,6 +281,9 @@ set(CPPSRC apps/ui_level.cpp apps/tpms_app.cpp apps/tpms_app.cpp + apps/ui_spectrum_painter.cpp + apps/ui_spectrum_painter_image.cpp + apps/ui_spectrum_painter_text.cpp protocols/aprs.cpp protocols/ax25.cpp protocols/bht.cpp diff --git a/firmware/application/apps/capture_app.hpp b/firmware/application/apps/capture_app.hpp index ab6a1c9ea..9acefaafc 100644 --- a/firmware/application/apps/capture_app.hpp +++ b/firmware/application/apps/capture_app.hpp @@ -29,6 +29,23 @@ #include "ui_record_view.hpp" #include "ui_spectrum.hpp" +#define BW_OPTIONS \ + { " 8k5", 8500 }, \ + { " 11k", 11000 }, \ + { " 16k", 16000 }, \ + { " 25k", 25000 }, \ + { " 50k", 50000 }, \ + { " 100k", 100000 }, \ + { " 250k", 250000 }, \ + { " 500k", 500000 }, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/ \ + { " 600k", 600000 }, /* That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) */ \ + { " 750k", 750000 }, /* From that BW onwards, the LCD is ok, but the recorded file is auto decimated,(not real file size) */ \ + { "1100k", 1100000 }, \ + { "1750k", 1750000 }, \ + { "2000k", 2000000 }, \ + { "2500k", 2500000 }, \ + { "2750k", 2750000 } // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling ADC) + namespace ui { class CaptureAppView : public View { @@ -88,21 +105,7 @@ private: { 5 * 8, 1 * 16 }, 5, { - { " 8k5", 8500 }, - { " 11k ", 11000 }, - { " 16k ", 16000 }, - { " 25k ", 25000 }, - { " 50k ", 50000 }, - { "100k ", 100000 }, - { "250k ", 250000 }, - { "500k ", 500000 }, // Previous Limit bandwith Option with perfect micro SD write .C16 format operaton. - { "600k ", 600000 }, // That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) - { "750k ", 750000 }, // From that BW onwards, the LCD is ok, but the recorded file is auto decimated,(not real file size) - { "1100k", 1100000 }, - { "1750k", 1750000 }, - { "2000k", 2000000 }, - { "2500k", 2500000 }, - { "2750k", 2750000 } // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling ADC) + BW_OPTIONS } }; diff --git a/firmware/application/apps/ui_fileman.hpp b/firmware/application/apps/ui_fileman.hpp index 0ff09fa0a..c3312a7c4 100644 --- a/firmware/application/apps/ui_fileman.hpp +++ b/firmware/application/apps/ui_fileman.hpp @@ -59,6 +59,7 @@ public: void focus() override; std::string title() const override { return "Fileman"; }; + void push_dir(const std::filesystem::path& path); protected: static constexpr size_t max_filename_length = 64; @@ -83,7 +84,6 @@ protected: std::filesystem::path get_selected_full_path() const; const fileman_entry& get_selected_entry() const; - void push_dir(const std::filesystem::path& path); void pop_dir(); void refresh_list(); void reload_current(); diff --git a/firmware/application/apps/ui_spectrum_painter.cpp b/firmware/application/apps/ui_spectrum_painter.cpp new file mode 100644 index 000000000..0a1749dab --- /dev/null +++ b/firmware/application/apps/ui_spectrum_painter.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_spectrum_painter.hpp" +#include "cpld_update.hpp" +#include "bmp.hpp" +#include "baseband_api.hpp" + +#include "ui_fileman.hpp" +#include "io_file.hpp" +#include "file.hpp" +#include "portapack_persistent_memory.hpp" + +namespace ui { + +SpectrumPainterView::SpectrumPainterView( + NavigationView& nav +) : nav_ (nav) { + baseband::run_image(portapack::spi_flash::image_tag_spectrum_painter); + + add_children({ + &labels, + &tab_view, + &input_image, + &input_text, + &progressbar, + &field_frequency, + &field_rfgain, + &field_rfamp, + &check_loop, + &button_play, + &option_bandwidth, + &field_duration, + &field_pause, + }); + + Rect view_rect = { 0, 3 * 8, 240, 80 }; + input_image.set_parent_rect(view_rect); + input_text.set_parent_rect(view_rect); + + field_frequency.set_value(target_frequency()); + field_frequency.set_step(5000); + field_frequency.on_change = [this](rf::Frequency f) { + this->on_target_frequency_changed(f); + }; + field_frequency.on_edit = [this, &nav]() { + auto new_view = nav.push(this->target_frequency()); + new_view->on_changed = [this](rf::Frequency f) { + this->on_target_frequency_changed(f); + this->field_frequency.set_value(f); + }; + }; + + tx_gain = 10; + field_rfgain.set_value(tx_gain); // Initial default value (-12 dB's max ). + field_rfgain.on_change = [this](int32_t v) { // allow initial value change just after opened file. + tx_gain = v; + portapack::transmitter_model.set_tx_gain(tx_gain); + }; + + field_rfamp.set_value(rf_amp ? 14 : 0); // Initial default value True. (TX RF amp on , +14dB's) + field_rfamp.on_change = [this](int32_t v) { // allow initial value change just after opened file. + rf_amp = (bool)v; + portapack::transmitter_model.set_rf_amp(rf_amp); + }; + + input_image.on_input_avaliable = [this]() { + image_input_avaliable = true; + }; + + button_play.on_select = [this](ImageButton&) { + if (tx_active == false) { + tx_mode = tab_view.selected(); + + if (tx_mode == 0 && image_input_avaliable == false) + return; + + //Enable Bias Tee if selected + radio::set_antenna_bias(portapack::get_antenna_bias()); + + radio::enable({ + portapack::receiver_model.tuning_frequency(), + 3072000U, + 1750000, + rf::Direction::Transmit, + rf_amp, + static_cast(portapack::receiver_model.lna()), + static_cast(portapack::receiver_model.vga()) + }); + + if (portapack::persistent_memory::stealth_mode()){ + DisplaySleepMessage message; + EventDispatcher::send_message(message); + } + + button_play.set_bitmap(&bitmap_stop); + + if (tx_mode == 0) { + tx_current_max_lines = input_image.get_height(); + tx_current_width = input_image.get_width(); + } + else { + tx_current_max_lines = input_text.get_height(); + tx_current_width = input_text.get_width(); + } + + progressbar.set_max(tx_current_max_lines); + progressbar.set_value(0); + + baseband::set_spectrum_painter_config(tx_current_width, tx_current_max_lines, false, option_bandwidth.selected_index_value()); + } + else { + stop_tx(); + } + }; + + option_bandwidth.on_change = [this](size_t, ui::OptionsField::value_t value) { + baseband::set_spectrum_painter_config(tx_current_width, tx_current_max_lines, true, value); + }; + + field_duration.set_value(10); + field_pause.set_value(5); +} + +void SpectrumPainterView::start_tx() { + tx_current_line = 0; + tx_active = true; + tx_timestamp_start = chTimeNow(); +} + +void SpectrumPainterView::stop_tx() { + button_play.set_bitmap(&bitmap_play); + portapack::transmitter_model.disable(); + tx_active = false; + tx_current_line = 0; +} + +void SpectrumPainterView::frame_sync() { + if (tx_active) { + if (fifo->is_empty()) { + + int32_t sequence_duration = (field_duration.value() + ( check_loop.value() ? field_pause.value() : 0)) * 1000; + int32_t sequence_time = tx_time_elapsed() % sequence_duration; + bool is_pausing = sequence_time > field_duration.value() * 1000; + + if (is_pausing) { + fifo->in(std::vector(tx_current_width)); + } else { + auto current_time_line = sequence_time * tx_current_max_lines / (field_duration.value() * 1000); + + if (tx_current_line > current_time_line && !check_loop.value()) { + fifo->in(std::vector(tx_current_width)); + stop_tx(); + return; + } + + tx_current_line = current_time_line; + progressbar.set_value(current_time_line); + + if (tx_mode == 0) { + std::vector line = input_image.get_line(current_time_line); + fifo->in(line); + } + else { + std::vector line = input_text.get_line(current_time_line); + fifo->in(line); + } + } + } + } +} + +SpectrumPainterView::~SpectrumPainterView() { + portapack::transmitter_model.disable(); + hackrf::cpld::load_sram_no_verify(); + baseband::shutdown(); +} + +void SpectrumPainterView::focus() { + tab_view.focus(); +} + +void SpectrumPainterView::on_target_frequency_changed(rf::Frequency f) { + set_target_frequency(f); +} + +void SpectrumPainterView::set_target_frequency(const rf::Frequency new_value) { + portapack::persistent_memory::set_tuned_frequency(new_value); +} + +rf::Frequency SpectrumPainterView::target_frequency() const { + return portapack::persistent_memory::tuned_frequency(); +} + +void SpectrumPainterView::paint(Painter& painter) { + View::paint(painter); + + size_t c; + Point pos = { 0, screen_pos().y() + 8 + footer_location }; + + for (c = 0; c < 20; c++) { + painter.draw_bitmap( + pos, + bitmap_stripes, + ui::Color(191, 191, 0), + ui::Color::black() + ); + if (c != 9) + pos += { 24, 0 }; + else + pos = { 0, screen_pos().y() + 8 + footer_location + 32 + 8 }; + } +} + +} diff --git a/firmware/application/apps/ui_spectrum_painter.hpp b/firmware/application/apps/ui_spectrum_painter.hpp new file mode 100644 index 000000000..0ecf98653 --- /dev/null +++ b/firmware/application/apps/ui_spectrum_painter.hpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "ui.hpp" +#include "ui_widget.hpp" + +#include "ui_navigation.hpp" +#include "ui_tabview.hpp" +#include "capture_app.hpp" +#include "baseband_api.hpp" + +#include "portapack.hpp" +#include "message.hpp" + +#include "ui_spectrum_painter_image.hpp" +#include "ui_spectrum_painter_text.hpp" + +namespace ui { + +class SpectrumPainterView : public View { +public: + SpectrumPainterView(NavigationView& nav); + ~SpectrumPainterView(); + + SpectrumPainterView(const SpectrumPainterView&) = delete; + SpectrumPainterView(SpectrumPainterView&&) = delete; + SpectrumPainterView& operator=(const SpectrumPainterView&) = delete; + SpectrumPainterView& operator=(SpectrumPainterView&&) = delete; + + void focus() override; + void paint(Painter& painter) override; + + std::string title() const override { return "Spec.Painter"; }; + +private: + void on_target_frequency_changed(rf::Frequency f); + void set_target_frequency(const rf::Frequency new_value); + rf::Frequency target_frequency() const; + + NavigationView& nav_; + bool image_input_avaliable { false }; + bool tx_active { false }; + uint32_t tx_mode { 0 }; + uint16_t tx_current_line { 0 }; + uint16_t tx_current_max_lines { 0 }; + uint16_t tx_current_width { 0 }; + systime_t tx_timestamp_start { 0 }; + + inline uint32_t tx_time_elapsed() { + auto now = chTimeNow(); + return now - tx_timestamp_start; + } + + int32_t tx_gain { 47 }; + bool rf_amp { false }; + + SpectrumInputImageView input_image { nav_ }; + SpectrumInputTextView input_text { nav_ }; + + std::array input_views { { &input_image, &input_text } }; + + TabView tab_view { + { "Image", Color::white(), input_views[0] }, + { "Text", Color::white(), input_views[1] } + }; + + static constexpr int32_t footer_location = 15 * 16 + 8; + ProgressBar progressbar { + { 4, footer_location - 16, 240-8, 16 } + }; + + Labels labels { + { { 10 * 8, footer_location + 1 * 16 }, "GAIN A:", Color::light_grey() }, + { { 1 * 8, footer_location + 2 * 16 }, "BW: Du: P:", Color::light_grey() }, + }; + + FrequencyField field_frequency { + { 0 * 8, footer_location + 1 * 16 }, + }; + + NumberField field_rfgain { + { 14 * 8, footer_location + 1 * 16 }, + 2, + { 0, 47 }, + 1, + ' ' + }; + + NumberField field_rfamp { + { 19 * 8, footer_location + 1 * 16 }, + 2, + { 0, 14 }, + 14, + ' ' + }; + + Checkbox check_loop { + { 21 * 8, footer_location + 1 * 16 }, + 4, + "Loop", + true + }; + + ImageButton button_play { + { 28 * 8, footer_location + 1 * 16, 2 * 8, 1 * 16 }, + &bitmap_play, + Color::green(), + Color::black() + }; + + OptionsField option_bandwidth { + { 4 * 8, footer_location + 2 * 16 }, + 5, + { + BW_OPTIONS + } + }; + + NumberField field_duration { + { 13 * 8, footer_location + 2 * 16 }, + 3, + { 1, 999 }, + 1, + ' ' + }; + + NumberField field_pause { + { 19 * 8, footer_location + 2 * 16 }, + 2, + { 0, 99 }, + 1, + ' ' + }; + + SpectrumPainterFIFO* fifo { nullptr }; + void start_tx(); + void frame_sync(); + void stop_tx(); + + MessageHandlerRegistration message_handler_fifo_signal { + Message::ID::SpectrumPainterBufferResponseConfigure, + [this](const Message* const p) { + const auto message = static_cast(p); + this->fifo = message->fifo; + this->start_tx(); + } + }; + + MessageHandlerRegistration message_handler_sample { + Message::ID::DisplayFrameSync, + [this](const Message* const) { + this->frame_sync(); + } + }; +}; + +} diff --git a/firmware/application/apps/ui_spectrum_painter_image.cpp b/firmware/application/apps/ui_spectrum_painter_image.cpp new file mode 100644 index 000000000..c514dee96 --- /dev/null +++ b/firmware/application/apps/ui_spectrum_painter_image.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_spectrum_painter_image.hpp" +#include "cpld_update.hpp" +#include "bmp.hpp" +#include "baseband_api.hpp" + +#include "ui_fileman.hpp" +#include "io_file.hpp" +#include "file.hpp" +#include "portapack_persistent_memory.hpp" + +namespace ui { + +SpectrumInputImageView::SpectrumInputImageView(NavigationView& nav) { + hidden(true); + + add_children({ + &button_load_image + }); + + button_load_image.on_select = [this, &nav](Button&) { + auto open_view = nav.push(".bmp"); + + constexpr auto data_directory = u"SPECTRUM"; + if (std::filesystem::is_directory(data_directory) == false) { + if (make_new_directory(data_directory).ok()) + open_view->push_dir(data_directory); + } + else + open_view->push_dir(data_directory); + + open_view->on_changed = [this](std::filesystem::path new_file_path) { + this->file = new_file_path.string(); + painted = false; + this->set_dirty(); + + this->on_input_avaliable(); + }; + }; +} + +SpectrumInputImageView::~SpectrumInputImageView() { +} + +void SpectrumInputImageView::focus() { + button_load_image.focus(); +} + +bool SpectrumInputImageView::drawBMP_scaled(const ui::Rect r, const std::string file) { + File bmpimage; + size_t file_pos = 0; + uint16_t pointer = 0; + int16_t px = 0, py, zoom_factor = 0; + bmp_header_t bmp_header; + char buffer[257]; + ui::Color line_buffer[240]; + + auto result = bmpimage.open(file); + if(result.is_valid()) + return false; + + bmpimage.seek(file_pos); + auto read_size = bmpimage.read(&bmp_header, sizeof(bmp_header)); + if (!((bmp_header.signature == 0x4D42) && // "BM" Signature + (bmp_header.planes == 1) && // Seems always to be 1 + (bmp_header.compression == 0 || bmp_header.compression == 3 ))) { // No compression + return false; + } + + switch(bmp_header.bpp) { + case 16: + file_pos = 0x36; + memset(buffer, 0, 16); + bmpimage.read(buffer, 16); + if(buffer[1] == 0x7C) + type = 3; // A1R5G5B5 + else + type = 0; // R5G6B5 + break; + case 24: + type = 1; + break; + case 32: + default: + type = 2; + break; + } + + width = bmp_header.width; + height = bmp_header.height; + + data_start = file_pos = bmp_header.image_data; + + while (r.width() < (width >> zoom_factor) || r.height() < (height >> zoom_factor)) { + zoom_factor++; + } + + py = height + 16; + + while(1) { + while(px < width) { + bmpimage.seek(file_pos); + memset(buffer, 0, 257); + read_size = bmpimage.read(buffer, 256); + if (read_size.is_error()) + return false; // Read error + + pointer = 0; + while(pointer < 256) { + if(pointer + 4 > 256) + break; + switch(type) { + case 0: // R5G6B5 + if ((((1 << zoom_factor) - 1) & px) == 0x00) + line_buffer[px >> zoom_factor] = ui::Color(((uint16_t)buffer[pointer] & 0x1F) | ((uint16_t)buffer[pointer] & 0xE0) << 1 | ((uint16_t)buffer[pointer + 1] & 0x7F) << 9); + + pointer += 2; + file_pos += 2; + break; + + case 3: // A1R5G5B5 + if ((((1 << zoom_factor) - 1) & px) == 0x00) + line_buffer[px >> zoom_factor] = ui::Color((uint16_t)buffer[pointer] | ((uint16_t)buffer[pointer + 1] << 8)); + + pointer += 2; + file_pos += 2; + break; + + case 1: // 24 + default: + if ((((1 << zoom_factor) - 1) & px) == 0x00) + line_buffer[px >> zoom_factor] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]); + pointer += 3; + file_pos += 3; + break; + + case 2: // 32 + if ((((1 << zoom_factor) - 1) & px) == 0x00) + line_buffer[px >> zoom_factor] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]); + pointer += 4; + file_pos += 4; + break; + } + + px++; + if(px >= width) { + break; + } + } + + if(read_size.value() != 256) + break; + } + + if ((((1 << zoom_factor) - 1) & py) == 0x00) + portapack::display.render_line({ r.left(), r.top() + (py >> zoom_factor) }, px >> zoom_factor, line_buffer); + + px = 0; + py--; + + if(read_size.value() < 256 || py < 0) + break; + } + + return true; +} + +uint16_t SpectrumInputImageView::get_width(){ + return this->width; +} + +uint16_t SpectrumInputImageView::get_height(){ + return this->height; +} + +std::vector SpectrumInputImageView::get_line(uint16_t y) { + File bmpimage; + bmpimage.open(this->file); + + //seek to line + uint32_t line_size = width * (type == 2 ? 4 : (type == 1 ? 3 : 2)); + uint32_t line_offset = y * line_size; + bmpimage.seek(data_start + line_offset); + + // allocate memory and read + auto buffer = new uint8_t[line_size]; + auto bytes_read = bmpimage.read(buffer, line_size); + + // greyscale + auto grey_buffer = new uint8_t[width]; + int pointer = 0; + for (uint16_t px = 0; px < width; px++) { + ui::Color color; + switch(type) { + case 0: // R5G6B5 + pointer = px * 2; + color = ui::Color(((uint16_t)buffer[pointer] & 0x1F) | ((uint16_t)buffer[pointer] & 0xE0) << 1 | ((uint16_t)buffer[pointer + 1] & 0x7F) << 9); + break; + + case 3: // A1R5G5B5 + pointer = px * 2; + color = ui::Color((uint16_t)buffer[pointer] | ((uint16_t)buffer[pointer + 1] << 8)); + break; + + case 1: // 24 + default: + pointer = px * 3; + color = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]); + break; + + case 2: // 32 + pointer = px * 4; + color = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]); + break; + } + + grey_buffer[px] = color.to_greyscale(); + } + + delete buffer; + + std::vector values(width); + for(int i = 0; i < width; i++) { + values[i] = grey_buffer[i]; + } + + delete grey_buffer; + + return values; +} + +void SpectrumInputImageView::paint(Painter& painter) { + painter.fill_rectangle( + {{0, 40}, {240, 204}}, + style().background + ); + + if (!painted) { + // This is very slow for big pictures. Do only once. + this->drawBMP_scaled({{ 0, 40 }, {240, 160}}, this->file); + painted = true; + } +} + +} diff --git a/firmware/application/apps/ui_spectrum_painter_image.hpp b/firmware/application/apps/ui_spectrum_painter_image.hpp new file mode 100644 index 000000000..51f1f2764 --- /dev/null +++ b/firmware/application/apps/ui_spectrum_painter_image.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "ui.hpp" +#include "ui_widget.hpp" + +#include "ui_navigation.hpp" +#include "ui_tabview.hpp" +#include "ui_transmitter.hpp" +#include "baseband_api.hpp" + +#include "portapack.hpp" +#include "message.hpp" + +namespace ui { + +class SpectrumInputImageView : public View { +public: + SpectrumInputImageView(NavigationView& nav); + ~SpectrumInputImageView(); + + void focus() override; + void paint(Painter&) override; + + uint16_t get_width(); + uint16_t get_height(); + std::vector get_line(uint16_t); + + std::function on_input_avaliable { }; + +private: + bool painted {false}; + std::string file {""}; + uint16_t width {0}; + uint16_t height {0}; + uint8_t type {0}; + uint32_t data_start {0}; + + Button button_load_image { + { 0 * 8, 11 * 16 - 4, 30 * 8, 28 }, + "Load Image ..." + }; + + bool drawBMP_scaled(const ui::Rect r, const std::string file); +}; + +} diff --git a/firmware/application/apps/ui_spectrum_painter_text.cpp b/firmware/application/apps/ui_spectrum_painter_text.cpp new file mode 100644 index 000000000..f4a93cde8 --- /dev/null +++ b/firmware/application/apps/ui_spectrum_painter_text.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_spectrum_painter_text.hpp" +#include "cpld_update.hpp" +#include "bmp.hpp" +#include "baseband_api.hpp" + +#include "ui_fileman.hpp" +#include "io_file.hpp" +#include "file.hpp" +#include "portapack_persistent_memory.hpp" +#include "ui_font_fixed_8x16.hpp" + +namespace ui { + +SpectrumInputTextView::SpectrumInputTextView(NavigationView& nav) { + hidden(true); + + add_children({ + &text_message_0, + &text_message_1, + &text_message_2, + &text_message_3, + &text_message_4, + &text_message_5, + &text_message_6, + &text_message_7, + &text_message_8, + &text_message_9, + &button_message + }); + + button_message.on_select = [this, &nav](Button&) { + this->on_set_text(nav); + }; +} + +SpectrumInputTextView::~SpectrumInputTextView() { +} + +void SpectrumInputTextView::on_set_text(NavigationView& nav) { + text_prompt(nav, buffer, 300); +} + +void SpectrumInputTextView::focus() { + button_message.focus(); +} + +void SpectrumInputTextView::paint(Painter& painter) { + message = buffer; + for (uint32_t i = 0 ; i < text_message.size(); i++) { + if (message.length() > i * 30) + text_message[i]->set(message.substr(i * 30, 30)); + else + text_message[i]->set(""); + } + + painter.fill_rectangle( + {{0, 40}, {240, 204}}, + style().background + ); +} + +constexpr uint32_t pixel_repeat = 32; +uint16_t SpectrumInputTextView::get_width(){ + return 16 * pixel_repeat; +} + +uint16_t SpectrumInputTextView::get_height(){ + return this->message.length() * 8; +} + +std::vector SpectrumInputTextView::get_line(uint16_t y) { + auto character_position = y / 8; + auto character = this->message[character_position]; + auto glyph_data = ui::font::fixed_8x16.glyph(character).pixels(); + + auto line_in_character = y % 8; + std::vector data(16 * pixel_repeat); + + for (uint32_t index = 0; index < 16; index++) { + auto glyph_byte = index; + auto glyph_bit = line_in_character; + uint8_t glyph_pixel = (glyph_data[glyph_byte] & (1 << glyph_bit)) >> glyph_bit; + + for (uint32_t j = 0; j < pixel_repeat; j++) + data[index*pixel_repeat + j] = glyph_pixel * 255; + } + + return data; +} +} diff --git a/firmware/application/apps/ui_spectrum_painter_text.hpp b/firmware/application/apps/ui_spectrum_painter_text.hpp new file mode 100644 index 000000000..41b236e56 --- /dev/null +++ b/firmware/application/apps/ui_spectrum_painter_text.hpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "ui.hpp" +#include "ui_widget.hpp" + +#include "ui_navigation.hpp" +#include "ui_tabview.hpp" +#include "ui_transmitter.hpp" +#include "baseband_api.hpp" + +#include "portapack.hpp" +#include "message.hpp" + +namespace ui { + +class SpectrumInputTextView : public View { +public: + SpectrumInputTextView(NavigationView& nav); + ~SpectrumInputTextView(); + + void focus() override; + void paint(Painter&) override; + + uint16_t get_width(); + uint16_t get_height(); + std::vector get_line(uint16_t); + +private: + std::string buffer { "PORTAPACK" }; + std::string message { }; + void on_set_text(NavigationView& nav); + + Text text_message_0 { + { 0 * 8, 0 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_1 { + { 0 * 8, 1 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_2 { + { 0 * 8, 2 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_3 { + { 0 * 8, 3 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_4 { + { 0 * 8, 4 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_5 { + { 0 * 8, 5 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_6 { + { 0 * 8, 6 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_7 { + { 0 * 8, 7 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_8 { + { 0 * 8, 8 * 16, 30 * 8, 16 }, + "" + }; + + Text text_message_9 { + { 0 * 8, 9 * 16, 30 * 8, 16 }, + "" + }; + + std::array text_message { { + &text_message_0, + &text_message_1, + &text_message_2, + &text_message_3, + &text_message_4, + &text_message_5, + &text_message_6, + &text_message_7, + &text_message_8, + &text_message_9, + } }; + + Button button_message { + { 0 * 8, 11 * 16 - 4, 30 * 8, 28 }, + "Set message" + }; + +}; + +} diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index ecf161204..37cfb5485 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -299,6 +299,11 @@ void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t d send_message(&message); } +void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw){ + SpectrumPainterBufferConfigureRequestMessage message { width, height, update, bw }; + send_message(&message); +} + static bool baseband_image_running = false; void run_image(const portapack::spi_flash::image_tag_t image_tag) { diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index e1c93545c..71887af0a 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -91,6 +91,7 @@ void set_rds_data(const uint16_t message_length); void set_spectrum(const size_t sampling_rate, const size_t trigger); void set_siggen_tone(const uint32_t tone); void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration); +void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw); void request_beep(); void run_image(const portapack::spi_flash::image_tag_t image_tag); diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 7df55e688..7103feb71 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -73,6 +73,7 @@ #include "ui_whipcalc.hpp" #include "ui_flash_utility.hpp" #include "ui_sd_over_usb.hpp" +#include "ui_spectrum_painter.hpp" //#include "acars_app.hpp" #include "ais_app.hpp" @@ -596,6 +597,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) { { "TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav](){ nav.push(); } }, { "TouchTune", ui::Color::yellow(), &bitmap_icon_remote, [&nav](){ nav.push(); } }, { "Playlist", ui::Color::yellow(), &bitmap_icon_remote, [&nav](){ nav.push(); } }, + { "S.Painter", ui::Color::orange(), &bitmap_icon_morse, [&nav](){ nav.push(); } }, //{ "Remote", ui::Color::dark_grey(), &bitmap_icon_remote, [&nav](){ nav.push(); } }, }); } diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index b19059d47..c6b44ef2a 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -140,6 +140,7 @@ set(CPPSRC debug.cpp ${COMMON}/gcc.cpp ${COMMON}/performance_counter.cpp + ${COMMON}/random.cpp tone_gen.cpp ) @@ -463,6 +464,13 @@ set(MODE_CPPSRC ) DeclareTargets(PSIG siggen) +### Spectrum painter + +set(MODE_CPPSRC + proc_spectrum_painter.cpp +) +DeclareTargets(PSPT spectrum_painter) + ### SSTV TX set(MODE_CPPSRC diff --git a/firmware/baseband/proc_spectrum_painter.cpp b/firmware/baseband/proc_spectrum_painter.cpp new file mode 100644 index 000000000..87512d477 --- /dev/null +++ b/firmware/baseband/proc_spectrum_painter.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "proc_spectrum_painter.hpp" +#include "event_m4.hpp" +#include "dsp_fft.hpp" +#include "random.hpp" + +#include + +// TODO move to class members SpectrumPainterProcessor +complex16_t *current_line_data = nullptr; +complex16_t * volatile next_line_data = nullptr; +uint32_t current_line_index = 0; +uint32_t current_line_width = 0; +int32_t current_bw = 0; + +std::vector fifo_data[1 << SpectrumPainterBufferConfigureResponseMessage::fifo_k] { }; +SpectrumPainterFIFO fifo { fifo_data, SpectrumPainterBufferConfigureResponseMessage::fifo_k }; + +int max_val = 127; + +// This is called at 3072000/2048 = 1500Hz +void SpectrumPainterProcessor::execute(const buffer_c8_t& buffer) { + + for (uint32_t i = 0; i < buffer.count; i++) { + if (current_line_data != nullptr) { + auto data = current_line_data[(current_line_index++ * current_bw / 3072 ) % current_line_width]; + buffer.p[i] = {(int8_t) data.real(), (int8_t) data.imag()}; + } + else + buffer.p[i] = {0, 0}; + } + + // collect new data + if (next_line_data != nullptr) { + if (current_line_data != nullptr) + delete current_line_data; + + current_line_data = next_line_data; + next_line_data = nullptr; + } +} + +WORKING_AREA(thread_wa, 4096); + +void SpectrumPainterProcessor::run() { + int ui = 0; + init_genrand(22267); + + while (true) { + if (fifo.is_empty() == false && next_line_data == nullptr) { + std::vector data; + fifo.out(data); + + auto picture_width = data.size(); + + auto fft_width = picture_width * 2; + auto qu = fft_width/4; + + complex16_t *v = new complex16_t[fft_width]; + complex16_t *tmp = new complex16_t[fft_width]; + + for (uint32_t fft_index = 0; fft_index < fft_width; fft_index++) { + if (fft_index < qu) { + } + else if (fft_index < qu*3) { + //TODO: Improve index handling + auto image_index = fft_index-qu; + + auto bin_power = data[image_index]; // 0 to 255 + auto bin_phase = genrand_int31(); // 0 to 255 + + // rotate by random angle + auto phase_cos = (sine_table_i8[((int)(bin_phase + 0x40)) & 0xFF]); // -127 to 127 + auto phase_sin = (sine_table_i8[((int)(bin_phase)) & 0xFF]); + + auto real = (int16_t)((int16_t)phase_cos * bin_power / 255); // -127 to 127 + auto imag = (int16_t)((int16_t)phase_sin * bin_power / 255); // -127 to 127 + + auto fftshift_index = 0; + if (fft_index < qu*2) // first half (fft_index = qu; fft_index < qu*2) + fftshift_index = fft_index + 2*qu; // goes to back + else // 2nd half (fft_index = qu*2; fft_index < qu*3) + fftshift_index = fft_index - 2*qu; // goes to front + + v[fftshift_index] = {real, imag}; + } + } + + ifft(v, fft_width, tmp); + + // normalize + volatile int32_t maximum = 1; + for (uint32_t i = 0; i < fft_width; i++) { + if (v[i].real() > maximum) + maximum = v[i].real(); + if (v[i].real() < -maximum) + maximum = -v[i].real(); + if (v[i].imag() > maximum) + maximum = v[i].imag(); + if (v[i].imag() < -maximum) + maximum = -v[i].imag(); + } + + if (maximum == 1) { // a black line + for (uint32_t i = 0; i < fft_width; i++) + v[i] = {0, 0}; + } + else { + for (uint32_t i = 0; i < fft_width; i++) { + v[i] = { (int8_t)((int32_t)v[i].real() * 120 / maximum), (int8_t)((int32_t)v[i].imag() * 120 / maximum)}; + } + } + + delete tmp; + next_line_data = v; + ui++; + } + else { + chThdSleepMilliseconds(1); + } + } +} + +void SpectrumPainterProcessor::on_message(const Message* const msg) { + + switch(msg->id) { + case Message::ID::SpectrumPainterBufferRequestConfigure: + { + const auto message = *reinterpret_cast(msg); + current_line_width = message.width; + current_bw = message.bw / 500; + + if (message.update == false) { + SpectrumPainterBufferConfigureResponseMessage response { &fifo }; + shared_memory.application_queue.push(response); + + if (configured == false) { + thread = chThdCreateStatic(thread_wa, sizeof(thread_wa), + NORMALPRIO, SpectrumPainterProcessor::fn, + this + ); + + configured = true; + } + } + break; + } + + default: + break; + } +} + +int main() { + EventDispatcher event_dispatcher { std::make_unique() }; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_spectrum_painter.hpp b/firmware/baseband/proc_spectrum_painter.hpp new file mode 100644 index 000000000..92ee29fee --- /dev/null +++ b/firmware/baseband/proc_spectrum_painter.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "portapack_shared_memory.hpp" +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" + +class SpectrumPainterProcessor : public BasebandProcessor { +public: + void execute(const buffer_c8_t& buffer) override; + void on_message(const Message* const p) override; + void run(); + +private: + bool configured { false }; + BasebandThread baseband_thread { 3072000, this, NORMALPRIO + 20, baseband::Direction::Transmit }; + Thread* thread {nullptr}; + +protected: + static msg_t fn(void* arg) { + auto obj = static_cast(arg); + obj->run(); + + return 0; + } +}; diff --git a/firmware/common/dsp_fft.cpp b/firmware/common/dsp_fft.cpp index 26f558e07..8d2f61541 100644 --- a/firmware/common/dsp_fft.cpp +++ b/firmware/common/dsp_fft.cpp @@ -20,3 +20,4 @@ */ #include "dsp_fft.hpp" +#include "complex.hpp" diff --git a/firmware/common/dsp_fft.hpp b/firmware/common/dsp_fft.hpp index c3884689b..f830c12ba 100644 --- a/firmware/common/dsp_fft.hpp +++ b/firmware/common/dsp_fft.hpp @@ -33,6 +33,7 @@ #include "complex.hpp" #include "hal.h" #include "utility.hpp" +#include "sine_table_int8.hpp" namespace std { /* https://github.com/AE9RB/fftbench/blob/master/cxlr.hpp @@ -135,4 +136,45 @@ void fft_c_preswapped(std::array& data, const size_t from, const size_t to } } +/* + ifft(v,N): + [0] If N==1 then return. + [1] For k = 0 to N/2-1, let ve[k] = v[2*k] + [2] Compute ifft(ve, N/2); + [3] For k = 0 to N/2-1, let vo[k] = v[2*k+1] + [4] Compute ifft(vo, N/2); + [5] For m = 0 to N/2-1, do [6] through [9] + [6] Let w.real() = cos(2*PI*m/N) + [7] Let w.imag() = sin(2*PI*m/N) + [8] Let v[m] = ve[m] + w*vo[m] + [9] Let v[m+N/2] = ve[m] - w*vo[m] + */ +template +void ifft( T *v, int n, T *tmp ) +{ + if(n>1) { + int k,m; + T z, w, *vo, *ve; + ve = tmp; vo = tmp+n/2; + for(k=0; k 256) break; switch(type) { - case 0: - case 3: + case 0: // R5G6B5 + case 3: // A1R5G5B5 if(!type) line_buffer[px] = ui::Color((uint16_t)buffer[pointer] | ((uint16_t)buffer[pointer + 1] << 8)); else @@ -498,13 +498,13 @@ bool ILI9341::drawBMP2(const ui::Point p, const std::string file) { pointer += 2; file_pos += 2; break; - case 1: + case 1: // 24 default: line_buffer[px] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]); pointer += 3; file_pos += 3; break; - case 2: + case 2: // 32 line_buffer[px] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]); pointer += 4; file_pos += 4; diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index a7a0784b7..4b71f5545 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -109,6 +109,8 @@ public: AudioSpectrum = 52, APRSPacket = 53, APRSRxConfigure = 54, + SpectrumPainterBufferRequestConfigure = 55, + SpectrumPainterBufferResponseConfigure = 56, MAX }; @@ -1143,4 +1145,40 @@ public: uint32_t return_code; }; +class SpectrumPainterBufferConfigureRequestMessage : public Message { +public: + constexpr SpectrumPainterBufferConfigureRequestMessage( + uint16_t width, + uint16_t height, + bool update, + int32_t bw + ) : Message { ID::SpectrumPainterBufferRequestConfigure }, + width { width }, + height { height }, + update { update }, + bw { bw } + { + } + + uint16_t width; + uint16_t height; + bool update; + int32_t bw; +}; + +using SpectrumPainterFIFO = FIFO>; +class SpectrumPainterBufferConfigureResponseMessage : public Message { +public: + static constexpr size_t fifo_k = 2; + + constexpr SpectrumPainterBufferConfigureResponseMessage( + SpectrumPainterFIFO* fifo + ) : Message { ID::SpectrumPainterBufferResponseConfigure }, + fifo { fifo } + { + } + + SpectrumPainterFIFO* fifo { nullptr }; +}; + #endif/*__MESSAGE_H__*/ diff --git a/firmware/common/portapack_shared_memory.hpp b/firmware/common/portapack_shared_memory.hpp index 667552a97..df5be8a19 100644 --- a/firmware/common/portapack_shared_memory.hpp +++ b/firmware/common/portapack_shared_memory.hpp @@ -68,7 +68,7 @@ struct SharedMemory { uint8_t volatile request_m4_performance_counter{ 0 }; uint8_t volatile m4_cpu_usage{ 0 }; uint16_t volatile m4_stack_usage{ 0 }; - uint16_t volatile m4_heap_usage{ 0 }; + uint32_t volatile m4_heap_usage{ 0 }; uint16_t volatile m4_buffer_missed{ 0 }; }; diff --git a/firmware/common/random.cpp b/firmware/common/random.cpp new file mode 100644 index 000000000..b7de06c41 --- /dev/null +++ b/firmware/common/random.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "random.hpp" + +static unsigned long state[N]; /* the array for the state vector */ +static int rnd_left = 1; +static int rnd_init = 0; +static unsigned long *rnd_next; + +/* initializes state[N] with a seed */ +void init_genrand(unsigned long s) +{ + int j; + state[0] = s & 0xffffffffUL; + + for (j = 1; j < N; j++) + { + state[j] = (1812433253UL * (state[j - 1] ^ (state[j - 1] >> 30)) + j); + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array state[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + state[j] &= 0xffffffffUL; /* for >32 bit machines */ + } + + rnd_left = 1; + rnd_init = 1; +} + +static void next_state(void) +{ + unsigned long *p = state; + int j; + + /* if init_genrand() has not been called, */ + /* a default initial seed is used */ + if (rnd_init == 0) + init_genrand(5489UL); + + rnd_left = N; + rnd_next = state; + + for (j = N - M + 1; --j; p++) + *p = p[M] ^ TWIST(p[0], p[1]); + + for (j = M; --j; p++) + *p = p[M - N] ^ TWIST(p[0], p[1]); + + *p = p[M - N] ^ TWIST(p[0], state[0]); +} + +long genrand_int31(void) +{ + unsigned long y; + + if (--rnd_left == 0) + next_state(); + y = *rnd_next++; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return (long)(y >> 1); +} diff --git a/firmware/common/random.hpp b/firmware/common/random.hpp new file mode 100644 index 000000000..5442fc319 --- /dev/null +++ b/firmware/common/random.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +/* Period parameters */ +#define N 624 +#define M 397 +#define MATRIX_A 0x9908b0dfUL /* constant vector a */ +#define UMASK 0x80000000UL /* most significant w-r bits */ +#define LMASK 0x7fffffffUL /* least significant r bits */ +#define MIXBITS(u, v) (((u)&UMASK) | ((v)&LMASK)) +#define TWIST(u, v) ((MIXBITS(u, v) >> 1) ^ ((v)&1UL ? MATRIX_A : 0UL)) + +/* initializes state[N] with a seed */ +extern void init_genrand(unsigned long s); +extern long genrand_int31(void); diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index 89679284f..486197e20 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -104,6 +104,7 @@ constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' }; constexpr image_tag_t image_tag_replay { 'P', 'R', 'E', 'P' }; constexpr image_tag_t image_tag_gps { 'P', 'G', 'P', 'S' }; constexpr image_tag_t image_tag_siggen { 'P', 'S', 'I', 'G' }; +constexpr image_tag_t image_tag_spectrum_painter { 'P', 'S', 'P', 'T' }; constexpr image_tag_t image_tag_sstv_tx { 'P', 'S', 'T', 'X' }; constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' }; constexpr image_tag_t image_tag_flash_utility { 'P', 'F', 'U', 'T' }; diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index 2c27fd33b..853a73676 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -57,6 +57,16 @@ struct Color { )} { } + + uint8_t to_greyscale() { + uint32_t r = ((v >> 11) & 31U) << 3; + uint32_t g = ((v >> 5) & 63U) << 2; + uint32_t b = (v & 31U) << 3; + + uint32_t grey = (r * 299 + g * 587 + b * 114) / 1000; + + return (uint8_t)grey; + } Color operator-() const { return (v ^ 0xffff);