mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-07-26 10:38:52 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 245a36459f | |||
| 590f19062b | |||
| f38ef4ebc8 | |||
| 900d2f2482 | |||
| 5aff5ac277 | |||
| cf4d245964 | |||
| c7f7173958 |
@@ -322,10 +322,10 @@ class BLERxView : public View {
|
||||
{UI_POS_X(21), UI_POS_Y(0)}};
|
||||
|
||||
RSSI rssi{
|
||||
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(6), 4}};
|
||||
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
|
||||
Channel channel{
|
||||
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(6), 4}};
|
||||
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
|
||||
Labels label_sort{
|
||||
{{UI_POS_X(0), UI_POS_Y(1)}, "Sort:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace ui {
|
||||
static const fs::path txt_ext{u".TXT"};
|
||||
static const fs::path ppl_ext{u".PPL"};
|
||||
static const fs::path c8_ext{u".C8"};
|
||||
static const fs::path cu8_ext{u".CU8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path cxx_ext{u".C*"};
|
||||
static const fs::path png_ext{u".PNG"};
|
||||
@@ -88,8 +89,11 @@ fs::path get_partner_file(fs::path path) {
|
||||
path.replace_extension(txt_ext);
|
||||
else if (path_iequal(ext, txt_ext)) {
|
||||
path.replace_extension(c8_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(cu8_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(c16_ext);
|
||||
|
||||
} else
|
||||
return {};
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ class FileManBaseView : public View {
|
||||
{u".PNG", &bitmap_icon_file_image, ui::Color::green()},
|
||||
{u".BMP", &bitmap_icon_file_image, ui::Color::green()},
|
||||
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".CU8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
|
||||
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // Playlist/Replay
|
||||
|
||||
@@ -172,6 +172,7 @@ void IQTrimView::update_range_controls(iq::TrimRange trim_range) {
|
||||
|
||||
void IQTrimView::profile_capture() {
|
||||
power_buckets_.clear();
|
||||
power_buckets_.resize(screen_width);
|
||||
iq::PowerBuckets buckets{
|
||||
.p = power_buckets_.data(),
|
||||
.size = power_buckets_.size()};
|
||||
|
||||
@@ -268,17 +268,11 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) {
|
||||
|
||||
// Save interrupt states before mass disable
|
||||
uint32_t saved_iser0 = NVIC->ISER[0];
|
||||
uint32_t saved_iser1 = NVIC->ISER[1];
|
||||
uint32_t saved_iser2 = NVIC->ISER[2];
|
||||
|
||||
// Disable and clear all pending interrupts
|
||||
NVIC->ICER[0] = 0xFFFFFFFF;
|
||||
NVIC->ICER[1] = 0xFFFFFFFF;
|
||||
NVIC->ICER[2] = 0xFFFFFFFF;
|
||||
|
||||
NVIC->ICPR[0] = 0xFFFFFFFF;
|
||||
NVIC->ICPR[1] = 0xFFFFFFFF;
|
||||
NVIC->ICPR[2] = 0xFFFFFFFF;
|
||||
|
||||
// Re-enable only necessary wakeup sources
|
||||
NVIC_EnableIRQ(RTC_IRQn);
|
||||
@@ -326,8 +320,6 @@ void EventDispatcher::charge_deep_sleep(const bool sleep) {
|
||||
|
||||
// Restore original interrupt enable states
|
||||
NVIC->ISER[0] = saved_iser0;
|
||||
NVIC->ISER[1] = saved_iser1;
|
||||
NVIC->ISER[2] = saved_iser2;
|
||||
|
||||
} // End of while(1) deep sleep loop
|
||||
} else {
|
||||
|
||||
@@ -193,15 +193,12 @@ void WaterfallDesignerView::on_profile_changed(std::filesystem::path new_profile
|
||||
auto reader = FileLineReader(playlist_file);
|
||||
|
||||
for (const auto& line : reader) {
|
||||
// remove empty lines
|
||||
if (line == "\n" || line == "\r\n" || line == "\r") continue;
|
||||
profile_levels.push_back(line);
|
||||
}
|
||||
|
||||
for (auto& line : profile_levels) {
|
||||
// remove empty lines
|
||||
if (line == "\n" || line == "\r\n" || line == "\r") {
|
||||
profile_levels.erase(std::remove(profile_levels.begin(), profile_levels.end(), line), profile_levels.end());
|
||||
}
|
||||
|
||||
// remove line end \n etc
|
||||
if (line.length() > 0 && (line[line.length() - 1] == '\n' || line[line.length() - 1] == '\r')) {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static const fs::path c8_ext{u".C8"};
|
||||
static const fs::path cu8_ext{u".CU8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
|
||||
Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BYTE mode) {
|
||||
@@ -543,12 +544,14 @@ bool path_iequal(
|
||||
|
||||
bool is_cxx_capture_file(const path& filename) {
|
||||
auto ext = filename.extension();
|
||||
return path_iequal(c8_ext, ext) || path_iequal(c16_ext, ext);
|
||||
return path_iequal(c8_ext, ext) || path_iequal(cu8_ext, ext) || path_iequal(c16_ext, ext);
|
||||
}
|
||||
|
||||
uint8_t capture_file_sample_size(const path& filename) {
|
||||
if (path_iequal(filename.extension(), c8_ext))
|
||||
return sizeof(complex8_t);
|
||||
if (path_iequal(filename.extension(), cu8_ext))
|
||||
return sizeof(complexu8_t);
|
||||
if (path_iequal(filename.extension(), c16_ext))
|
||||
return sizeof(complex16_t);
|
||||
return 0;
|
||||
|
||||
@@ -28,13 +28,11 @@ namespace fs = std::filesystem;
|
||||
const std::filesystem::path default_gradient_file = u"waterfall.txt";
|
||||
|
||||
Gradient::Gradient() {
|
||||
prev_index = 0;
|
||||
prev_r = 0;
|
||||
prev_g = 0;
|
||||
prev_b = 0;
|
||||
step(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void Gradient::set_default() {
|
||||
step(0, 0, 0, 0);
|
||||
step(86, 0, 0, 255);
|
||||
step(171, 0, 255, 0);
|
||||
step(255, 255, 0, 0);
|
||||
@@ -47,6 +45,9 @@ bool Gradient::load_file(const std::filesystem::path& file_path) {
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
// make sure the table starts at 0
|
||||
step(0, 0, 0, 0);
|
||||
|
||||
auto reader = FileLineReader(gradient_file);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
@@ -73,17 +74,24 @@ bool Gradient::load_file(const std::filesystem::path& file_path) {
|
||||
}
|
||||
}
|
||||
|
||||
// extend the gradient from the current index to the end of the table
|
||||
step(255, prev_r, prev_g, prev_b);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Gradient::step(int16_t index, int16_t r, int16_t g, int16_t b) {
|
||||
for (int16_t i = prev_index; i <= index; i++) {
|
||||
float x = (float)(i - prev_index) / (index - prev_index);
|
||||
float y = 1.0f - x;
|
||||
// prevent division by 0 if index == prev_index
|
||||
int16_t range = index - prev_index;
|
||||
if (range == 0) range = 1;
|
||||
|
||||
for (int16_t i = prev_index; i <= index; i++) {
|
||||
int16_t t = i - prev_index;
|
||||
|
||||
int16_t new_r = prev_r + (t * (r - prev_r) + range / 2) / range;
|
||||
int16_t new_g = prev_g + (t * (g - prev_g) + range / 2) / range;
|
||||
int16_t new_b = prev_b + (t * (b - prev_b) + range / 2) / range;
|
||||
|
||||
int16_t new_r = prev_r * y + r * x;
|
||||
int16_t new_g = prev_g * y + g * x;
|
||||
int16_t new_b = prev_b * y + b * x;
|
||||
if (i <= 255) lut[i] = ui::Color(new_r, new_g, new_b);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
static const fs::path c8_ext = u".C8";
|
||||
static const fs::path cu8_ext = u".CU8";
|
||||
|
||||
namespace file_convert {
|
||||
|
||||
@@ -59,21 +60,42 @@ void c8_to_c16(const void* buffer, File::Size bytes) {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert cu8 buffer to c16 buffer.
|
||||
// Same buffer used for input & output; input size is bytes; output size is 2*bytes.
|
||||
void cu8_to_c16(const void* buffer, File::Size bytes) {
|
||||
complexu8_t* src = (complexu8_t*)buffer;
|
||||
complex16_t* dest = (complex16_t*)buffer;
|
||||
uint32_t i = bytes / sizeof(complexu8_t);
|
||||
|
||||
if (i != 0) {
|
||||
do {
|
||||
i--;
|
||||
int16_t re_out = ((int16_t)src[i].real() - 128) * 256;
|
||||
int16_t im_out = ((int16_t)src[i].imag() - 128) * 256;
|
||||
dest[i] = {(int16_t)re_out, (int16_t)im_out};
|
||||
} while (i != 0);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace file_convert */
|
||||
|
||||
// Automatically enables C8/C16 conversion based on file extension
|
||||
Optional<File::Error> FileConvertReader::open(const std::filesystem::path& filename) {
|
||||
convert_c8_to_c16 = path_iequal(filename.extension(), c8_ext);
|
||||
convert_cu8_to_c16 = path_iequal(filename.extension(), cu8_ext);
|
||||
return file_.open(filename);
|
||||
}
|
||||
|
||||
// If C8 conversion enabled, half the number of bytes are read from the file & expanded to fill the whole buffer.
|
||||
File::Result<File::Size> FileConvertReader::read(void* const buffer, const File::Size bytes) {
|
||||
auto read_result = file_.read(buffer, convert_c8_to_c16 ? bytes / 2 : bytes);
|
||||
auto read_result = file_.read(buffer, (convert_c8_to_c16 || convert_cu8_to_c16) ? bytes / 2 : bytes);
|
||||
if (read_result.is_ok()) {
|
||||
if (convert_c8_to_c16) {
|
||||
file_convert::c8_to_c16(buffer, read_result.value());
|
||||
read_result = read_result.value() * 2;
|
||||
} else if (convert_cu8_to_c16) {
|
||||
file_convert::cu8_to_c16(buffer, read_result.value());
|
||||
read_result = read_result.value() * 2;
|
||||
}
|
||||
bytes_read_ += read_result.value();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class FileConvertReader : public stream::Reader {
|
||||
const File& file() const& { return file_; }
|
||||
|
||||
bool convert_c8_to_c16{};
|
||||
bool convert_cu8_to_c16{};
|
||||
|
||||
protected:
|
||||
File file_{};
|
||||
|
||||
@@ -159,16 +159,15 @@ size_t MenuView::item_count() const {
|
||||
return menu_items.size();
|
||||
}
|
||||
|
||||
void MenuView::add_item(MenuItem new_item) {
|
||||
menu_items.push_back(new_item);
|
||||
void MenuView::add_item(MenuItem&& new_item) {
|
||||
menu_items.push_back(std::move(new_item));
|
||||
|
||||
update_items();
|
||||
}
|
||||
|
||||
void MenuView::add_items(std::initializer_list<MenuItem> new_items) {
|
||||
for (auto item : new_items) {
|
||||
add_item(item);
|
||||
}
|
||||
menu_items.insert(menu_items.end(), new_items);
|
||||
update_items();
|
||||
}
|
||||
|
||||
void MenuView::update_items() {
|
||||
|
||||
@@ -83,7 +83,10 @@ class MenuView : public View {
|
||||
|
||||
~MenuView();
|
||||
|
||||
void add_item(MenuItem new_item);
|
||||
// This function takes a temporary to avoid copies and heap allocations.
|
||||
// To pass a variable 'item', use add_item(std::move(item)),
|
||||
// or construct the MenuItem in the call, add_item(MenuItem{...})
|
||||
void add_item(MenuItem&& new_item);
|
||||
void add_items(std::initializer_list<MenuItem> new_items);
|
||||
void clear();
|
||||
size_t item_count() const;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <libopencm3/lpc43xx/rgu.h>
|
||||
#include <libopencm3/lpc43xx/wwdt.h>
|
||||
#include "delay.h"
|
||||
#include "string.h"
|
||||
|
||||
volatile bool usb_bulk_block_done = false;
|
||||
|
||||
|
||||
@@ -106,10 +106,12 @@ struct complex<int16_t> {
|
||||
} /* namespace std */
|
||||
|
||||
using complex8_t = std::complex<int8_t>;
|
||||
using complexu8_t = std::complex<uint8_t>;
|
||||
using complex16_t = std::complex<int16_t>;
|
||||
using complex32_t = std::complex<int32_t>;
|
||||
|
||||
static_assert(sizeof(complex8_t) == 2, "complex8_t size wrong");
|
||||
static_assert(sizeof(complexu8_t) == 2, "complexu8_t size wrong");
|
||||
static_assert(sizeof(complex16_t) == 4, "complex16_t size wrong");
|
||||
static_assert(sizeof(complex32_t) == 8, "complex32_t size wrong");
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
// to check battery mins and maxes in pmem, and validity
|
||||
#define BATT_18650_MIN_MAH 1000
|
||||
#define BATT_18650_MAX_MAH 5000
|
||||
#define BATT_18650_MAX_MAH 20000
|
||||
|
||||
using namespace modems;
|
||||
using namespace serializer;
|
||||
|
||||
@@ -393,27 +393,56 @@ void Text::getAccessibilityText(std::string& result) {
|
||||
void Text::getWidgetName(std::string& result) {
|
||||
result = "Text";
|
||||
}
|
||||
|
||||
void Text::paint(Painter& painter) {
|
||||
const auto rect = screen_rect();
|
||||
auto s = has_focus() ? style().invert() : style();
|
||||
painter.fill_rectangle(rect, s.background);
|
||||
const int char_width = s.font.char_width();
|
||||
const int line_height = s.font.line_height();
|
||||
auto default_style = has_focus() ? style().invert() : style();
|
||||
painter.fill_rectangle(rect, default_style.background);
|
||||
const int char_width = default_style.font.char_width();
|
||||
const int line_height = default_style.font.line_height();
|
||||
if (char_width == 0 || line_height == 0) return;
|
||||
const size_t chars_per_line = rect.width() / char_width;
|
||||
if (chars_per_line == 0) return;
|
||||
size_t lines_capacity = rect.height() / line_height;
|
||||
if (lines_capacity == 0) lines_capacity = 1; // at least one line, even if it overflows vertically
|
||||
if (lines_capacity == 0) lines_capacity = 1;
|
||||
auto text_view = std::string_view{text};
|
||||
size_t current_offset = 0;
|
||||
// Track the active text color across line breaks
|
||||
Color active_color = default_style.foreground;
|
||||
for (size_t line_idx = 0; line_idx < lines_capacity; ++line_idx) {
|
||||
if (current_offset >= text_view.length()) break;
|
||||
size_t chunk_len = std::min(chars_per_line, text_view.length() - current_offset);
|
||||
size_t scan_offset = current_offset;
|
||||
size_t printable_count = 0;
|
||||
Color next_color = active_color; // This will hold the color state as we scan the current chunk
|
||||
// Scan forward to find chunk length AND track color changes
|
||||
while (scan_offset < text_view.length() && printable_count < chars_per_line) {
|
||||
if (text_view[scan_offset] == '\x1B') {
|
||||
scan_offset++;
|
||||
if (scan_offset < text_view.length()) {
|
||||
uint8_t color_idx = text_view[scan_offset];
|
||||
// Mirror the logic from Painter::draw_string
|
||||
if (color_idx < std::size(term_colors)) {
|
||||
next_color = term_colors[color_idx];
|
||||
} else {
|
||||
next_color = active_color;
|
||||
}
|
||||
scan_offset++;
|
||||
}
|
||||
} else {
|
||||
scan_offset++;
|
||||
printable_count++;
|
||||
}
|
||||
}
|
||||
size_t chunk_len = scan_offset - current_offset;
|
||||
// Create a temporary style for this line, initialized with the carried-over color
|
||||
Style line_style = {.font = default_style.font, .background = default_style.background, .foreground = active_color};
|
||||
painter.draw_string(
|
||||
rect.location() + Point(0, line_idx * line_height),
|
||||
s,
|
||||
line_style,
|
||||
text_view.substr(current_offset, chunk_len));
|
||||
current_offset += chunk_len;
|
||||
// Carry the final color state of this line into the start of the next line
|
||||
active_color = next_color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user