mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-13 18:19:33 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc963c3562 | |||
| 63f99742fc | |||
| a4636d7872 | |||
| bd2ee03e44 | |||
| deeb81c183 | |||
| 12dbf957b3 | |||
| e1cc0b1ad0 | |||
| f079d57fc6 | |||
| 09b9ed642f | |||
| e74a9f3b41 | |||
| ff7a9d10cb | |||
| 5cfd7f1dc2 | |||
| 853ca2ef53 |
@@ -23,7 +23,9 @@
|
||||
|
||||
#include "app_settings.hpp"
|
||||
|
||||
#include "convert.hpp"
|
||||
#include "file.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "utility.hpp"
|
||||
@@ -36,6 +38,126 @@ namespace fs = std::filesystem;
|
||||
using namespace portapack;
|
||||
using namespace std::literals;
|
||||
|
||||
namespace {
|
||||
fs::path get_settings_path(const std::string& app_name) {
|
||||
return fs::path{u"/SETTINGS"} / app_name + u".ini";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void BoundSetting::parse(std::string_view value) {
|
||||
switch (type_) {
|
||||
case SettingType::I64:
|
||||
parse_int(value, as<int64_t>());
|
||||
break;
|
||||
case SettingType::I32:
|
||||
parse_int(value, as<int32_t>());
|
||||
break;
|
||||
case SettingType::U32:
|
||||
parse_int(value, as<uint32_t>());
|
||||
break;
|
||||
case SettingType::U8:
|
||||
parse_int(value, as<uint8_t>());
|
||||
break;
|
||||
case SettingType::String:
|
||||
as<std::string>() = std::string{value};
|
||||
break;
|
||||
case SettingType::Bool: {
|
||||
int parsed = 0;
|
||||
parse_int(value, parsed);
|
||||
as<bool>() = (parsed != 0);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void BoundSetting::write(File& file) const {
|
||||
// NB: Write directly without allocations. This happens on every
|
||||
// app exit when enabled so should be fast to keep the UX responsive.
|
||||
StringFormatBuffer buffer;
|
||||
size_t length = 0;
|
||||
|
||||
file.write(name_.data(), name_.length());
|
||||
file.write("=", 1);
|
||||
|
||||
switch (type_) {
|
||||
case SettingType::I64:
|
||||
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::I32:
|
||||
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::U32:
|
||||
file.write(to_string_dec_uint(as<uint32_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::U8:
|
||||
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::String: {
|
||||
const auto& str = as<std::string>();
|
||||
file.write(str.data(), str.length());
|
||||
break;
|
||||
}
|
||||
case SettingType::Bool:
|
||||
file.write(as<bool>() ? "1" : "0", 1);
|
||||
break;
|
||||
}
|
||||
|
||||
file.write("\r\n", 2);
|
||||
}
|
||||
|
||||
SettingsStore::SettingsStore(std::string_view store_name, SettingBindings bindings)
|
||||
: store_name_{store_name}, bindings_{bindings} {
|
||||
load_settings(store_name_, bindings_);
|
||||
}
|
||||
|
||||
SettingsStore::~SettingsStore() {
|
||||
save_settings(store_name_, bindings_);
|
||||
}
|
||||
|
||||
bool load_settings(std::string_view store_name, SettingBindings& bindings) {
|
||||
File f;
|
||||
auto path = get_settings_path(std::string{store_name});
|
||||
|
||||
auto error = f.open(path);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
auto reader = FileLineReader(f);
|
||||
for (const auto& line : reader) {
|
||||
auto cols = split_string(line, '=');
|
||||
|
||||
if (cols.size() != 2)
|
||||
continue;
|
||||
|
||||
// Find a binding with the name.
|
||||
auto it = std::find_if(
|
||||
bindings.begin(), bindings.end(),
|
||||
[name = cols[0]](auto& bound_setting) {
|
||||
return name == bound_setting.name();
|
||||
});
|
||||
|
||||
// If found, parse the value.
|
||||
if (it != bindings.end())
|
||||
it->parse(cols[1]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool save_settings(std::string_view store_name, const SettingBindings& bindings) {
|
||||
File f;
|
||||
auto path = get_settings_path(std::string{store_name});
|
||||
|
||||
auto error = f.create(path);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
for (const auto& bound_setting : bindings)
|
||||
bound_setting.write(f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace app_settings {
|
||||
|
||||
template <typename T>
|
||||
@@ -64,10 +186,6 @@ static void write_setting(File& file, std::string_view setting_name, const T& va
|
||||
file.write("\r\n", 2);
|
||||
}
|
||||
|
||||
static fs::path get_settings_path(const std::string& app_name) {
|
||||
return fs::path{u"/SETTINGS"} / app_name + u".ini";
|
||||
}
|
||||
|
||||
namespace setting {
|
||||
constexpr std::string_view baseband_bandwidth = "baseband_bandwidth="sv;
|
||||
constexpr std::string_view sampling_rate = "sampling_rate="sv;
|
||||
@@ -88,13 +206,6 @@ constexpr std::string_view squelch = "squelch="sv;
|
||||
constexpr std::string_view volume = "volume="sv;
|
||||
} // namespace setting
|
||||
|
||||
// TODO: Only load/save values that are declared used.
|
||||
// This will prevent switching apps from changing setting unnecessarily.
|
||||
// TODO: Track which values are actually read.
|
||||
// TODO: Maybe just use a dictionary which would allow for custom settings.
|
||||
// TODO: Create a control value binding which will allow controls to
|
||||
// be declaratively bound to a setting and persistence will be magic.
|
||||
|
||||
ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
|
||||
if (!portapack::persistent_memory::load_app_settings())
|
||||
return ResultCode::SettingsDisabled;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -27,12 +28,77 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "file.hpp"
|
||||
#include "max283x.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
/* Represents a named setting bound to a variable instance. */
|
||||
/* Using void* instead of std::variant, because variant is a pain to dispatch over. */
|
||||
class BoundSetting {
|
||||
/* The type of bound setting. */
|
||||
enum class SettingType : uint8_t {
|
||||
I64,
|
||||
I32,
|
||||
U32,
|
||||
U8,
|
||||
String,
|
||||
Bool,
|
||||
};
|
||||
|
||||
public:
|
||||
BoundSetting(std::string_view name, int64_t* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::I64} {}
|
||||
|
||||
BoundSetting(std::string_view name, int32_t* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::I32} {}
|
||||
|
||||
BoundSetting(std::string_view name, uint32_t* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::U32} {}
|
||||
|
||||
BoundSetting(std::string_view name, uint8_t* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::U8} {}
|
||||
|
||||
BoundSetting(std::string_view name, std::string* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::String} {}
|
||||
|
||||
BoundSetting(std::string_view name, bool* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::Bool} {}
|
||||
|
||||
std::string_view name() const { return name_; }
|
||||
void parse(std::string_view value);
|
||||
void write(File& file) const;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
constexpr auto& as() const {
|
||||
return *reinterpret_cast<T*>(target_);
|
||||
}
|
||||
|
||||
std::string_view name_;
|
||||
void* target_;
|
||||
SettingType type_;
|
||||
};
|
||||
|
||||
using SettingBindings = std::vector<BoundSetting>;
|
||||
|
||||
/* RAII wrapper for Settings that loads/saves to the SD card. */
|
||||
class SettingsStore {
|
||||
public:
|
||||
SettingsStore(std::string_view store_name, SettingBindings bindings);
|
||||
~SettingsStore();
|
||||
|
||||
private:
|
||||
std::string_view store_name_;
|
||||
SettingBindings bindings_;
|
||||
};
|
||||
|
||||
bool load_settings(std::string_view store_name, SettingBindings& bindings);
|
||||
bool save_settings(std::string_view store_name, const SettingBindings& bindings);
|
||||
|
||||
namespace app_settings {
|
||||
|
||||
enum class ResultCode : uint8_t {
|
||||
|
||||
@@ -86,7 +86,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
};
|
||||
|
||||
receiver_model.enable();
|
||||
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
|
||||
option_bandwidth.set_by_value(500000); // better by_value than by option_bandwidth.set_selected_index(4), Preselected default option 500kHz.
|
||||
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
|
||||
@@ -71,6 +71,8 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
||||
if (!settings_.loaded())
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
|
||||
check_log.set_value(enable_logging);
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
// TODO: app setting instead?
|
||||
@@ -88,7 +90,6 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
||||
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
|
||||
|
||||
audio::output::start();
|
||||
|
||||
baseband::set_pocsag();
|
||||
}
|
||||
|
||||
@@ -99,8 +100,9 @@ void POCSAGAppView::focus() {
|
||||
POCSAGAppView::~POCSAGAppView() {
|
||||
audio::output::stop();
|
||||
|
||||
// Save ignored address
|
||||
// Save settings.
|
||||
persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32());
|
||||
enable_logging = check_log.value();
|
||||
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
@@ -65,7 +65,14 @@ class POCSAGAppView : public View {
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_pocsag", app_settings::Mode::RX};
|
||||
"rx_pocsag",
|
||||
app_settings::Mode::RX};
|
||||
|
||||
// Settings
|
||||
bool enable_logging = false;
|
||||
SettingsStore settings_store_{
|
||||
"rx_pocsag_ui",
|
||||
{{"enable_logging", &enable_logging}}};
|
||||
|
||||
uint32_t last_address = 0xFFFFFFFF;
|
||||
pocsag::POCSAGState pocsag_state{};
|
||||
|
||||
@@ -57,14 +57,14 @@ class DfuMenu : public View {
|
||||
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_4{{15 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_5{{15 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_4{{15 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_5{{15 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
class DfuMenu2 : public View {
|
||||
|
||||
@@ -185,7 +185,6 @@ FileManBaseView::FileManBaseView(
|
||||
extension_filter{filter} {
|
||||
add_children({&labels,
|
||||
&text_current,
|
||||
&text_info,
|
||||
&button_exit});
|
||||
|
||||
button_exit.on_select = [this, &nav](Button&) {
|
||||
@@ -250,8 +249,10 @@ void FileManBaseView::refresh_list() {
|
||||
auto entry_name = truncate(entry.path, 20);
|
||||
|
||||
if (entry.is_directory) {
|
||||
auto size_str = (entry.path == parent_dir_path) ? "" : to_string_dec_uint(file_count(entry.path));
|
||||
|
||||
menu_view.add_item(
|
||||
{entry_name,
|
||||
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
ui::Color::yellow(),
|
||||
&bitmap_icon_dir,
|
||||
[this](KeyEvent key) {
|
||||
@@ -278,7 +279,6 @@ void FileManBaseView::refresh_list() {
|
||||
break;
|
||||
}
|
||||
|
||||
text_info.set(menu_view.item_count() >= max_items_shown ? "Too many files!" : "");
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
}
|
||||
|
||||
@@ -342,7 +342,6 @@ FileSaveView::FileSaveView(
|
||||
file_{ file }
|
||||
{
|
||||
add_children({
|
||||
&labels,
|
||||
&text_path,
|
||||
&button_edit_path,
|
||||
&text_name,
|
||||
@@ -546,7 +545,6 @@ FileManagerView::FileManagerView(
|
||||
|
||||
add_children({
|
||||
&menu_view,
|
||||
&labels,
|
||||
&text_date,
|
||||
&button_rename,
|
||||
&button_delete,
|
||||
@@ -560,10 +558,16 @@ FileManagerView::FileManagerView(
|
||||
});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
if (selected_is_valid())
|
||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
else
|
||||
text_date.set("");
|
||||
if (menu_view.highlighted_index() >= max_items_shown - 1) {
|
||||
text_date.set_style(&Styles::red);
|
||||
text_date.set("Too many files!");
|
||||
} else {
|
||||
text_date.set_style(&Styles::grey);
|
||||
if (selected_is_valid())
|
||||
text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
else
|
||||
text_date.set("");
|
||||
}
|
||||
};
|
||||
|
||||
refresh_list();
|
||||
|
||||
@@ -115,11 +115,6 @@ class FileManBaseView : public View {
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
|
||||
// HACK: for item count limit.
|
||||
Text text_info{
|
||||
{1 * 8, 35 * 8, 15 * 8, 16},
|
||||
""};
|
||||
|
||||
Button button_exit{
|
||||
{22 * 8, 34 * 8, 8 * 8, 32},
|
||||
"Exit"};
|
||||
@@ -218,11 +213,8 @@ class FileManagerView : public FileManBaseView {
|
||||
// True if the selected entry is a real file item.
|
||||
bool selected_is_valid() const;
|
||||
|
||||
Labels labels{
|
||||
{{0, 26 * 8}, "Created ", Color::light_grey()}};
|
||||
|
||||
Text text_date{
|
||||
{8 * 8, 26 * 8, 19 * 8, 16},
|
||||
{0 * 8, 26 * 8, 28 * 8, 16},
|
||||
""};
|
||||
|
||||
NewButton button_rename{
|
||||
|
||||
@@ -37,6 +37,22 @@ namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ReconView::check_update_ranges_from_current() {
|
||||
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
|
||||
if (update_ranges && !manual_mode) {
|
||||
button_manual_start.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.min = current_entry().frequency_a;
|
||||
if (current_entry().frequency_b != 0) {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_b));
|
||||
frequency_range.max = current_entry().frequency_b;
|
||||
} else {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.max = current_entry().frequency_a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ReconView::current_is_valid() {
|
||||
return (unsigned)current_index < frequency_list.size();
|
||||
}
|
||||
@@ -193,8 +209,23 @@ bool ReconView::recon_load_config_from_sd() {
|
||||
break;
|
||||
case 6:
|
||||
parse_int(line, wait);
|
||||
break;
|
||||
case 7:
|
||||
if (!update_ranges) {
|
||||
parse_int(line, frequency_range.min);
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
if (!update_ranges) {
|
||||
parse_int(line, frequency_range.max);
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
|
||||
}
|
||||
complete = true; // NB: Last entry.
|
||||
break;
|
||||
default:
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (complete) break;
|
||||
@@ -220,6 +251,9 @@ bool ReconView::recon_save_config_to_sd() {
|
||||
settings_file.write_line(to_string_dec_int(squelch));
|
||||
settings_file.write_line(to_string_dec_uint(recon_match_mode));
|
||||
settings_file.write_line(to_string_dec_int(wait));
|
||||
settings_file.write_line(to_string_dec_uint(frequency_range.min));
|
||||
settings_file.write_line(to_string_dec_uint(frequency_range.max));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -290,19 +324,7 @@ void ReconView::handle_retune() {
|
||||
}
|
||||
if (last_index != current_index) {
|
||||
last_index = current_index;
|
||||
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
|
||||
if (update_ranges && !manual_mode) {
|
||||
button_manual_start.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.min = current_entry().frequency_a;
|
||||
if (current_entry().frequency_b != 0) {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_b));
|
||||
frequency_range.max = current_entry().frequency_b;
|
||||
} else {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.max = current_entry().frequency_a;
|
||||
}
|
||||
}
|
||||
}
|
||||
check_update_ranges_from_current();
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
update_description();
|
||||
}
|
||||
@@ -562,9 +584,14 @@ ReconView::ReconView(NavigationView& nav)
|
||||
recon_pause();
|
||||
if (!frequency_range.min || !frequency_range.max) {
|
||||
nav_.display_modal("Error", "Both START and END freqs\nneed a value");
|
||||
} else if (frequency_range.min > frequency_range.max) {
|
||||
nav_.display_modal("Error", "END freq\nis lower than START");
|
||||
} else {
|
||||
if (frequency_range.min > frequency_range.max) {
|
||||
std::swap(frequency_range.min, frequency_range.max);
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
|
||||
}
|
||||
|
||||
// no need to stop audio in SPEC
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
@@ -839,17 +866,7 @@ void ReconView::frequency_file_load(bool) {
|
||||
receiver_model.enable();
|
||||
receiver_model.set_squelch_level(0);
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
if (update_ranges && !manual_mode) {
|
||||
button_manual_start.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.min = current_entry().frequency_a;
|
||||
if (current_entry().frequency_b != 0) {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_b));
|
||||
frequency_range.max = current_entry().frequency_b;
|
||||
} else {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.max = current_entry().frequency_a;
|
||||
}
|
||||
}
|
||||
check_update_ranges_from_current();
|
||||
update_description();
|
||||
handle_retune();
|
||||
}
|
||||
@@ -859,12 +876,14 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
uint32_t local_recon_lock_duration = recon_lock_duration;
|
||||
|
||||
chrono_end = chTimeNow();
|
||||
time_interval = chrono_end - chrono_start;
|
||||
if (field_lock_wait.value() == 0) {
|
||||
if (time_interval <= 1) // capping here to avoid freeze because too quick
|
||||
local_recon_lock_duration = 2; // minimum working tested value
|
||||
else
|
||||
local_recon_lock_duration = time_interval;
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION) {
|
||||
time_interval = chrono_end - chrono_start;
|
||||
if (field_lock_wait.value() == 0) {
|
||||
if (time_interval <= 1) // capping here to avoid freeze because too quick
|
||||
local_recon_lock_duration = 2; // minimum working tested value
|
||||
else
|
||||
local_recon_lock_duration = time_interval;
|
||||
}
|
||||
}
|
||||
chrono_start = chrono_end;
|
||||
|
||||
@@ -886,7 +905,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
status = 0;
|
||||
freq_lock = 0;
|
||||
timer = local_recon_lock_duration;
|
||||
big_display.set_style(&Styles::white);
|
||||
}
|
||||
if (freq_lock < recon_lock_nb_match) // LOCKING
|
||||
{
|
||||
@@ -901,7 +919,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (db > squelch) // MATCHING LEVEL
|
||||
{
|
||||
freq_lock++;
|
||||
timer += local_recon_lock_duration; // give some more time for next lock
|
||||
timer += time_interval; // give some more time for next lock
|
||||
} else {
|
||||
// continuous, direct cut it if not consecutive match after 1 first match
|
||||
if (recon_match_mode == RECON_MATCH_CONTINUOUS) {
|
||||
@@ -955,7 +973,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
}
|
||||
|
||||
if (timer != 0) {
|
||||
timer -= local_recon_lock_duration;
|
||||
timer -= time_interval;
|
||||
if (timer < 0) {
|
||||
timer = 0;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ class ReconView : public View {
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_recon", app_settings::Mode::RX};
|
||||
|
||||
void check_update_ranges_from_current();
|
||||
void set_loop_config(bool v);
|
||||
void clear_freqlist_for_ui_action();
|
||||
void reset_indexes();
|
||||
|
||||
@@ -110,11 +110,11 @@ class ScannerView : public View {
|
||||
std::string title() const override { return "Scanner"; };
|
||||
|
||||
private:
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_scanner", app_settings::Mode::RX};
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
|
||||
void start_scan_thread();
|
||||
void restart_scan();
|
||||
|
||||
@@ -360,6 +360,8 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
&text_size,
|
||||
});
|
||||
|
||||
viewer.set_font_zoom(enable_zoom);
|
||||
|
||||
viewer.on_select = [this]() {
|
||||
// Treat as if menu button was pressed.
|
||||
if (button_menu.on_select)
|
||||
@@ -382,7 +384,7 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
};
|
||||
|
||||
menu.on_zoom() = [this]() {
|
||||
viewer.toggle_font_zoom();
|
||||
enable_zoom = viewer.toggle_font_zoom();
|
||||
refresh_ui();
|
||||
hide_menu(true);
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "file_wrapper.hpp"
|
||||
#include "optional.hpp"
|
||||
|
||||
@@ -80,7 +81,10 @@ class TextViewer : public Widget {
|
||||
|
||||
const Style& style() { return *font_style; }
|
||||
void set_font_zoom(bool zoom);
|
||||
void toggle_font_zoom() { set_font_zoom(!font_zoom); };
|
||||
bool toggle_font_zoom() {
|
||||
set_font_zoom(!font_zoom);
|
||||
return font_zoom;
|
||||
};
|
||||
|
||||
private:
|
||||
bool font_zoom{};
|
||||
@@ -220,6 +224,12 @@ class TextEditorView : public View {
|
||||
void on_show() override;
|
||||
|
||||
private:
|
||||
// Settings
|
||||
bool enable_zoom = false;
|
||||
SettingsStore settings_store_{
|
||||
"notepad",
|
||||
{{"enable_zoom", &enable_zoom}}};
|
||||
|
||||
static constexpr size_t max_edit_length = 1024;
|
||||
std::string edit_line_buffer_{};
|
||||
|
||||
|
||||
@@ -579,6 +579,17 @@ bool is_empty_directory(const path& file_path) {
|
||||
return !((result == FR_OK) && (filinfo.fname[0] != (TCHAR)'\0'));
|
||||
}
|
||||
|
||||
int file_count(const path& directory) {
|
||||
int count{0};
|
||||
|
||||
for (auto& entry : std::filesystem::directory_iterator(directory, (const TCHAR*)u"*")) {
|
||||
(void)entry; // avoid unused warning
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
space_info space(const path& p) {
|
||||
DWORD free_clusters{0};
|
||||
FATFS* fs;
|
||||
|
||||
@@ -250,6 +250,8 @@ bool file_exists(const path& file_path);
|
||||
bool is_directory(const path& file_path);
|
||||
bool is_empty_directory(const path& file_path);
|
||||
|
||||
int file_count(const path& dir_path);
|
||||
|
||||
space_info space(const path& p);
|
||||
|
||||
} /* namespace filesystem */
|
||||
|
||||
@@ -76,10 +76,12 @@ options_t freqman_bandwidths[4] = {
|
||||
// SPEC -- TODO: these should be indexes.
|
||||
{"12k5", 12500},
|
||||
{"16k", 16000},
|
||||
{"20k", 20000},
|
||||
{"25k", 25000},
|
||||
{"32k", 32000},
|
||||
{"50k", 50000},
|
||||
{"75k", 75000},
|
||||
{"100k", 100000},
|
||||
{"150k", 150000},
|
||||
{"250k", 250000},
|
||||
{"500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/
|
||||
{"600k", 600000}, /* We doubled x2 previous REC BW limit , now extended BW from 600k to 1M with fast enough SD card in C16 or C8 format .*/
|
||||
@@ -268,18 +270,18 @@ std::string to_freqman_string(const freqman_entry& entry) {
|
||||
|
||||
switch (entry.type) {
|
||||
case freqman_type::Single:
|
||||
append_field("f", to_string_dec_uint64(entry.frequency_a));
|
||||
append_field("f", to_string_dec_uint(entry.frequency_a));
|
||||
break;
|
||||
case freqman_type::Range:
|
||||
append_field("a", to_string_dec_uint64(entry.frequency_a));
|
||||
append_field("b", to_string_dec_uint64(entry.frequency_b));
|
||||
append_field("a", to_string_dec_uint(entry.frequency_a));
|
||||
append_field("b", to_string_dec_uint(entry.frequency_b));
|
||||
|
||||
if (is_valid(entry.step))
|
||||
append_field("s", freqman_entry_get_step_string_short(entry.step));
|
||||
break;
|
||||
case freqman_type::HamRadio:
|
||||
append_field("r", to_string_dec_uint64(entry.frequency_a));
|
||||
append_field("t", to_string_dec_uint64(entry.frequency_b));
|
||||
append_field("r", to_string_dec_uint(entry.frequency_a));
|
||||
append_field("t", to_string_dec_uint(entry.frequency_b));
|
||||
|
||||
if (is_valid(entry.tone))
|
||||
append_field("c", tonekey::tone_key_value_string(entry.tone));
|
||||
|
||||
@@ -59,30 +59,37 @@ static char* to_string_dec_uint_pad_internal(
|
||||
return q;
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
char* to_string_dec_uint_internal(Int n, StringFormatBuffer& buffer, size_t& length) {
|
||||
static char* to_string_dec_uint_internal(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
|
||||
auto end = &buffer.back();
|
||||
auto start = to_string_dec_uint_internal(end, n);
|
||||
length = end - start;
|
||||
return start;
|
||||
}
|
||||
|
||||
char* to_string_dec_uint(uint32_t n, StringFormatBuffer& buffer, size_t& length) {
|
||||
char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
|
||||
return to_string_dec_uint_internal(n, buffer, length);
|
||||
}
|
||||
|
||||
char* to_string_dec_uint64(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
|
||||
return to_string_dec_uint_internal(n, buffer, length);
|
||||
char* to_string_dec_int(int64_t n, StringFormatBuffer& buffer, size_t& length) {
|
||||
bool negative = n < 0;
|
||||
auto start = to_string_dec_uint(negative ? -n : n, buffer, length);
|
||||
|
||||
if (negative) {
|
||||
*(--start) = '-';
|
||||
++length;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
std::string to_string_dec_uint(uint32_t n) {
|
||||
std::string to_string_dec_int(int64_t n) {
|
||||
StringFormatBuffer b{};
|
||||
size_t len{};
|
||||
char* str = to_string_dec_uint(n, b, len);
|
||||
char* str = to_string_dec_int(n, b, len);
|
||||
return std::string(str, len);
|
||||
}
|
||||
|
||||
std::string to_string_dec_uint64(uint64_t n) {
|
||||
std::string to_string_dec_uint(uint64_t n) {
|
||||
StringFormatBuffer b{};
|
||||
size_t len{};
|
||||
char* str = to_string_dec_uint(n, b, len);
|
||||
@@ -194,14 +201,14 @@ std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
|
||||
};
|
||||
|
||||
if (precision < 1) {
|
||||
final_str = to_string_dec_uint64(f / 1000000);
|
||||
final_str = to_string_dec_uint(f / 1000000);
|
||||
} else {
|
||||
if (precision > 6)
|
||||
precision = 6;
|
||||
|
||||
uint32_t divisor = pow10[6 - precision];
|
||||
|
||||
final_str = to_string_dec_uint64(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||
final_str = to_string_dec_uint(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||
}
|
||||
return final_str;
|
||||
}
|
||||
|
||||
@@ -43,17 +43,17 @@ const char unit_prefix[7]{'n', 'u', 'm', 0, 'k', 'M', 'G'};
|
||||
|
||||
using StringFormatBuffer = std::array<char, 24>;
|
||||
|
||||
/* uint conversion without memory allocations. */
|
||||
char* to_string_dec_uint(uint32_t n, StringFormatBuffer& buffer, size_t& length);
|
||||
char* to_string_dec_uint64(uint64_t n, StringFormatBuffer& buffer, size_t& length);
|
||||
/* Integer conversion without memory allocations. */
|
||||
char* to_string_dec_int(int64_t n, StringFormatBuffer& buffer, size_t& length);
|
||||
char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length);
|
||||
|
||||
std::string to_string_dec_uint(uint32_t n);
|
||||
std::string to_string_dec_uint64(uint64_t n);
|
||||
std::string to_string_dec_int(int64_t n);
|
||||
std::string to_string_dec_uint(uint64_t n);
|
||||
|
||||
// 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, const char fill = ' ');
|
||||
std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0);
|
||||
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
|
||||
std::string to_string_decimal(float decimal, int8_t precision);
|
||||
|
||||
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
|
||||
|
||||
@@ -383,7 +383,7 @@ void WaterfallView::on_audio_spectrum() {
|
||||
// TODO: Comments below refer to a fixed oversample rate (8x), cleanup.
|
||||
uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate) {
|
||||
switch (sampling_rate) { // Use the var fs (sampling_rate) to set up BPF aprox < fs_max / 2 by Nyquist theorem.
|
||||
case 0 ... 2'000'000: // BW Captured range (0 <= 250kHz max) fs = 8 x 250 kHz.
|
||||
case 0 ... 3'500'000: // BW Captured range (0 <= 250kHz max) fs = 8x250 kHz =2000., 16x150 khz =2400, 32x100 khz =3200, (32x75k = 2400), (future 64x40 khz =2400)
|
||||
return 1'750'000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
|
||||
case 4'000'000 ... 6'000'000: // BW capture range (500k...750kHz max) fs_max = 8 x 750kHz = 6Mhz
|
||||
|
||||
@@ -113,7 +113,7 @@ uint32_t RecordView::set_sampling_rate(uint32_t new_sampling_rate) {
|
||||
* They are ok as recorded spectrum indication, but they should not be used by Replay app. (the voice speed will be accelerated)
|
||||
|
||||
* We keep original black background in all the correct IQ .C16 files BW's Options. */
|
||||
if (actual_sampling_rate > 8'000'000) { // Till fw 1.7.4 BW limit yellow REC button was >500khz, now BW >1Mhz (fs=8*BW), (>1Mhz ..2M75)
|
||||
if (actual_sampling_rate > 8'000'000) { // yellow REC button means not ok for REC, BW >1Mhz (BW from 12k5 till 1Mhz OK for REC and Replay)
|
||||
button_record.set_background(ui::Color::yellow());
|
||||
} else {
|
||||
button_record.set_background(ui::Color::black());
|
||||
@@ -144,11 +144,11 @@ OversampleRate RecordView::get_oversample_rate(uint32_t sample_rate) {
|
||||
|
||||
auto rate = ::get_oversample_rate(sample_rate);
|
||||
|
||||
// Currently proc_capture only supports x8 and x16 for decimation.
|
||||
if (rate < OversampleRate::x8)
|
||||
// Currently proc_capture only supports /8, /16, /32 for decimation.
|
||||
if (rate < OversampleRate::x8) // clipping while /4 is not implemented yet (it will be used >1Mhz onwards when available)
|
||||
rate = OversampleRate::x8;
|
||||
else if (rate > OversampleRate::x16)
|
||||
rate = OversampleRate::x16;
|
||||
else if (rate > OversampleRate::x64) // clipping while /128 is not implemented yet , (but it is not necessary for 12k5)
|
||||
rate = OversampleRate::x64;
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ CaptureProcessor::CaptureProcessor() {
|
||||
decim_0_8.configure(taps_200k_decim_0.taps, 33554432); // to be used with decim1 (/2), then total two stages decim (/16)
|
||||
decim_0_8_180k.configure(taps_180k_wfm_decim_0.taps, 33554432); // to be used alone - no additional decim1 (/2), then total single stage decim (/8)
|
||||
|
||||
decim_1.configure(taps_200k_decim_1.taps, 131072);
|
||||
decim_1_2.configure(taps_200k_decim_1.taps, 131072);
|
||||
decim_1_8.configure(taps_16k0_decim_1.taps, 131072); // tentative decim1 /8 and taps, pending to be optimized.
|
||||
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
baseband_thread.start();
|
||||
@@ -39,11 +40,16 @@ CaptureProcessor::CaptureProcessor() {
|
||||
|
||||
void CaptureProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 2.4576MHz, 2048 samples */
|
||||
const auto decim_0_out = decim_0_execute(buffer, dst_buffer); // selectable 3 possible decim_0, (/4. /8 200k soft transition filter , /8 180k sharp )
|
||||
const auto decim_0_out = decim_0_execute(buffer, dst_buffer); // selectable 3 possible decim_0, (/4. /8 200k soft filter , /8 180k sharp )
|
||||
const auto decim_1_out = decim_1_execute(decim_0_out, dst_buffer); // selectable 3 possible decim_1, (/8. /2 200k or bypassed /1 )
|
||||
|
||||
/* this code was valid when we had only 2 decim1 cases.
|
||||
const auto decim_1_out = baseband_fs < 4800'000
|
||||
? decim_1.execute(decim_0_out, dst_buffer) // < 500khz double decim. stage
|
||||
: decim_0_out; // > 500khz single decim. stage
|
||||
? decim_1_2.execute(decim_0_out, dst_buffer) // < 600khz double decim. stage , means 500khz and lower bit rates.
|
||||
// ? decim_1_8.execute(decim_0_out, dst_buffer) // < 600khz double decim. stage , means 500khz and lower bit rates.
|
||||
: decim_0_out; // >= 600khz single decim. stage , means 600khz and upper bit rates.
|
||||
|
||||
} */
|
||||
|
||||
const auto& decimator_out = decim_1_out;
|
||||
const auto& channel = decimator_out;
|
||||
@@ -91,13 +97,48 @@ void CaptureProcessor::sample_rate_config(const SampleRateConfigMessage& message
|
||||
|
||||
baseband_thread.set_sampling_rate(baseband_fs);
|
||||
|
||||
// Current fw , we are using only 2 decim_0 modes, /4 , /8
|
||||
auto decim_0_factor = oversample_rate == OversampleRate::x8
|
||||
? decim_0_4.decimation_factor
|
||||
: decim_0_8.decimation_factor;
|
||||
size_t decim_0_output_fs = baseband_fs / decim_0_factor;
|
||||
|
||||
size_t decim_0_output_fs = baseband_fs / decim_0_factor;
|
||||
size_t decim_1_input_fs = decim_0_output_fs;
|
||||
size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
|
||||
|
||||
size_t decim_1_factor;
|
||||
switch (oversample_rate) { // we are using 3 decim_1 modes, /1 , /2 , /8
|
||||
case OversampleRate::x8:
|
||||
if (baseband_fs < 4800'000) {
|
||||
decim_1_factor = decim_1_2.decimation_factor; // /8 = /4x2
|
||||
} else {
|
||||
decim_1_factor = 2 * 1; // 600khz and onwards, single decim /8 = /8x1 (we applied additional *2 correction to speed up waterfall, no effect to scale spectrum)
|
||||
}
|
||||
break;
|
||||
|
||||
case OversampleRate::x16:
|
||||
decim_1_factor = 2 * decim_1_2.decimation_factor; // /16 = /8x2 (we applied additional *2 correction to increase waterfall spped >=600k and smooth & avoid abnormal motion >1M5 )
|
||||
break;
|
||||
|
||||
case OversampleRate::x32:
|
||||
decim_1_factor = 2 * decim_1_8.decimation_factor; // /32 = /4x8 (we applied additional *2 correction to speed up waterfall, no effect to scale spectrum)
|
||||
break;
|
||||
|
||||
case OversampleRate::x64:
|
||||
decim_1_factor = 8 * decim_1_8.decimation_factor; // /64 = /8x8 (we applied additional *8 correction to speed up waterfall, no effect to scale spectrum)
|
||||
break;
|
||||
|
||||
default:
|
||||
decim_1_factor = 2; // just default initial value to remove compile warning.
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
auto decim_1_factor = oversample_rate == OversampleRate::x32 // that was ok, when we had only 2 oversampling x8 , x16
|
||||
? decim_1_8.decimation_factor
|
||||
: decim_1_2.decimation_factor;
|
||||
|
||||
*/
|
||||
size_t decim_1_output_fs = decim_1_input_fs / decim_1_factor;
|
||||
|
||||
channel_filter_low_f = taps_200k_decim_1.low_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_high_f = taps_200k_decim_1.high_frequency_normalized * decim_1_input_fs;
|
||||
@@ -117,15 +158,45 @@ void CaptureProcessor::capture_config(const CaptureConfigMessage& message) {
|
||||
|
||||
buffer_c16_t CaptureProcessor::decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst) {
|
||||
switch (oversample_rate) {
|
||||
case OversampleRate::x8: // we can get /8 by two means , decim0 (:4) + decim1 (:2) . or just decim0 (;8)
|
||||
case OversampleRate::x8: // we can get /8 by two means , decim0 (/4) + decim1 (/2) . or just decim0 (/8)
|
||||
if (baseband_fs < 4800'000) { // 600khz (600k x 8)
|
||||
return decim_0_4.execute(src, dst); // decim_0 /4 with double decim stage
|
||||
return decim_0_4.execute(src, dst); // decim_0 , /4 with double decim stage
|
||||
} else {
|
||||
return decim_0_8_180k.execute(src, dst); // decim_0 /8 with single decim stage
|
||||
}
|
||||
|
||||
case OversampleRate::x16:
|
||||
return decim_0_8.execute(src, dst);
|
||||
return decim_0_8.execute(src, dst); // decim_0 , /8 with double decim stage
|
||||
|
||||
case OversampleRate::x32:
|
||||
return decim_0_4.execute(src, dst); // decim_0 , /4 with double decim stage
|
||||
|
||||
case OversampleRate::x64:
|
||||
return decim_0_8.execute(src, dst); // decim_0 , /8 with double decim stage
|
||||
|
||||
default:
|
||||
chDbgPanic("Unhandled OversampleRate");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
buffer_c16_t CaptureProcessor::decim_1_execute(const buffer_c16_t& src, const buffer_c16_t& dst) {
|
||||
switch (oversample_rate) {
|
||||
case OversampleRate::x8: // we can get /8 by two means , decim0 (/4) + decim1 (/2) . or just decim0 (/8)
|
||||
if (baseband_fs < 4800'000) { // 600khz (600k x 8)
|
||||
return decim_1_2.execute(src, dst);
|
||||
} else {
|
||||
return src;
|
||||
}
|
||||
|
||||
case OversampleRate::x16:
|
||||
return decim_1_2.execute(src, dst); // total decim /16 = /8x2, applied to 100khz and 150khz
|
||||
|
||||
case OversampleRate::x32:
|
||||
return decim_1_8.execute(src, dst); // total decim /32 = /4x8, appled to 75k , 50k, 32k
|
||||
|
||||
case OversampleRate::x64:
|
||||
return decim_1_8.execute(src, dst); // total decim /64 = /8x8, appled to 16k and 12k5
|
||||
|
||||
default:
|
||||
chDbgPanic("Unhandled OversampleRate");
|
||||
|
||||
@@ -57,7 +57,8 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0_4{};
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8{};
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8_180k{};
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1{};
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1_2{};
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1_8{};
|
||||
|
||||
int32_t channel_filter_low_f = 0;
|
||||
int32_t channel_filter_high_f = 0;
|
||||
@@ -80,6 +81,9 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
|
||||
/* Dispatch to the correct decim_0 based on oversample rate. */
|
||||
buffer_c16_t decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst);
|
||||
|
||||
/* Dispatch to the correct decim_1 based on oversample rate. */
|
||||
buffer_c16_t decim_1_execute(const buffer_c16_t& src, const buffer_c16_t& dst);
|
||||
};
|
||||
|
||||
#endif /*__PROC_CAPTURE_HPP__*/
|
||||
|
||||
@@ -815,6 +815,12 @@ enum class OversampleRate : uint8_t {
|
||||
|
||||
/* Oversample rate of 32 times the sample rate. */
|
||||
x32 = 32,
|
||||
|
||||
/* Oversample rate of 64 times the sample rate. */
|
||||
x64 = 64,
|
||||
|
||||
/* Oversample rate of 128 times the sample rate. */
|
||||
x128 = 128,
|
||||
};
|
||||
|
||||
class SampleRateConfigMessage : public Message {
|
||||
|
||||
@@ -40,16 +40,28 @@
|
||||
* The baseband needs to know how to correctly decimate (or interpolate) so
|
||||
* the set of allowed scalars is fixed (See OversampleRate enum).
|
||||
* In testing, a minimum rate of 400kHz seems to the functional minimum.
|
||||
*
|
||||
* There are several different concepts or terms related to Capture and Replay,
|
||||
* (1) oversampling (x8, x16 ,...) / decimation (/8, /16...)
|
||||
* In Capture App , when ADC can not handle directly a requiered low sample rates ,
|
||||
* we need to apply oversampling (x8. x16 ex) , getting more real samples than needed) by "x_number" ,
|
||||
* and later to write it to SD card with the proper needed real sample rate , we apply Decimation , "/ number" .
|
||||
*
|
||||
* (2) up-sampling or re-escale or interpolation. (x8, x16, ...)
|
||||
* In Replay-list App, when we got too low bit rate data for Hackrf ,
|
||||
* we need to upsampling or interpolate or resampling to increase those low bit rates
|
||||
* to a proper sample rate higher than the min. to be able to be transmitted by Hackrf.
|
||||
*/
|
||||
|
||||
/* Gets the oversample rate for a given sample rate.
|
||||
* The oversample rate is used to increase the sample rate to improve SNR and quality.
|
||||
* This is also used as the interpolation rate when replaying captures. */
|
||||
inline OversampleRate get_oversample_rate(uint32_t sample_rate) {
|
||||
if (sample_rate < 25'000) return OversampleRate::x32;
|
||||
if (sample_rate < 50'000) return OversampleRate::x16;
|
||||
if (sample_rate < 30'000) return OversampleRate::x64; // 25k, 16k, 12k5.
|
||||
if (sample_rate < 80'000) return OversampleRate::x32; // 75k, 50k, 32k.
|
||||
if (sample_rate < 250'000) return OversampleRate::x16; // 100k and 150k.
|
||||
|
||||
return OversampleRate::x8;
|
||||
return OversampleRate::x8; // 250k .. 1Mhz, that decim x8 , is already applied.(OVerSampling and decim OK)
|
||||
}
|
||||
|
||||
/* Gets the actual sample rate for a given sample rate.
|
||||
|
||||
@@ -365,9 +365,13 @@ void Text::set(std::string_view value) {
|
||||
void Text::paint(Painter& painter) {
|
||||
const auto rect = screen_rect();
|
||||
auto s = has_focus() ? style().invert() : style();
|
||||
auto max_len = (unsigned)rect.width() / s.font.char_width();
|
||||
|
||||
painter.fill_rectangle(rect, s.background);
|
||||
|
||||
if (text.length() > max_len)
|
||||
text.resize(max_len);
|
||||
|
||||
painter.draw_string(
|
||||
rect.location(),
|
||||
s,
|
||||
|
||||
@@ -99,4 +99,10 @@ TEST_CASE("It should convert 8-bit.") {
|
||||
CHECK_EQ(val, 123);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert negative.") {
|
||||
int8_t val = 0;
|
||||
REQUIRE(parse_int("-64", val));
|
||||
CHECK_EQ(val, -64);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
@@ -24,12 +24,27 @@
|
||||
|
||||
/* TODO: Tests for all string_format functions. */
|
||||
|
||||
TEST_CASE("to_string_dec_uint64 returns correct value.") {
|
||||
CHECK_EQ(to_string_dec_uint64(0), "0");
|
||||
CHECK_EQ(to_string_dec_uint64(1), "1");
|
||||
CHECK_EQ(to_string_dec_uint64(1'000'000), "1000000");
|
||||
CHECK_EQ(to_string_dec_uint64(1'234'567'890), "1234567890");
|
||||
CHECK_EQ(to_string_dec_uint64(1'234'567'891), "1234567891");
|
||||
TEST_CASE("to_string_dec_int returns correct value.") {
|
||||
CHECK_EQ(to_string_dec_int(0), "0");
|
||||
CHECK_EQ(to_string_dec_int(1), "1");
|
||||
CHECK_EQ(to_string_dec_int(-1), "-1");
|
||||
CHECK_EQ(to_string_dec_int(1'000'000), "1000000");
|
||||
CHECK_EQ(to_string_dec_int(-1'000'000), "-1000000");
|
||||
CHECK_EQ(to_string_dec_int(1'234'567'890), "1234567890");
|
||||
CHECK_EQ(to_string_dec_int(-1'234'567'890), "-1234567890");
|
||||
CHECK_EQ(to_string_dec_int(1'234'567'891), "1234567891");
|
||||
CHECK_EQ(to_string_dec_int(-1'234'567'891), "-1234567891");
|
||||
CHECK_EQ(to_string_dec_int(9'876'543'210), "9876543210");
|
||||
CHECK_EQ(to_string_dec_int(-9'876'543'210), "-9876543210");
|
||||
}
|
||||
|
||||
TEST_CASE("to_string_dec_uint returns correct value.") {
|
||||
CHECK_EQ(to_string_dec_uint(0), "0");
|
||||
CHECK_EQ(to_string_dec_uint(1), "1");
|
||||
CHECK_EQ(to_string_dec_uint(1'000'000), "1000000");
|
||||
CHECK_EQ(to_string_dec_uint(1'234'567'890), "1234567890");
|
||||
CHECK_EQ(to_string_dec_uint(1'234'567'891), "1234567891");
|
||||
CHECK_EQ(to_string_dec_uint(9'876'543'210), "9876543210");
|
||||
}
|
||||
|
||||
TEST_CASE("to_string_freq returns correct value.") {
|
||||
|
||||
Reference in New Issue
Block a user