mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-22 07:29:03 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2279e297c | |||
| cdd524b9f3 | |||
| 99809c7919 | |||
| 2390d79111 | |||
| 3b41d73ddd | |||
| 3d46c28911 | |||
| ca7840557b | |||
| ceaa115025 | |||
| 8dbcefbc75 | |||
| b28b96fb4a | |||
| e15a8ed2d8 | |||
| 19b77bf03b | |||
| 7b1541d2e8 | |||
| 830fea63ed | |||
| 3d59de55f8 | |||
| 5c1f5a7d2e | |||
| 4e985420d4 | |||
| 3e584a9652 | |||
| 39acaf846a | |||
| 76948cffa8 | |||
| 4ea8abb53b | |||
| 32085f317b | |||
| faa4367295 | |||
| 3f8a4957af |
@@ -1 +1 @@
|
||||
v1.7.1
|
||||
v1.7.2
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.7.2
|
||||
v1.7.3
|
||||
|
||||
@@ -287,7 +287,7 @@ set(CPPSRC
|
||||
apps/ert_app.cpp
|
||||
apps/lge_app.cpp
|
||||
apps/pocsag_app.cpp
|
||||
apps/replay_app.cpp
|
||||
# apps/replay_app.cpp
|
||||
apps/ui_playlist.cpp
|
||||
apps/gps_sim_app.cpp
|
||||
apps/soundboard_app.cpp
|
||||
|
||||
@@ -404,22 +404,9 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
}
|
||||
|
||||
void AnalogAudioView::handle_coded_squelch(uint32_t value) {
|
||||
float diff, min_diff = value;
|
||||
size_t min_idx{0};
|
||||
size_t c;
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("CTCSS " + tone_keys[min_idx].first);
|
||||
tone_index idx = tone_key_index_by_value(value);
|
||||
if (idx >= 0)
|
||||
text_ctcss.set("CTCSS " + tone_key_string(idx));
|
||||
else
|
||||
text_ctcss.set("???");
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ class AnalogAudioView : public View {
|
||||
4096,
|
||||
4};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{true};
|
||||
spectrum::WaterfallView waterfall{true};
|
||||
|
||||
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
|
||||
void on_modulation_changed(ReceiverModel::Mode modulation);
|
||||
|
||||
@@ -60,41 +60,8 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card */
|
||||
waterfall.on_hide();
|
||||
|
||||
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options . */
|
||||
switch (sampling_rate) { // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem.
|
||||
|
||||
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max ) fs = 8 x 250 kHz
|
||||
anti_alias_baseband_bandwidth_filter = 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
break;
|
||||
|
||||
case 4000000 ... 6000000: // BW capture range (500k ... 750kHz max ) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k ... 750kHz , ex. 500kHz (fs = 8*BW = 4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW 750 kHz (fs = 6Mhz)
|
||||
anti_alias_baseband_bandwidth_filter = 2500000; // in some IC MAX2837 appear 2250000 , but both works similar.
|
||||
break;
|
||||
|
||||
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz . (1Mhz showed slightly higher noise background).
|
||||
anti_alias_baseband_bandwidth_filter = 3500000;
|
||||
break;
|
||||
|
||||
case 14000000: // BW capture 1,75Mhz , fs = 8 x 1,75Mhz = 14Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 5000000;
|
||||
break;
|
||||
|
||||
case 16000000: // BW capture 2Mhz , fs = 8 x 2Mhz = 16Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 6000000;
|
||||
break;
|
||||
|
||||
case 20000000: // BW capture 2,5Mhz , fs= 8 x 2,5 Mhz = 20Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 7000000;
|
||||
break;
|
||||
|
||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz= 22Mhz max ADC sampling) and others.
|
||||
// We tested also 9Mhz FPB stightly too much noise floor, better 8Mhz
|
||||
anti_alias_baseband_bandwidth_filter = 8000000;
|
||||
}
|
||||
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options. */
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
|
||||
record_view.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
|
||||
@@ -56,7 +56,6 @@ class CaptureAppView : public View {
|
||||
app_settings::Options::UseGlobalTargetFrequency};
|
||||
|
||||
uint32_t sampling_rate = 0;
|
||||
uint32_t anti_alias_baseband_bandwidth_filter = 2500000;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
|
||||
@@ -97,7 +96,7 @@ class CaptureAppView : public View {
|
||||
16384,
|
||||
3};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -124,7 +124,7 @@ class GpsSimAppView : public View {
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
|
||||
@@ -121,7 +121,7 @@ class ReplayAppView : public View {
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
|
||||
@@ -32,7 +32,7 @@ class SpectrumAnalysisModel {
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
|
||||
class SpectrumAnalysisView : public spectrum::WaterfallView {
|
||||
public:
|
||||
private:
|
||||
SpectrumAnalysisModel model;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "ui_dfu_menu.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "performance_counter.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -80,4 +81,57 @@ void DfuMenu::paint(Painter& painter) {
|
||||
ui::Color::dark_cyan());
|
||||
}
|
||||
|
||||
DfuMenu2::DfuMenu2(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&text_head,
|
||||
&labels,
|
||||
&text_info_line_1,
|
||||
&text_info_line_2,
|
||||
&text_info_line_3,
|
||||
&text_info_line_4,
|
||||
&text_info_line_5,
|
||||
&text_info_line_6,
|
||||
&text_info_line_7,
|
||||
&text_info_line_8});
|
||||
}
|
||||
|
||||
void DfuMenu2::paint(Painter& painter) {
|
||||
text_info_line_1.set(to_string_dec_uint(portapack::receiver_model.target_frequency(), 10));
|
||||
text_info_line_2.set(to_string_dec_uint(portapack::receiver_model.baseband_bandwidth(), 10));
|
||||
text_info_line_3.set(to_string_dec_uint(portapack::receiver_model.sampling_rate(), 10));
|
||||
text_info_line_4.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
|
||||
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
|
||||
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
|
||||
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
|
||||
text_info_line_8.set("");
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 8 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{19 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::black());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{24 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
|
||||
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
|
||||
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||
ui::Color::dark_cyan());
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -67,6 +67,38 @@ class DfuMenu : public View {
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
class DfuMenu2 : public View {
|
||||
public:
|
||||
DfuMenu2(NavigationView& nav);
|
||||
~DfuMenu2() = default;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 8 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Receiver"};
|
||||
|
||||
Labels labels{
|
||||
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "Tgt freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "Bandwidt:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "Sampl Rt:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_3{{14 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_4{{14 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_5{{14 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_6{{14 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{14 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_DFU_MENU_H__*/
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_playlist.hpp"
|
||||
#include "ui_ss_viewer.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "string_format.hpp"
|
||||
@@ -35,6 +36,13 @@
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
static const fs::path txt_ext{u".TXT"};
|
||||
static const fs::path ppl_ext{u".PPL"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path png_ext{u".PNG"};
|
||||
} // namespace ui
|
||||
|
||||
namespace {
|
||||
using namespace ui;
|
||||
|
||||
@@ -47,25 +55,6 @@ std::string truncate(const fs::path& path, size_t max_length) {
|
||||
return ::truncate(path.string(), max_length);
|
||||
}
|
||||
|
||||
// Case insensitive path equality on underlying "native" string.
|
||||
bool iequal(
|
||||
const fs::path& lhs,
|
||||
const fs::path& rhs) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inserts the entry into the entry list sorted directories first then by file name.
|
||||
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
auto it = std::lower_bound(
|
||||
@@ -86,15 +75,12 @@ void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
fs::path get_partner_file(fs::path path) {
|
||||
if (fs::is_directory(path))
|
||||
return {};
|
||||
|
||||
const fs::path txt_path{u".TXT"};
|
||||
const fs::path c16_path{u".C16"};
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(ext, txt_path))
|
||||
ext = c16_path;
|
||||
else if (iequal(ext, c16_path))
|
||||
ext = txt_path;
|
||||
if (path_iequal(ext, txt_ext))
|
||||
ext = c16_ext;
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
ext = txt_ext;
|
||||
else
|
||||
return {};
|
||||
|
||||
@@ -163,7 +149,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
continue;
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || iequal(entry.path().extension(), extension_filter))
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter))
|
||||
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path(), 0, true});
|
||||
@@ -301,7 +287,7 @@ const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
size_t index = 0;
|
||||
|
||||
for (; index < file_types.size() - 1; ++index)
|
||||
if (iequal(ext, file_types[index].extension))
|
||||
if (path_iequal(ext, file_types[index].extension))
|
||||
return file_types[index];
|
||||
|
||||
// Default to last entry in the list.
|
||||
@@ -507,10 +493,14 @@ bool FileManagerView::handle_file_open() {
|
||||
auto path = get_selected_full_path();
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(u".TXT", ext) || iequal(u".PPL", ext)) {
|
||||
if (path_iequal(txt_ext, ext)) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
return true;
|
||||
} else if (iequal(u".PNG", ext)) {
|
||||
} else if (path_iequal(c16_ext, ext) || path_iequal(ppl_ext, ext)) {
|
||||
// TODO: Enough memory to push?
|
||||
nav_.push<PlaylistView>(path);
|
||||
return true;
|
||||
} else if (path_iequal(png_ext, ext)) {
|
||||
nav_.push<ScreenshotViewer>(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const char16_t* firmware_folder = u"/FIRMWARE";
|
||||
|
||||
Thread* FlashUtilityView::thread{nullptr};
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
|
||||
@@ -35,7 +37,9 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(u"FIRMWARE", u"*.bin")) {
|
||||
ensure_directory(firmware_folder);
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.bin")) {
|
||||
auto filename = entry.path().filename();
|
||||
auto path = entry.path().native();
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class FlashUtilityView : public View {
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "FlashUtility"; }; // Removed the space because the title and speaker icon overlapped.
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -205,34 +205,7 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
||||
uint32_t anti_alias_baseband_bandwidth_filter = 2500000;
|
||||
switch (sampling_rate) { // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem.
|
||||
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max ) fs = 8 x 250 kHz
|
||||
anti_alias_baseband_bandwidth_filter = 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
break;
|
||||
case 4000000 ... 6000000: // BW capture range (500k ... 750kHz max ) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k ... 750kHz , ex. 500kHz (fs = 8*BW = 4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW 750 kHz (fs = 6Mhz)
|
||||
anti_alias_baseband_bandwidth_filter = 2500000; // in some IC MAX2837 appear 2250000 , but both works similar.
|
||||
break;
|
||||
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz . (1Mhz showed slightly higher noise background).
|
||||
anti_alias_baseband_bandwidth_filter = 3500000;
|
||||
break;
|
||||
case 14000000: // BW capture 1,75Mhz , fs = 8 x 1,75Mhz = 14Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 5000000;
|
||||
break;
|
||||
case 16000000: // BW capture 2Mhz , fs = 8 x 2Mhz = 16Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 6000000;
|
||||
break;
|
||||
case 20000000: // BW capture 2,5Mhz , fs= 8 x 2,5 Mhz = 20Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 7000000;
|
||||
break;
|
||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz= 22Mhz max ADC sampling) and others.
|
||||
// We tested also 9Mhz FPB stightly too much noise floor, better 8Mhz
|
||||
anti_alias_baseband_bandwidth_filter = 8000000;
|
||||
}
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
};
|
||||
@@ -243,34 +216,23 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
|
||||
}
|
||||
|
||||
void LevelView::handle_coded_squelch(const uint32_t value) {
|
||||
static int32_t last_idx = -1;
|
||||
static tone_index last_squelch_index = -1;
|
||||
|
||||
float diff, min_diff = value;
|
||||
size_t min_idx{0};
|
||||
size_t c;
|
||||
if (field_mode.selected_index() == NFM_MODULATION) {
|
||||
tone_index idx = tone_key_index_by_value(value);
|
||||
|
||||
if (field_mode.selected_index() != NFM_MODULATION) {
|
||||
text_ctcss.set(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
if ((last_squelch_index < 0) || (last_squelch_index != idx)) {
|
||||
last_squelch_index = idx;
|
||||
if (idx >= 0) {
|
||||
text_ctcss.set("T: " + tone_key_string(idx));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (last_idx < 0 || (unsigned)last_idx != min_idx) {
|
||||
last_idx = min_idx;
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("T: " + tone_keys[min_idx].first);
|
||||
else
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -91,7 +91,7 @@ class LevelView : public View {
|
||||
|
||||
OptionsField field_mode{
|
||||
{15 * 8, 1 * 16},
|
||||
3,
|
||||
4,
|
||||
{}};
|
||||
|
||||
OptionsField step_mode{
|
||||
|
||||
@@ -63,7 +63,7 @@ class GlassView : public View {
|
||||
GlassView& operator=(const GlassView& nav);
|
||||
|
||||
~GlassView();
|
||||
std::string title() const override { return "LookingGlass"; };
|
||||
std::string title() const override { return "Looking Glass"; };
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
@@ -40,8 +40,17 @@
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/* TODO
|
||||
* - Should frequency overrides be saved in the playlist?
|
||||
* - Start playing from current track (vs start over)?
|
||||
*/
|
||||
|
||||
namespace ui {
|
||||
|
||||
// TODO: consolidate extesions into a shared header?
|
||||
static const fs::path c16_ext = u".C16";
|
||||
static const fs::path ppl_ext = u".PPL";
|
||||
|
||||
void PlaylistView::load_file(const fs::path& playlist_path) {
|
||||
File playlist_file;
|
||||
auto error = playlist_file.open(playlist_path.string());
|
||||
@@ -79,11 +88,9 @@ Optional<PlaylistView::playlist_entry> PlaylistView::load_entry(fs::path&& path)
|
||||
auto metadata_path = get_metadata_path(path);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
|
||||
// TODO: For now, require a metadata file. Eventually,
|
||||
// allow a user-defined center_freq like there was in Replay.
|
||||
// metadata = {0, 500'000};
|
||||
// If no metadata found, fallback to the TX frequency.
|
||||
if (!metadata)
|
||||
return {};
|
||||
metadata = {transmitter_model.target_frequency(), 500'000};
|
||||
|
||||
return playlist_entry{
|
||||
std::move(path),
|
||||
@@ -101,7 +108,11 @@ void PlaylistView::on_file_changed(const fs::path& new_file_path) {
|
||||
load_file(playlist_path_);
|
||||
|
||||
update_ui();
|
||||
button_play.focus();
|
||||
|
||||
// TODO: fix in UI framework with 'try_focus()'?
|
||||
// Hack around focus getting called by ctor before parent is set.
|
||||
if (parent())
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void PlaylistView::open_file(bool prompt_save) {
|
||||
@@ -117,6 +128,7 @@ void PlaylistView::open_file(bool prompt_save) {
|
||||
}
|
||||
|
||||
auto open_view = nav_.push<FileLoadView>(".PPL");
|
||||
open_view->push_dir(u"PLAYLIST");
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
@@ -126,6 +138,8 @@ void PlaylistView::save_file(bool show_dialogs) {
|
||||
if (!playlist_dirty_ || playlist_path_.empty())
|
||||
return;
|
||||
|
||||
ensure_directory(playlist_path_.parent_path());
|
||||
|
||||
File playlist_file;
|
||||
auto error = playlist_file.create(playlist_path_.string());
|
||||
|
||||
@@ -154,7 +168,10 @@ void PlaylistView::save_file(bool show_dialogs) {
|
||||
void PlaylistView::add_entry(fs::path&& path) {
|
||||
if (playlist_path_.empty()) {
|
||||
playlist_path_ = next_filename_matching_pattern(u"/PLAYLIST/PLAY_????.PPL");
|
||||
button_play.focus();
|
||||
|
||||
// Hack around focus getting called by ctor before parent is set.
|
||||
if (parent())
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
auto entry = load_entry(std::move(path));
|
||||
@@ -185,7 +202,7 @@ void PlaylistView::show_file_error(const fs::path& path, const std::string& mess
|
||||
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
|
||||
}
|
||||
|
||||
const PlaylistView::playlist_entry* PlaylistView::current() const {
|
||||
PlaylistView::playlist_entry* PlaylistView::current() {
|
||||
return playlist_db_.empty() ? nullptr : &playlist_db_[current_index_];
|
||||
}
|
||||
|
||||
@@ -248,7 +265,7 @@ void PlaylistView::send_current_track() {
|
||||
return;
|
||||
}
|
||||
|
||||
// ReplayThread starts immediately on contruction so
|
||||
// ReplayThread starts immediately on construction so
|
||||
// these need to be set before creating the ReplayThread.
|
||||
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
|
||||
transmitter_model.set_sampling_rate(current()->metadata.sample_rate * 8);
|
||||
@@ -289,10 +306,9 @@ void PlaylistView::stop() {
|
||||
|
||||
void PlaylistView::update_ui() {
|
||||
if (playlist_db_.empty()) {
|
||||
text_filename.set("");
|
||||
text_filename.set("-");
|
||||
text_sample_rate.set("-");
|
||||
text_duration.set("-");
|
||||
text_frequency.set("-");
|
||||
|
||||
if (playlist_path_.empty())
|
||||
text_track.set("Open playlist or add capture.");
|
||||
@@ -311,7 +327,7 @@ void PlaylistView::update_ui() {
|
||||
|
||||
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, 4);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
text_frequency.set(to_string_short_freq(current()->metadata.center_frequency));
|
||||
field_frequency.set_value(current()->metadata.center_frequency);
|
||||
|
||||
text_track.set(
|
||||
to_string_dec_uint(current_index_ + 1) + "/" +
|
||||
@@ -355,7 +371,7 @@ PlaylistView::PlaylistView(
|
||||
&text_duration,
|
||||
&progressbar_track,
|
||||
&progressbar_transmit,
|
||||
&text_frequency,
|
||||
&field_frequency,
|
||||
&tx_view,
|
||||
&check_loop,
|
||||
&button_play,
|
||||
@@ -371,30 +387,53 @@ PlaylistView::PlaylistView(
|
||||
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
|
||||
field_frequency.set_value(100'000'000);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
if (current())
|
||||
current()->metadata.center_frequency = f;
|
||||
};
|
||||
field_frequency.on_edit = [this]() {
|
||||
auto freq_view = nav_.push<FrequencyKeypadView>(field_frequency.value());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
toggle();
|
||||
};
|
||||
|
||||
button_add.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
return;
|
||||
auto open_view = nav_.push<FileLoadView>(".C16");
|
||||
open_view->push_dir(u"CAPTURES");
|
||||
open_view->on_changed = [this](fs::path path) {
|
||||
add_entry(std::move(path));
|
||||
};
|
||||
};
|
||||
|
||||
button_delete.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
return;
|
||||
delete_entry();
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
stop();
|
||||
open_file();
|
||||
};
|
||||
|
||||
button_save.on_select = [this]() {
|
||||
if (is_active())
|
||||
stop();
|
||||
save_file();
|
||||
};
|
||||
|
||||
button_prev.on_select = [this]() {
|
||||
if (is_active())
|
||||
return;
|
||||
--current_index_;
|
||||
if (at_end())
|
||||
current_index_ = playlist_db_.size() - 1;
|
||||
@@ -402,6 +441,8 @@ PlaylistView::PlaylistView(
|
||||
};
|
||||
|
||||
button_next.on_select = [this]() {
|
||||
if (is_active())
|
||||
return;
|
||||
++current_index_;
|
||||
if (at_end())
|
||||
current_index_ = 0;
|
||||
@@ -411,6 +452,17 @@ PlaylistView::PlaylistView(
|
||||
update_ui();
|
||||
}
|
||||
|
||||
PlaylistView::PlaylistView(
|
||||
NavigationView& nav,
|
||||
const fs::path& path)
|
||||
: PlaylistView(nav) {
|
||||
auto ext = path.extension();
|
||||
if (path_iequal(ext, ppl_ext))
|
||||
on_file_changed(path);
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
add_entry(fs::path{path});
|
||||
}
|
||||
|
||||
PlaylistView::~PlaylistView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
@@ -425,14 +477,17 @@ void PlaylistView::set_parent_rect(Rect new_parent_rect) {
|
||||
waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
void PlaylistView::focus() {
|
||||
if (playlist_path_.empty())
|
||||
button_add.focus();
|
||||
else
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void PlaylistView::on_hide() {
|
||||
stop();
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
void PlaylistView::focus() {
|
||||
button_open.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -45,13 +45,15 @@ namespace ui {
|
||||
class PlaylistView : public View {
|
||||
public:
|
||||
PlaylistView(NavigationView& nav);
|
||||
PlaylistView(NavigationView& nav, const std::filesystem::path& path);
|
||||
~PlaylistView();
|
||||
|
||||
// Following 2 called by 'NavigationView::update_view' after view is created.
|
||||
void set_parent_rect(Rect new_parent_rect) override;
|
||||
void on_hide() override;
|
||||
void focus() override;
|
||||
void on_hide() override;
|
||||
|
||||
std::string title() const override { return "Playlist"; };
|
||||
std::string title() const override { return "Replay"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
@@ -90,7 +92,7 @@ class PlaylistView : public View {
|
||||
const std::filesystem::path& path,
|
||||
const std::string& message);
|
||||
|
||||
const playlist_entry* current() const;
|
||||
playlist_entry* current();
|
||||
|
||||
bool is_active() const;
|
||||
bool at_end() const;
|
||||
@@ -110,23 +112,28 @@ class PlaylistView : public View {
|
||||
Text text_filename{
|
||||
{0 * 8, 0 * 16, 30 * 8, 16}};
|
||||
|
||||
// TODO: delay duration field.
|
||||
// TODO: TxFrequencyField to edit entry frequency.
|
||||
Text text_frequency{
|
||||
{0 * 8, 1 * 16, 9 * 8, 16}};
|
||||
FrequencyField field_frequency{
|
||||
{0 * 8, 1 * 16}};
|
||||
|
||||
Text text_sample_rate{
|
||||
{10 * 8, 1 * 16, 7 * 8, 16}};
|
||||
|
||||
/*v making there's 1px line (instead of two) between two progress bars,
|
||||
* by letting 1px overlapped.
|
||||
* So, since they overlapped 1px, they are visually same, and looks better.
|
||||
*/
|
||||
|
||||
ProgressBar progressbar_track{
|
||||
{18 * 8, 1 * 16, 12 * 8, 8}};
|
||||
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
|
||||
|
||||
ProgressBar progressbar_transmit{
|
||||
{18 * 8, 3 * 8, 12 * 8, 8}};
|
||||
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
|
||||
|
||||
Text text_duration{
|
||||
{0 * 8, 2 * 16, 5 * 8, 16}};
|
||||
|
||||
// TODO: delay duration field.
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
9 * 8, 1 * 8, SHORT_UI};
|
||||
|
||||
@@ -146,42 +153,42 @@ class PlaylistView : public View {
|
||||
{0 * 8, 3 * 16, 30 * 8, 16}};
|
||||
|
||||
NewButton button_prev{
|
||||
{0 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_arrow_left,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_add{
|
||||
NewButton button_next{
|
||||
{6 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_arrow_right,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_add{
|
||||
{11 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_new_file,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_delete{
|
||||
{10 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
{15 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_delete,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_open{
|
||||
{16 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_load,
|
||||
Color::dark_blue()};
|
||||
|
||||
NewButton button_save{
|
||||
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
{24 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_save,
|
||||
Color::dark_blue()};
|
||||
|
||||
NewButton button_next{
|
||||
{26 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_arrow_right,
|
||||
Color::dark_grey()};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
|
||||
@@ -30,8 +30,6 @@ using portapack::memory::map::backup_ram;
|
||||
|
||||
namespace ui {
|
||||
|
||||
static RecordView* record_view = NULL;
|
||||
|
||||
void ReconView::set_loop_config(bool v) {
|
||||
continuous = v;
|
||||
button_loop_config.set_style(v ? &Styles::green : &Styles::white);
|
||||
@@ -365,7 +363,6 @@ void ReconView::focus() {
|
||||
|
||||
ReconView::~ReconView() {
|
||||
recon_stop_recording();
|
||||
delete record_view;
|
||||
recon_save_config_to_sd();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
@@ -377,7 +374,9 @@ ReconView::~ReconView() {
|
||||
ReconView::ReconView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
chrono_start = chTimeNow();
|
||||
record_view = new RecordView({0, 0, 30 * 8, 1 * 16}, u"AUTO_AUDIO_", u"AUDIO", RecordView::FileType::WAV, 4096, 4);
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO_", u"AUDIO",
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
add_children({&labels,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
@@ -414,7 +413,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
&button_restart,
|
||||
&button_mic_app,
|
||||
&button_remove,
|
||||
record_view});
|
||||
record_view.get()});
|
||||
|
||||
record_view->hidden(true);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
@@ -1339,22 +1338,21 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
recon_stop_recording();
|
||||
if (new_mod != SPEC_MODULATION) {
|
||||
remove_children({record_view});
|
||||
delete record_view;
|
||||
record_view = new RecordView({0, 0, 30 * 8, 1 * 16}, u"AUTO_AUDIO_", u"AUDIO", RecordView::FileType::WAV, 4096, 4);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
add_children({record_view});
|
||||
}
|
||||
remove_child(record_view.get());
|
||||
record_view.reset();
|
||||
if (new_mod == SPEC_MODULATION) {
|
||||
audio::output::stop();
|
||||
remove_children({record_view});
|
||||
delete record_view;
|
||||
record_view = new RecordView({0, 0, 30 * 8, 1 * 16}, u"AUTO_RAW_", u"CAPTURES", RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
add_children({record_view});
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_RAW_", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO_", u"AUDIO",
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
}
|
||||
add_child(record_view.get());
|
||||
record_view->hidden(true);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
record_view->on_error = [this](std::string message) {
|
||||
nav_.display_modal("Error", message);
|
||||
};
|
||||
@@ -1401,34 +1399,7 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
||||
uint32_t anti_alias_baseband_bandwidth_filter = 2500000;
|
||||
switch (sampling_rate) { // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem.
|
||||
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max ) fs = 8 x 250 kHz
|
||||
anti_alias_baseband_bandwidth_filter = 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
break;
|
||||
case 4000000 ... 6000000: // BW capture range (500k ... 750kHz max ) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k ... 750kHz , ex. 500kHz (fs = 8*BW = 4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW 750 kHz (fs = 6Mhz)
|
||||
anti_alias_baseband_bandwidth_filter = 2500000; // in some IC MAX2837 appear 2250000 , but both works similar.
|
||||
break;
|
||||
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz . (1Mhz showed slightly higher noise background).
|
||||
anti_alias_baseband_bandwidth_filter = 3500000;
|
||||
break;
|
||||
case 14000000: // BW capture 1,75Mhz , fs = 8 x 1,75Mhz = 14Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 5000000;
|
||||
break;
|
||||
case 16000000: // BW capture 2Mhz , fs = 8 x 2Mhz = 16Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 6000000;
|
||||
break;
|
||||
case 20000000: // BW capture 2,5Mhz , fs= 8 x 2,5 Mhz = 20Mhz
|
||||
// good BPF, good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 7000000;
|
||||
break;
|
||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz= 22Mhz max ADC sampling) and others.
|
||||
// We tested also 9Mhz FPB stightly too much noise floor, better 8Mhz
|
||||
anti_alias_baseband_bandwidth_filter = 8000000;
|
||||
}
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
record_view->set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
@@ -1440,14 +1411,12 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
}
|
||||
if (new_mod != SPEC_MODULATION)
|
||||
record_view->set_sampling_rate(recording_sampling_rate);
|
||||
|
||||
field_mode.set_selected_index(new_mod);
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v != -1) {
|
||||
change_mode(v);
|
||||
}
|
||||
};
|
||||
|
||||
// for some motive, audio output gets stopped.
|
||||
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::start(); // so if recon was stopped we resume audio
|
||||
@@ -1457,32 +1426,20 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
}
|
||||
|
||||
void ReconView::handle_coded_squelch(const uint32_t value) {
|
||||
float diff{0.0};
|
||||
float min_diff{(float)value};
|
||||
size_t min_idx{0};
|
||||
size_t c{0};
|
||||
if (field_mode.selected_index() == NFM_MODULATION) {
|
||||
tone_index idx = tone_key_index_by_value(value);
|
||||
|
||||
if (field_mode.selected_index() != NFM_MODULATION) {
|
||||
text_ctcss.set(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
if ((last_squelch_index < 0) || (last_squelch_index != idx)) {
|
||||
last_squelch_index = idx;
|
||||
if (idx >= 0) {
|
||||
text_ctcss.set("T: " + tone_key_string(idx));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (last_squelch_index < 0 || (unsigned)last_squelch_index != min_idx) {
|
||||
last_squelch_index = min_idx;
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("T: " + tone_keys[min_idx].first);
|
||||
else
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -139,12 +139,14 @@ class ReconView : public View {
|
||||
int8_t last_rssi_med{-127};
|
||||
int8_t last_rssi_max{-127};
|
||||
int32_t last_index{-1};
|
||||
int32_t last_squelch_index{-1};
|
||||
tone_index last_squelch_index{-1};
|
||||
int64_t last_freq{0};
|
||||
std::string freq_file_path{};
|
||||
systime_t chrono_start{};
|
||||
systime_t chrono_end{};
|
||||
|
||||
std::unique_ptr<RecordView> record_view{};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
|
||||
{{3 * 8, 8 * 16}, "START END", Color::light_grey()},
|
||||
|
||||
@@ -259,11 +259,16 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
&toggle_converter,
|
||||
&toggle_bias_tee,
|
||||
&toggle_clock,
|
||||
&toggle_speaker,
|
||||
&toggle_mute,
|
||||
&toggle_sd_card,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
// Display "Disable speaker" option only if AK4951 Codec which has separate speaker/headphone control
|
||||
if (audio::speaker_disable_supported()) {
|
||||
add_child(&toggle_speaker);
|
||||
}
|
||||
|
||||
checkbox_disable_touchscreen.set_value(pmem::disable_touchscreen());
|
||||
checkbox_showsplash.set_value(pmem::config_splash());
|
||||
checkbox_showclock.set_value(!pmem::hide_clock());
|
||||
@@ -287,6 +292,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
toggle_bias_tee.set_value(!pmem::ui_hide_bias_tee());
|
||||
toggle_clock.set_value(!pmem::ui_hide_clock());
|
||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
@@ -312,6 +318,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
pmem::set_ui_hide_bias_tee(!toggle_bias_tee.value());
|
||||
pmem::set_ui_hide_clock(!toggle_clock.value());
|
||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||
send_system_refresh();
|
||||
|
||||
@@ -549,16 +556,13 @@ void SetPersistentMemoryView::focus() {
|
||||
SetAudioView::SetAudioView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_tone_mix,
|
||||
&checkbox_speaker_disable,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
field_tone_mix.set_value(pmem::tone_mix());
|
||||
checkbox_speaker_disable.set_value(pmem::config_speaker_disable());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_tone_mix(field_tone_mix.value());
|
||||
pmem::set_config_speaker_disable(checkbox_speaker_disable.value());
|
||||
audio::output::update_audio_mute();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -280,12 +280,16 @@ class SetUIView : public View {
|
||||
{17 * 8, 14 * 16 + 2, 8, 16},
|
||||
&bitmap_icon_clk_ext};
|
||||
|
||||
ImageToggle toggle_speaker{
|
||||
ImageToggle toggle_mute{
|
||||
{18 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_and_headphones_mute};
|
||||
|
||||
ImageToggle toggle_speaker{
|
||||
{20 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_mute};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
{20 * 8, 14 * 16 + 2, 16, 16},
|
||||
{22 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_sd_card_ok};
|
||||
|
||||
Button button_save{
|
||||
@@ -425,11 +429,6 @@ class SetAudioView : public View {
|
||||
1,
|
||||
'0'};
|
||||
|
||||
Checkbox checkbox_speaker_disable{
|
||||
{2 * 8, 6 * 16},
|
||||
25,
|
||||
"Disable AK speaker amp"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
@@ -42,10 +42,9 @@ namespace ui {
|
||||
/* TextViewer *******************************************************/
|
||||
|
||||
TextViewer::TextViewer(Rect parent_rect)
|
||||
: Widget(parent_rect),
|
||||
max_line{static_cast<uint8_t>(parent_rect.height() / char_height)},
|
||||
max_col{static_cast<uint8_t>(parent_rect.width() / char_width)} {
|
||||
: Widget(parent_rect) {
|
||||
set_focusable(true);
|
||||
set_font_zoom(false);
|
||||
}
|
||||
|
||||
void TextViewer::paint(Painter& painter) {
|
||||
@@ -138,6 +137,16 @@ uint32_t TextViewer::offset() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TextViewer::cursor_home() {
|
||||
cursor_.col = 0;
|
||||
redraw();
|
||||
}
|
||||
|
||||
void TextViewer::cursor_end() {
|
||||
cursor_.col = line_length() - 1;
|
||||
redraw();
|
||||
}
|
||||
|
||||
uint16_t TextViewer::line_length() {
|
||||
return file_->line_length(cursor_.line);
|
||||
}
|
||||
@@ -158,18 +167,14 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
|
||||
++new_line;
|
||||
}
|
||||
|
||||
// Snap to first/last to make navigating easier.
|
||||
if (new_line < 0 && new_col > 0) {
|
||||
// Snap to first/last line to make navigating easier.
|
||||
if (new_line < 0 && cursor_.line > 0) {
|
||||
new_line = 0;
|
||||
new_col = 0;
|
||||
} else if (new_line >= (int32_t)file_->line_count()) {
|
||||
auto last_line = file_->line_count() - 1;
|
||||
int32_t last_col = file_->line_length(last_line) - 1;
|
||||
|
||||
if (new_col < last_col) {
|
||||
if (cursor_.line < last_line)
|
||||
new_line = last_line;
|
||||
new_col = last_col;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_line < 0 || (uint32_t)new_line >= file_->line_count())
|
||||
@@ -177,7 +182,6 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
|
||||
|
||||
new_line_length = file_->line_length(new_line);
|
||||
|
||||
// TODO: don't wrap with encoder?
|
||||
// Wrap or clamp column.
|
||||
if (new_line_length == 0)
|
||||
new_col = 0;
|
||||
@@ -207,7 +211,7 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
|
||||
if (result && *result > 0)
|
||||
painter.draw_string(
|
||||
{0, r.top() + (int)i * char_height},
|
||||
Styles::white_small, {buffer, *result});
|
||||
style(), {buffer, *result});
|
||||
|
||||
// Clear empty line sections. This is less visually jarring than full clear.
|
||||
int32_t clear_width = max_col - (result ? *result : 0);
|
||||
@@ -216,7 +220,7 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
|
||||
{(max_col - clear_width) * char_width,
|
||||
r.top() + (int)i * char_height,
|
||||
clear_width * char_width, char_height},
|
||||
Styles::white_small.background);
|
||||
style().background);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,8 +241,8 @@ void TextViewer::paint_cursor(Painter& painter) {
|
||||
};
|
||||
|
||||
// Clear old cursor. CONSIDER: XOR cursor?
|
||||
draw_cursor(paint_state_.line, paint_state_.col, Styles::white_small.background);
|
||||
draw_cursor(cursor_.line, cursor_.col, Styles::white_small.foreground);
|
||||
draw_cursor(paint_state_.line, paint_state_.col, style().background);
|
||||
draw_cursor(cursor_.line, cursor_.col, style().foreground);
|
||||
paint_state_.line = cursor_.line;
|
||||
paint_state_.col = cursor_.col;
|
||||
}
|
||||
@@ -252,6 +256,15 @@ void TextViewer::reset_file(FileWrapper* file) {
|
||||
redraw(true);
|
||||
}
|
||||
|
||||
void TextViewer::set_font_zoom(bool zoom) {
|
||||
font_zoom = zoom;
|
||||
font_style = font_zoom ? &Styles::white : &Styles::white_small;
|
||||
char_height = style().font.line_height();
|
||||
char_width = style().font.char_width();
|
||||
max_line = (uint8_t)(parent_rect().height() / char_height);
|
||||
max_col = (uint8_t)(parent_rect().width() / char_width);
|
||||
}
|
||||
|
||||
/* TextEditorMenu ***************************************************/
|
||||
|
||||
TextEditorMenu::TextEditorMenu()
|
||||
@@ -259,9 +272,9 @@ TextEditorMenu::TextEditorMenu()
|
||||
add_children(
|
||||
{
|
||||
&rect_frame,
|
||||
&button_cut,
|
||||
&button_paste,
|
||||
&button_copy,
|
||||
&button_home,
|
||||
&button_end,
|
||||
&button_zoom,
|
||||
&button_delline,
|
||||
&button_edit,
|
||||
&button_addline,
|
||||
@@ -310,14 +323,20 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
};
|
||||
|
||||
menu.hidden(true);
|
||||
menu.on_cut() = [this]() {
|
||||
show_nyi();
|
||||
menu.on_home() = [this]() {
|
||||
viewer.cursor_home();
|
||||
hide_menu(true);
|
||||
};
|
||||
menu.on_paste() = [this]() {
|
||||
show_nyi();
|
||||
|
||||
menu.on_end() = [this]() {
|
||||
viewer.cursor_end();
|
||||
hide_menu(true);
|
||||
};
|
||||
menu.on_copy() = [this]() {
|
||||
show_nyi();
|
||||
|
||||
menu.on_zoom() = [this]() {
|
||||
viewer.toggle_font_zoom();
|
||||
refresh_ui();
|
||||
hide_menu(true);
|
||||
};
|
||||
|
||||
menu.on_delete_line() = [this]() {
|
||||
@@ -490,10 +509,6 @@ void TextEditorView::show_edit_line() {
|
||||
});
|
||||
}
|
||||
|
||||
void TextEditorView::show_nyi() {
|
||||
nav_.display_modal("Soon...", "Coming soon.");
|
||||
}
|
||||
|
||||
void TextEditorView::show_save_prompt(std::function<void()> continuation) {
|
||||
if (!file_dirty_) {
|
||||
if (continuation)
|
||||
|
||||
@@ -72,15 +72,23 @@ class TextViewer : public Widget {
|
||||
uint32_t col() const { return cursor_.col; }
|
||||
uint32_t offset() const;
|
||||
|
||||
void cursor_home();
|
||||
void cursor_end();
|
||||
|
||||
// Gets the length of the current line.
|
||||
uint16_t line_length();
|
||||
|
||||
private:
|
||||
static constexpr int8_t char_width = 5;
|
||||
static constexpr int8_t char_height = 8;
|
||||
const Style& style() { return *font_style; }
|
||||
void set_font_zoom(bool zoom);
|
||||
void toggle_font_zoom() { set_font_zoom(!font_zoom); };
|
||||
|
||||
const uint8_t max_line = 32;
|
||||
const uint8_t max_col = 48;
|
||||
private:
|
||||
bool font_zoom{};
|
||||
const Style* font_style{};
|
||||
int8_t char_width{};
|
||||
int8_t char_height{};
|
||||
uint8_t max_line{};
|
||||
uint8_t max_col{};
|
||||
|
||||
/* Returns true if the cursor was updated. */
|
||||
bool apply_scrolling_constraints(
|
||||
@@ -120,14 +128,12 @@ class TextEditorMenu : public View {
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
std::function<void()>& on_cut() { return button_cut.on_select; }
|
||||
std::function<void()>& on_paste() { return button_paste.on_select; }
|
||||
std::function<void()>& on_copy() { return button_copy.on_select; }
|
||||
|
||||
std::function<void()>& on_home() { return button_home.on_select; }
|
||||
std::function<void()>& on_end() { return button_end.on_select; }
|
||||
std::function<void()>& on_zoom() { return button_zoom.on_select; }
|
||||
std::function<void()>& on_delete_line() { return button_delline.on_select; }
|
||||
std::function<void()>& on_edit_line() { return button_edit.on_select; }
|
||||
std::function<void()>& on_add_line() { return button_addline.on_select; }
|
||||
|
||||
std::function<void()>& on_open() { return button_open.on_select; }
|
||||
std::function<void()>& on_save() { return button_save.on_select; }
|
||||
std::function<void()>& on_exit() { return button_exit.on_select; }
|
||||
@@ -139,23 +145,23 @@ class TextEditorMenu : public View {
|
||||
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_cut{
|
||||
NewButton button_home{
|
||||
{1 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Cut",
|
||||
&bitmap_icon_cut,
|
||||
"Home",
|
||||
&bitmap_arrow_left,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_paste{
|
||||
NewButton button_end{
|
||||
{8 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Paste",
|
||||
&bitmap_icon_paste,
|
||||
"End",
|
||||
&bitmap_arrow_right,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_copy{
|
||||
NewButton button_zoom{
|
||||
{15 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Copy",
|
||||
&bitmap_icon_copy,
|
||||
Color::dark_grey()};
|
||||
"Zoom",
|
||||
&bitmap_icon_search,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_delline{
|
||||
{1 * 8, 8 * 8, 7 * 8, 7 * 8},
|
||||
@@ -220,7 +226,6 @@ class TextEditorView : public View {
|
||||
void hide_menu(bool hidden = true);
|
||||
void show_file_picker(bool immediate = true);
|
||||
void show_edit_line();
|
||||
void show_nyi();
|
||||
void show_save_prompt(std::function<void()> continuation);
|
||||
|
||||
void prepare_for_write();
|
||||
@@ -258,4 +263,4 @@ class TextEditorView : public View {
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif // __UI_TEXT_EDITOR_H__
|
||||
#endif // __UI_TEXT_EDITOR_H__
|
||||
|
||||
@@ -184,6 +184,9 @@ void speaker_unmute() {
|
||||
|
||||
void update_audio_mute() {
|
||||
cfg_speaker_disable = portapack::persistent_memory::config_speaker_disable();
|
||||
if (cfg_speaker_disable) {
|
||||
audio_codec->speaker_disable();
|
||||
}
|
||||
|
||||
if (portapack::persistent_memory::config_audio_mute())
|
||||
speaker_mute();
|
||||
@@ -269,4 +272,8 @@ void set_rate(const Rate rate) {
|
||||
clock_manager.set_base_audio_clock_divider(toUType(rate));
|
||||
}
|
||||
|
||||
bool speaker_disable_supported() {
|
||||
return audio_codec->speaker_disable_supported();
|
||||
}
|
||||
|
||||
} /* namespace audio */
|
||||
|
||||
@@ -43,6 +43,7 @@ class Codec {
|
||||
|
||||
virtual void speaker_enable() = 0;
|
||||
virtual void speaker_disable() = 0;
|
||||
virtual bool speaker_disable_supported() const = 0;
|
||||
|
||||
virtual void headphone_enable() = 0;
|
||||
virtual void headphone_disable() = 0;
|
||||
@@ -107,6 +108,7 @@ size_t reg_bits();
|
||||
|
||||
void init(audio::Codec* const codec);
|
||||
void shutdown();
|
||||
bool speaker_disable_supported();
|
||||
|
||||
enum class Rate {
|
||||
Hz_12000 = 4,
|
||||
|
||||
@@ -5493,6 +5493,82 @@ static constexpr Bitmap bitmap_arrow_right{
|
||||
{16, 16},
|
||||
bitmap_arrow_right_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_speaker_and_headphones_data[] = {
|
||||
0x20,
|
||||
0x10,
|
||||
0x30,
|
||||
0x20,
|
||||
0x38,
|
||||
0x44,
|
||||
0x3E,
|
||||
0x48,
|
||||
0x3E,
|
||||
0x91,
|
||||
0x3E,
|
||||
0x92,
|
||||
0x38,
|
||||
0x92,
|
||||
0x30,
|
||||
0x92,
|
||||
0x20,
|
||||
0x92,
|
||||
0x00,
|
||||
0x92,
|
||||
0x30,
|
||||
0x91,
|
||||
0x48,
|
||||
0x48,
|
||||
0x84,
|
||||
0x44,
|
||||
0x84,
|
||||
0x20,
|
||||
0x86,
|
||||
0x11,
|
||||
0x86,
|
||||
0x01,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_speaker_and_headphones{
|
||||
{16, 16},
|
||||
bitmap_icon_speaker_and_headphones_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_speaker_and_headphones_mute_data[] = {
|
||||
0x40,
|
||||
0x00,
|
||||
0x60,
|
||||
0x44,
|
||||
0x70,
|
||||
0x6C,
|
||||
0x7C,
|
||||
0x38,
|
||||
0x7C,
|
||||
0x10,
|
||||
0x7C,
|
||||
0x38,
|
||||
0x70,
|
||||
0x6C,
|
||||
0x60,
|
||||
0x44,
|
||||
0x40,
|
||||
0x00,
|
||||
0x00,
|
||||
0x44,
|
||||
0x30,
|
||||
0x6C,
|
||||
0x48,
|
||||
0x38,
|
||||
0x84,
|
||||
0x10,
|
||||
0x84,
|
||||
0x38,
|
||||
0x86,
|
||||
0x6D,
|
||||
0x86,
|
||||
0x45,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_speaker_and_headphones_mute{
|
||||
{16, 16},
|
||||
bitmap_icon_speaker_and_headphones_mute_data};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__BITMAP_HPP__*/
|
||||
|
||||
@@ -488,6 +488,24 @@ path operator/(const path& lhs, const path& rhs) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool path_iequal(
|
||||
const path& lhs,
|
||||
const path& rhs) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
std::filesystem::path path,
|
||||
std::filesystem::path wild)
|
||||
|
||||
@@ -168,6 +168,9 @@ bool operator>(const path& lhs, const path& rhs);
|
||||
path operator+(const path& lhs, const path& rhs);
|
||||
path operator/(const path& lhs, const path& rhs);
|
||||
|
||||
/* Case insensitive path equality on underlying "native" string. */
|
||||
bool path_iequal(const path& lhs, const path& rhs);
|
||||
|
||||
using file_status = BYTE;
|
||||
|
||||
static_assert(sizeof(path::value_type) == 2, "sizeof(std::filesystem::path::value_type) != 2");
|
||||
|
||||
@@ -73,9 +73,9 @@ const tone_key_t tone_keys = {
|
||||
{"34 M3", 218.100},
|
||||
{"35 M4", 225.700},
|
||||
{"49 9Z", 229.100},
|
||||
{"36 --", 233.600},
|
||||
{"37 --", 241.800},
|
||||
{"38 --", 250.300},
|
||||
{"36 M5", 233.600},
|
||||
{"37 M6", 241.800},
|
||||
{"38 M7", 250.300},
|
||||
{"50 0Z", 254.100},
|
||||
{"Axient 28kHz", 28000.0},
|
||||
{"Senn. 32.768k", 32768.0},
|
||||
@@ -113,6 +113,36 @@ std::string tone_key_string(tone_index index) {
|
||||
return tone_keys[index].first;
|
||||
}
|
||||
|
||||
std::string tone_key_string_by_value(uint32_t value) {
|
||||
return tone_key_string(tone_key_index_by_value(value));
|
||||
}
|
||||
|
||||
tone_index tone_key_index_by_value(uint32_t value) {
|
||||
float diff;
|
||||
float min_diff{(float)value};
|
||||
float fvalue{(float)((min_diff + 50.0) / 100.0)};
|
||||
tone_index min_idx{-1};
|
||||
tone_index idx;
|
||||
|
||||
// Find nearest match
|
||||
for (idx = 0; idx < (tone_index)tone_keys.size(); idx++) {
|
||||
diff = abs(fvalue - tone_keys[idx].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = idx;
|
||||
min_diff = diff;
|
||||
} else {
|
||||
// list is sorted in frequency order; if diff is getting larger than we've passed it
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (min_diff < 40.0)
|
||||
return min_idx;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
tone_index tone_key_index_by_string(char* str) {
|
||||
if (!str)
|
||||
return -1;
|
||||
@@ -123,9 +153,4 @@ tone_index tone_key_index_by_string(char* str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* tone_index tone_key_index_by_value( int32_t freq )
|
||||
{
|
||||
return -1 ;
|
||||
} */
|
||||
|
||||
} // namespace tonekey
|
||||
|
||||
@@ -30,7 +30,7 @@ using namespace ui;
|
||||
|
||||
namespace tonekey {
|
||||
|
||||
typedef int16_t tone_index;
|
||||
typedef int32_t tone_index;
|
||||
|
||||
using tone_key_t = std::vector<std::pair<std::string, float>>;
|
||||
|
||||
@@ -40,8 +40,8 @@ void tone_keys_populate(OptionsField& field);
|
||||
float tone_key_frequency(const tone_index index);
|
||||
|
||||
std::string tone_key_string(const tone_index index);
|
||||
tone_index tone_key_index_by_string(char* str);
|
||||
// tone_index tone_key_index_by_value( int32_t freq );
|
||||
std::string tone_key_string_by_value(uint32_t value);
|
||||
tone_index tone_key_index_by_value(uint32_t value);
|
||||
|
||||
} // namespace tonekey
|
||||
|
||||
|
||||
@@ -37,9 +37,13 @@ AlphanumView::AlphanumView(
|
||||
: TextEntryView(nav, str, max_length) {
|
||||
size_t n;
|
||||
|
||||
add_children({&button_mode,
|
||||
&text_raw,
|
||||
&field_raw});
|
||||
add_children({
|
||||
&labels,
|
||||
&field_raw,
|
||||
&text_raw_to_char,
|
||||
&button_delete,
|
||||
&button_mode,
|
||||
});
|
||||
|
||||
const auto button_fn = [this](Button& button) {
|
||||
this->on_button(button);
|
||||
@@ -65,10 +69,19 @@ AlphanumView::AlphanumView(
|
||||
set_mode(mode + 1);
|
||||
};
|
||||
|
||||
button_delete.on_select = [this](Button&) {
|
||||
char_delete();
|
||||
};
|
||||
|
||||
field_raw.set_value('0');
|
||||
field_raw.on_select = [this](NumberField&) {
|
||||
char_add(field_raw.value());
|
||||
};
|
||||
|
||||
// make text_raw_to_char widget display the char value from field_raw
|
||||
field_raw.on_change = [this](auto) {
|
||||
text_raw_to_char.set(std::string{static_cast<char>(field_raw.value())});
|
||||
};
|
||||
}
|
||||
|
||||
void AlphanumView::set_mode(const uint32_t new_mode) {
|
||||
@@ -96,11 +109,7 @@ void AlphanumView::set_mode(const uint32_t new_mode) {
|
||||
|
||||
void AlphanumView::on_button(Button& button) {
|
||||
const auto c = button.text()[0];
|
||||
|
||||
if (c == '<')
|
||||
char_delete();
|
||||
else
|
||||
char_add(c);
|
||||
char_add(c);
|
||||
}
|
||||
|
||||
bool AlphanumView::on_encoder(const EncoderEvent delta) {
|
||||
|
||||
@@ -43,14 +43,14 @@ class AlphanumView : public TextEntryView {
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
|
||||
private:
|
||||
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, .<";
|
||||
const char* const keys_lower = "abcdefghijklmnopqrstuvwxyz, .<";
|
||||
const char* const keys_digit = "0123456789!\"#'()*+-/:;=>?@[\\]<";
|
||||
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, ._";
|
||||
const char* const keys_lower = "abcdefghijklmnopqrstuvwxyz, ._";
|
||||
const char* const keys_digit = "0123456789!\"#'()*+-/:;=<>@[\\]?";
|
||||
|
||||
const std::pair<std::string, const char*> key_sets[3] = {
|
||||
{"Upper", keys_upper},
|
||||
{"Lower", keys_lower},
|
||||
{"Digit", keys_digit}};
|
||||
{"ABC", keys_upper},
|
||||
{"abc", keys_lower},
|
||||
{"123", keys_digit}};
|
||||
|
||||
int16_t focused_button = 0;
|
||||
uint32_t mode = 0; // Uppercase
|
||||
@@ -60,13 +60,10 @@ class AlphanumView : public TextEntryView {
|
||||
|
||||
std::array<Button, 30> buttons{};
|
||||
|
||||
Button button_mode{
|
||||
{21 * 8, 33 * 8, 8 * 8, 32},
|
||||
""};
|
||||
Labels labels{
|
||||
{{1 * 8, 33 * 8}, "Raw:", Color::light_grey()},
|
||||
{{1 * 8, 35 * 8}, "AKA:", Color::light_grey()}};
|
||||
|
||||
Text text_raw{
|
||||
{1 * 8, 33 * 8, 4 * 8, 16},
|
||||
"Raw:"};
|
||||
NumberField field_raw{
|
||||
{5 * 8, 33 * 8},
|
||||
3,
|
||||
@@ -74,9 +71,17 @@ class AlphanumView : public TextEntryView {
|
||||
1,
|
||||
'0'};
|
||||
|
||||
Button button_ok{
|
||||
{10 * 8, 33 * 8, 9 * 8, 32},
|
||||
"OK"};
|
||||
Text text_raw_to_char{
|
||||
{5 * 8, 35 * 8, 4 * 8, 16},
|
||||
"0"};
|
||||
|
||||
Button button_delete{
|
||||
{9 * 8, 33 * 8, 6 * 8, 32},
|
||||
"<DEL"};
|
||||
|
||||
Button button_mode{
|
||||
{16 * 8, 33 * 8, 5 * 8, 32},
|
||||
""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -36,7 +36,7 @@ using namespace portapack;
|
||||
namespace ui {
|
||||
namespace spectrum {
|
||||
|
||||
/* AudioSpectrumView******************************************************/
|
||||
/* AudioSpectrumView ******************************************************/
|
||||
|
||||
AudioSpectrumView::AudioSpectrumView(
|
||||
const Rect parent_rect)
|
||||
@@ -247,28 +247,25 @@ void FrequencyScale::on_tick_second() {
|
||||
_blink = !_blink;
|
||||
}
|
||||
|
||||
/* WaterfallView *********************************************************/
|
||||
/* WaterfallWidget *********************************************************/
|
||||
// TODO: buffer and use "paint" instead of immediate drawing would help with
|
||||
// preventing flicker from drawing. Would use more RAM however.
|
||||
|
||||
void WaterfallView::on_show() {
|
||||
void WaterfallWidget::on_show() {
|
||||
clear();
|
||||
|
||||
const auto screen_r = screen_rect();
|
||||
display.scroll_set_area(screen_r.top(), screen_r.bottom());
|
||||
}
|
||||
|
||||
void WaterfallView::on_hide() {
|
||||
void WaterfallWidget::on_hide() {
|
||||
/* TODO: Clear region to eliminate brief flash of content at un-shifted
|
||||
* position?
|
||||
*/
|
||||
display.scroll_disable();
|
||||
}
|
||||
|
||||
void WaterfallView::paint(Painter& painter) {
|
||||
// Do nothing.
|
||||
(void)painter;
|
||||
}
|
||||
|
||||
void WaterfallView::on_channel_spectrum(
|
||||
void WaterfallWidget::on_channel_spectrum(
|
||||
const ChannelSpectrum& spectrum) {
|
||||
/* TODO: static_assert that message.spectrum.db.size() >= pixel_row.size() */
|
||||
|
||||
@@ -290,16 +287,16 @@ void WaterfallView::on_channel_spectrum(
|
||||
pixel_row);
|
||||
}
|
||||
|
||||
void WaterfallView::clear() {
|
||||
void WaterfallWidget::clear() {
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
Color::black());
|
||||
}
|
||||
|
||||
/* WaterfallWidget *******************************************************/
|
||||
/* WaterfallView *******************************************************/
|
||||
|
||||
WaterfallWidget::WaterfallWidget(const bool cursor) {
|
||||
add_children({&waterfall_view,
|
||||
WaterfallView::WaterfallView(const bool cursor) {
|
||||
add_children({&waterfall_widget,
|
||||
&frequency_scale});
|
||||
|
||||
frequency_scale.set_focusable(cursor);
|
||||
@@ -310,17 +307,17 @@ WaterfallWidget::WaterfallWidget(const bool cursor) {
|
||||
};
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_show() {
|
||||
void WaterfallView::on_show() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_start();
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_hide() {
|
||||
void WaterfallView::on_hide() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_stop();
|
||||
}
|
||||
|
||||
void WaterfallWidget::show_audio_spectrum_view(const bool show) {
|
||||
void WaterfallView::show_audio_spectrum_view(const bool show) {
|
||||
if ((audio_spectrum_view && show) || (!audio_spectrum_view && !show)) return;
|
||||
|
||||
if (show) {
|
||||
@@ -335,18 +332,18 @@ void WaterfallWidget::show_audio_spectrum_view(const bool show) {
|
||||
}
|
||||
}
|
||||
|
||||
void WaterfallWidget::update_widgets_rect() {
|
||||
void WaterfallView::update_widgets_rect() {
|
||||
if (audio_spectrum_view) {
|
||||
frequency_scale.set_parent_rect({0, audio_spectrum_height, screen_rect().width(), scale_height});
|
||||
waterfall_view.set_parent_rect(waterfall_reduced_rect);
|
||||
waterfall_widget.set_parent_rect(waterfall_reduced_rect);
|
||||
} else {
|
||||
frequency_scale.set_parent_rect({0, 0, screen_rect().width(), scale_height});
|
||||
waterfall_view.set_parent_rect(waterfall_normal_rect);
|
||||
waterfall_widget.set_parent_rect(waterfall_normal_rect);
|
||||
}
|
||||
waterfall_view.on_show();
|
||||
waterfall_widget.on_show();
|
||||
}
|
||||
|
||||
void WaterfallWidget::set_parent_rect(const Rect new_parent_rect) {
|
||||
void WaterfallView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
waterfall_normal_rect = {0, scale_height, new_parent_rect.width(), new_parent_rect.height() - scale_height};
|
||||
@@ -355,13 +352,8 @@ void WaterfallWidget::set_parent_rect(const Rect new_parent_rect) {
|
||||
update_widgets_rect();
|
||||
}
|
||||
|
||||
void WaterfallWidget::paint(Painter& painter) {
|
||||
// TODO:
|
||||
(void)painter;
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
waterfall_view.on_channel_spectrum(spectrum);
|
||||
void WaterfallView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
waterfall_widget.on_channel_spectrum(spectrum);
|
||||
sampling_rate = spectrum.sampling_rate;
|
||||
frequency_scale.set_spectrum_sampling_rate(sampling_rate);
|
||||
frequency_scale.set_channel_filter(
|
||||
@@ -370,9 +362,40 @@ void WaterfallWidget::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
spectrum.channel_filter_transition);
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_audio_spectrum() {
|
||||
void WaterfallView::on_audio_spectrum() {
|
||||
audio_spectrum_view->on_audio_spectrum(audio_spectrum_data);
|
||||
}
|
||||
|
||||
} /* namespace spectrum */
|
||||
|
||||
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 ... 2000000: // BW Captured range (0 <= 250kHz max) fs = 8 x 250 kHz.
|
||||
return 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
|
||||
case 4000000 ... 6000000: // BW capture range (500k...750kHz max) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k...750kHz, ex. 500kHz (fs = 8 x BW = 4Mhz), BW 600kHz (fs = 4,8Mhz), BW 750 kHz (fs = 6Mhz).
|
||||
return 2500000; // In some IC, MAX2837 appears as 2250000, but both work similarly.
|
||||
|
||||
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz. (1Mhz showed slightly higher noise background).
|
||||
return 3500000;
|
||||
|
||||
case 14000000: // BW capture 1,75Mhz, fs = 8 x 1,75Mhz = 14Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
return 5000000;
|
||||
|
||||
case 16000000: // BW capture 2Mhz, fs = 8 x 2Mhz = 16Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
return 6000000;
|
||||
|
||||
case 20000000: // BW capture 2,5Mhz, fs = 8 x 2,5 Mhz = 20Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
return 7000000;
|
||||
|
||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz = 22Mhz max ADC sampling and others.
|
||||
// We tested also 9Mhz FPB slightly too much noise floor, better at 8Mhz.
|
||||
return 8000000;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -108,12 +108,11 @@ class FrequencyScale : public Widget {
|
||||
* If the baseband is shutdown or otherwise not running when interacting
|
||||
* with these, they will almost certainly hang the device. */
|
||||
|
||||
class WaterfallView : public Widget {
|
||||
class WaterfallWidget : public Widget {
|
||||
public:
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void paint(Painter&) override {}
|
||||
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
|
||||
@@ -121,16 +120,16 @@ class WaterfallView : public Widget {
|
||||
void clear();
|
||||
};
|
||||
|
||||
class WaterfallWidget : public View {
|
||||
class WaterfallView : public View {
|
||||
public:
|
||||
std::function<void(int32_t offset)> on_select{};
|
||||
|
||||
WaterfallWidget(const bool cursor = false);
|
||||
WaterfallView(const bool cursor = false);
|
||||
|
||||
WaterfallWidget(const WaterfallWidget&) = delete;
|
||||
WaterfallWidget(WaterfallWidget&&) = delete;
|
||||
WaterfallWidget& operator=(const WaterfallWidget&) = delete;
|
||||
WaterfallWidget& operator=(WaterfallWidget&&) = delete;
|
||||
WaterfallView(const WaterfallView&) = delete;
|
||||
WaterfallView(WaterfallView&&) = delete;
|
||||
WaterfallView& operator=(const WaterfallView&) = delete;
|
||||
WaterfallView& operator=(WaterfallView&&) = delete;
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
@@ -139,8 +138,6 @@ class WaterfallWidget : public View {
|
||||
|
||||
void show_audio_spectrum_view(const bool show);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
void update_widgets_rect();
|
||||
|
||||
@@ -148,7 +145,7 @@ class WaterfallWidget : public View {
|
||||
static constexpr Dim audio_spectrum_height = 16 * 2 + 20;
|
||||
static constexpr Dim scale_height = 20;
|
||||
|
||||
WaterfallView waterfall_view{};
|
||||
WaterfallWidget waterfall_widget{};
|
||||
FrequencyScale frequency_scale{};
|
||||
|
||||
ChannelSpectrumFIFO* channel_fifo{nullptr};
|
||||
@@ -195,6 +192,10 @@ class WaterfallWidget : public View {
|
||||
};
|
||||
|
||||
} /* namespace spectrum */
|
||||
|
||||
/* Calculates the best anti_alias_baseband_bandwidth_filter for the given sampling rate. */
|
||||
uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate);
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_SPECTRUM_H__*/
|
||||
|
||||
@@ -50,7 +50,7 @@ class TextEntryView : public View {
|
||||
|
||||
TextField text_input;
|
||||
Button button_ok{
|
||||
{10 * 8, 33 * 8, 9 * 8, 32},
|
||||
{22 * 8, 33 * 8, 7 * 8, 32},
|
||||
"OK"};
|
||||
};
|
||||
|
||||
|
||||
@@ -183,6 +183,10 @@ SystemStatusView::SystemStatusView(
|
||||
this->on_speaker();
|
||||
};
|
||||
|
||||
button_mute.on_select = [this](ImageButton&) {
|
||||
this->on_mute();
|
||||
};
|
||||
|
||||
button_stealth.on_select = [this](ImageButton&) {
|
||||
this->on_stealth();
|
||||
};
|
||||
@@ -218,13 +222,18 @@ void SystemStatusView::refresh() {
|
||||
if (!pmem::ui_hide_converter()) status_icons.add(&button_converter);
|
||||
if (!pmem::ui_hide_bias_tee()) status_icons.add(&button_bias_tee);
|
||||
if (!pmem::ui_hide_clock()) status_icons.add(&button_clock_status);
|
||||
if (!pmem::ui_hide_speaker()) status_icons.add(&button_speaker);
|
||||
if (!pmem::ui_hide_mute()) status_icons.add(&button_mute);
|
||||
|
||||
// Display "Disable speaker" icon only if AK4951 Codec which has separate speaker/headphone control
|
||||
if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker())
|
||||
status_icons.add(&button_speaker);
|
||||
|
||||
if (!pmem::ui_hide_sd_card()) status_icons.add(&sd_card_status_view);
|
||||
status_icons.update_layout();
|
||||
|
||||
// Update icon display (try to keep all in on place).
|
||||
// Speaker
|
||||
if (pmem::config_audio_mute()) {
|
||||
// Speaker Enable/Disable (AK4951 boards only)
|
||||
if (pmem::config_speaker_disable()) {
|
||||
button_speaker.set_foreground(Color::light_grey());
|
||||
button_speaker.set_bitmap(&bitmap_icon_speaker_mute);
|
||||
} else {
|
||||
@@ -232,6 +241,15 @@ void SystemStatusView::refresh() {
|
||||
button_speaker.set_bitmap(&bitmap_icon_speaker);
|
||||
}
|
||||
|
||||
// Audio Mute (both headphones & speaker)
|
||||
if (pmem::config_audio_mute()) {
|
||||
button_mute.set_foreground(Color::light_grey());
|
||||
button_mute.set_bitmap(&bitmap_icon_speaker_and_headphones_mute);
|
||||
} else {
|
||||
button_mute.set_foreground(Color::green());
|
||||
button_mute.set_bitmap(&bitmap_icon_speaker_and_headphones);
|
||||
}
|
||||
|
||||
// Clock status
|
||||
bool external_clk = portapack::clock_manager.get_reference().source == ClockManager::ReferenceSource::External;
|
||||
button_clock_status.set_bitmap(external_clk ? &bitmap_icon_clk_ext : &bitmap_icon_clk_int);
|
||||
@@ -279,7 +297,9 @@ void SystemStatusView::set_title(const std::string new_value) {
|
||||
if (new_value.empty()) {
|
||||
title.set(default_title);
|
||||
} else {
|
||||
title.set(new_value);
|
||||
// Limit length of title string to prevent partial characters if too many StatusView icons
|
||||
size_t max_len = (status_icons.parent_rect().left() - title.parent_rect().left()) / 8;
|
||||
title.set(truncate(new_value, max_len));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +315,12 @@ void SystemStatusView::on_converter() {
|
||||
}
|
||||
|
||||
void SystemStatusView::on_speaker() {
|
||||
pmem::set_config_speaker_disable(!pmem::config_speaker_disable());
|
||||
audio::output::update_audio_mute();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SystemStatusView::on_mute() {
|
||||
pmem::set_config_audio_mute(!pmem::config_audio_mute());
|
||||
audio::output::update_audio_mute();
|
||||
refresh();
|
||||
@@ -558,7 +584,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
{"SSTV", ui::Color::green(), &bitmap_icon_sstv, [&nav]() { nav.push<SSTVTXView>(); }},
|
||||
{"TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav]() { nav.push<LCRView>(); }},
|
||||
{"TouchTune", ui::Color::green(), &bitmap_icon_touchtunes, [&nav]() { nav.push<TouchTunesView>(); }},
|
||||
{"Playlist", ui::Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<PlaylistView>(); }},
|
||||
//{"Playlist", ui::Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<PlaylistView>(); }},
|
||||
{"S.Painter", ui::Color::orange(), &bitmap_icon_paint, [&nav]() { nav.push<SpectrumPainterView>(); }},
|
||||
//{ "Remote", ui::Color::dark_grey(), &bitmap_icon_remote, [&nav](){ nav.push<RemoteView>(); } },
|
||||
});
|
||||
@@ -582,7 +608,7 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
|
||||
|
||||
{"Wipe SD card", Color::red(), &bitmap_icon_tools_wipesd, [&nav]() { nav.push<WipeSDView>(); }},
|
||||
{"Flash Utility", Color::red(), &bitmap_icon_temperature, [&nav]() { nav.push<FlashUtilityView>(); }},
|
||||
{"SD over USB", Color::green(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
|
||||
{"SD over USB", Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -604,7 +630,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav) {
|
||||
{"Receive", Color::cyan(), &bitmap_icon_receivers, [&nav]() { nav.push<ReceiversMenuView>(); }},
|
||||
{"Transmit", Color::cyan(), &bitmap_icon_transmit, [&nav]() { nav.push<TransmittersMenuView>(); }},
|
||||
{"Capture", Color::red(), &bitmap_icon_capture, [&nav]() { nav.push<CaptureAppView>(); }},
|
||||
{"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<ReplayAppView>(); }},
|
||||
{"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<PlaylistView>(); }},
|
||||
{"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
|
||||
{"Scanner", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ScannerView>(); }},
|
||||
{"Microphone", Color::green(), &bitmap_icon_microphone, [&nav]() { nav.push<MicTXView>(); }},
|
||||
@@ -688,20 +714,27 @@ Context& SystemView::context() const {
|
||||
}
|
||||
|
||||
void SystemView::toggle_overlay() {
|
||||
if (overlay_active) {
|
||||
this->remove_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
} else {
|
||||
this->add_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 1;
|
||||
shared_memory.m4_cpu_usage = 0;
|
||||
shared_memory.m4_heap_usage = 0;
|
||||
shared_memory.m4_stack_usage = 0;
|
||||
switch (++overlay_active) {
|
||||
case 1:
|
||||
this->add_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 1;
|
||||
shared_memory.m4_cpu_usage = 0;
|
||||
shared_memory.m4_heap_usage = 0;
|
||||
shared_memory.m4_stack_usage = 0;
|
||||
break;
|
||||
case 2:
|
||||
this->remove_child(&this->overlay);
|
||||
this->add_child(&this->overlay2);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
break;
|
||||
case 3:
|
||||
this->remove_child(&this->overlay2);
|
||||
this->set_dirty();
|
||||
overlay_active = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
overlay_active = !overlay_active;
|
||||
}
|
||||
|
||||
void SystemView::paint_overlay() {
|
||||
@@ -712,7 +745,10 @@ void SystemView::paint_overlay() {
|
||||
return;
|
||||
|
||||
last_paint_state = !last_paint_state;
|
||||
this->overlay.set_dirty();
|
||||
if (overlay_active == 1)
|
||||
this->overlay.set_dirty();
|
||||
else
|
||||
this->overlay2.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,10 +131,12 @@ class StatusTray : public View {
|
||||
StatusTray(const StatusTray&) = delete;
|
||||
StatusTray& operator=(const StatusTray&) = delete;
|
||||
|
||||
void add_button(ImageButton* child);
|
||||
void add(Widget* child);
|
||||
void update_layout();
|
||||
void clear();
|
||||
void paint(Painter& painter) override;
|
||||
uint8_t width() { return width_; };
|
||||
|
||||
private:
|
||||
static constexpr uint8_t height = 16;
|
||||
@@ -185,7 +187,13 @@ class SystemStatusView : public View {
|
||||
// TODO: Convert to ImageToggle buttons.
|
||||
ImageButton button_speaker{
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_speaker_mute,
|
||||
&bitmap_icon_speaker,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_mute{
|
||||
{0, 0, 2 * 8, 1 * 16},
|
||||
&bitmap_icon_speaker_and_headphones,
|
||||
Color::light_grey(),
|
||||
Color::dark_grey()};
|
||||
|
||||
@@ -230,6 +238,7 @@ class SystemStatusView : public View {
|
||||
|
||||
void on_converter();
|
||||
void on_speaker();
|
||||
void on_mute();
|
||||
void on_stealth();
|
||||
void on_bias_tee();
|
||||
void on_camera();
|
||||
@@ -319,11 +328,12 @@ class SystemView : public View {
|
||||
void paint_overlay();
|
||||
|
||||
private:
|
||||
bool overlay_active{false};
|
||||
uint8_t overlay_active{0};
|
||||
|
||||
SystemStatusView status_view{navigation_view};
|
||||
InformationView info_view{navigation_view};
|
||||
DfuMenu overlay{navigation_view};
|
||||
DfuMenu2 overlay2{navigation_view};
|
||||
NavigationView navigation_view{};
|
||||
Context& context_;
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ include(CheckCXXCompilerFlag)
|
||||
project(baseband_shared)
|
||||
|
||||
# Compiler options here.
|
||||
set(USE_OPT "-O2 -g -falign-functions=16 -fno-math-errno --specs=nano.specs")
|
||||
set(USE_OPT "-O3 -g -falign-functions=16 -fno-math-errno --specs=nano.specs")
|
||||
|
||||
# C specific options here (added to USE_OPT).
|
||||
set(USE_COPT "-std=gnu99")
|
||||
|
||||
@@ -837,6 +837,10 @@ class AK4951 : public audio::Codec {
|
||||
void speaker_enable();
|
||||
void speaker_disable();
|
||||
|
||||
bool speaker_disable_supported() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_headphone_volume(const volume_t volume) override;
|
||||
void headphone_mute();
|
||||
|
||||
|
||||
@@ -34,6 +34,17 @@ class Optional {
|
||||
constexpr Optional(T&& value)
|
||||
: value_{std::move(value)}, valid_{true} {}
|
||||
|
||||
constexpr Optional& operator=(const T& value) {
|
||||
value_ = value;
|
||||
valid_ = true;
|
||||
return *this;
|
||||
}
|
||||
constexpr Optional& operator=(T&& value) {
|
||||
value_ = std::move(value);
|
||||
valid_ = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool is_valid() const { return valid_; }
|
||||
operator bool() const { return valid_; }
|
||||
|
||||
|
||||
@@ -265,9 +265,11 @@ struct ui_config2_t {
|
||||
bool hide_clock : 1;
|
||||
bool hide_sd_card : 1;
|
||||
|
||||
bool hide_mute : 1;
|
||||
bool UNUSED : 7;
|
||||
|
||||
uint8_t placeholder_1;
|
||||
uint8_t placeholder_2;
|
||||
uint8_t placeholder_3;
|
||||
};
|
||||
static_assert(sizeof(ui_config2_t) == sizeof(uint32_t));
|
||||
|
||||
@@ -861,6 +863,9 @@ void set_recon_auto_record_locked(const bool v) {
|
||||
bool ui_hide_speaker() {
|
||||
return data->ui_config2.hide_speaker;
|
||||
}
|
||||
bool ui_hide_mute() {
|
||||
return data->ui_config2.hide_mute;
|
||||
}
|
||||
bool ui_hide_converter() {
|
||||
return data->ui_config2.hide_converter;
|
||||
}
|
||||
@@ -886,6 +891,9 @@ bool ui_hide_sd_card() {
|
||||
void set_ui_hide_speaker(bool v) {
|
||||
data->ui_config2.hide_speaker = v;
|
||||
}
|
||||
void set_ui_hide_mute(bool v) {
|
||||
data->ui_config2.hide_mute = v;
|
||||
}
|
||||
void set_ui_hide_converter(bool v) {
|
||||
data->ui_config2.hide_converter = v;
|
||||
if (v)
|
||||
@@ -1022,11 +1030,13 @@ bool debug_dump() {
|
||||
painter.draw_string({0, 320 - 16}, ui::Styles::red, "ERROR DUMPING " + filename.filename().string() + " !");
|
||||
return false;
|
||||
}
|
||||
pmem_dump_file.write_line("FW version " VERSION_STRING);
|
||||
|
||||
// write persistent memory
|
||||
pmem_dump_file.write_line("[Persistent Memory]");
|
||||
pmem_dump_file.write_line("\n[Persistent Memory]");
|
||||
|
||||
// full variables
|
||||
pmem_dump_file.write_line("structure_version: " + to_string_dec_uint(data->structure_version));
|
||||
pmem_dump_file.write_line("structure_version: 0x" + to_string_hex(data->structure_version, 8));
|
||||
pmem_dump_file.write_line("target_frequency: " + to_string_dec_int(data->target_frequency));
|
||||
pmem_dump_file.write_line("correction_ppb: " + to_string_dec_int(data->correction_ppb));
|
||||
pmem_dump_file.write_line("modem_def_index: " + to_string_dec_uint(data->modem_def_index));
|
||||
@@ -1046,7 +1056,7 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("pocsag_ignore_address: " + to_string_dec_uint(data->pocsag_ignore_address));
|
||||
pmem_dump_file.write_line("tone_mix: " + to_string_dec_uint(data->tone_mix));
|
||||
pmem_dump_file.write_line("hardware_config: " + to_string_dec_uint(data->hardware_config));
|
||||
pmem_dump_file.write_line("recon_config: " + to_string_dec_uint(data->recon_config));
|
||||
pmem_dump_file.write_line("recon_config: 0x" + to_string_hex(data->recon_config, 16));
|
||||
pmem_dump_file.write_line("placeholder_0: " + to_string_dec_int(data->placeholder_0));
|
||||
pmem_dump_file.write_line("converter: " + to_string_dec_int(data->converter));
|
||||
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(data->updown_converter));
|
||||
@@ -1084,12 +1094,13 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("ui_config2 hide_bias_tee: " + to_string_dec_uint(data->ui_config2.hide_bias_tee));
|
||||
pmem_dump_file.write_line("ui_config2 hide_clock: " + to_string_dec_uint(data->ui_config2.hide_clock));
|
||||
pmem_dump_file.write_line("ui_config2 hide_sd_card: " + to_string_dec_uint(data->ui_config2.hide_sd_card));
|
||||
pmem_dump_file.write_line("ui_config2 hide_mute: " + to_string_dec_uint(data->ui_config2.hide_mute));
|
||||
|
||||
// misc_config bits
|
||||
pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute()));
|
||||
pmem_dump_file.write_line("misc_config config_speaker_disable: " + to_string_dec_int(config_speaker_disable()));
|
||||
// receiver_model
|
||||
pmem_dump_file.write_line("[Receiver Model]");
|
||||
pmem_dump_file.write_line("\n[Receiver Model]");
|
||||
pmem_dump_file.write_line("target_frequency: " + to_string_dec_uint(receiver_model.target_frequency()));
|
||||
pmem_dump_file.write_line("frequency_step: " + to_string_dec_uint(receiver_model.frequency_step()));
|
||||
pmem_dump_file.write_line("lna: " + to_string_dec_int(receiver_model.lna()));
|
||||
@@ -1117,14 +1128,14 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("modulation: !!unknown mode!!");
|
||||
break;
|
||||
}
|
||||
pmem_dump_file.write_line("headphone_volume.centibel: " + to_string_dec_uint(receiver_model.headphone_volume().centibel()));
|
||||
pmem_dump_file.write_line("headphone_volume.centibel: " + to_string_dec_int(receiver_model.headphone_volume().centibel()));
|
||||
pmem_dump_file.write_line("normalized_headphone_volume: " + to_string_dec_uint(receiver_model.normalized_headphone_volume()));
|
||||
pmem_dump_file.write_line("am_configuration: " + to_string_dec_uint(receiver_model.am_configuration()));
|
||||
pmem_dump_file.write_line("nbfm_configuration: " + to_string_dec_uint(receiver_model.nbfm_configuration()));
|
||||
pmem_dump_file.write_line("wfm_configuration: " + to_string_dec_uint(receiver_model.wfm_configuration()));
|
||||
|
||||
// transmitter_model
|
||||
pmem_dump_file.write_line("[Transmitter Model]");
|
||||
pmem_dump_file.write_line("\n[Transmitter Model]");
|
||||
pmem_dump_file.write_line("target_frequency: " + to_string_dec_uint(transmitter_model.target_frequency()));
|
||||
pmem_dump_file.write_line("lna: " + to_string_dec_int(transmitter_model.lna()));
|
||||
pmem_dump_file.write_line("vga: " + to_string_dec_int(transmitter_model.vga()));
|
||||
|
||||
@@ -252,6 +252,7 @@ void set_recon_match_mode(const bool v);
|
||||
|
||||
/* UI Config 2 */
|
||||
bool ui_hide_speaker();
|
||||
bool ui_hide_mute();
|
||||
bool ui_hide_converter();
|
||||
bool ui_hide_stealth();
|
||||
bool ui_hide_camera();
|
||||
@@ -260,6 +261,7 @@ bool ui_hide_bias_tee();
|
||||
bool ui_hide_clock();
|
||||
bool ui_hide_sd_card();
|
||||
void set_ui_hide_speaker(bool v);
|
||||
void set_ui_hide_mute(bool v);
|
||||
void set_ui_hide_converter(bool v);
|
||||
void set_ui_hide_stealth(bool v);
|
||||
void set_ui_hide_camera(bool v);
|
||||
|
||||
@@ -140,6 +140,9 @@ struct Color {
|
||||
static constexpr Color dark_grey() {
|
||||
return {63, 63, 63};
|
||||
}
|
||||
static constexpr Color darker_grey() {
|
||||
return {31, 31, 31};
|
||||
}
|
||||
|
||||
static constexpr Color purple() {
|
||||
return {204, 0, 102};
|
||||
|
||||
@@ -80,7 +80,7 @@ int Painter::draw_string(
|
||||
|
||||
void Painter::draw_bitmap(Point p, const Bitmap& bitmap, Color foreground, Color background) {
|
||||
// If bright foreground colors on white background, darken the foreground color to improve visibility
|
||||
if ((background.v == ui::Color::white().v) && (foreground.to_greyscale() > 175))
|
||||
if ((background.v == ui::Color::white().v) && (foreground.to_greyscale() > 146))
|
||||
foreground = foreground.dark();
|
||||
|
||||
display.draw_bitmap(p, bitmap.size, bitmap.data, foreground, background);
|
||||
|
||||
@@ -39,6 +39,10 @@ Dim Font::line_height() const {
|
||||
return h;
|
||||
}
|
||||
|
||||
Dim Font::char_width() const {
|
||||
return w;
|
||||
}
|
||||
|
||||
Size Font::size_of(const std::string s) const {
|
||||
Size size;
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ class Font {
|
||||
Glyph glyph(const char c) const;
|
||||
|
||||
Dim line_height() const;
|
||||
Dim char_width() const;
|
||||
|
||||
Size size_of(const std::string s) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -177,6 +177,8 @@ const std::vector<Widget*>& Widget::children() const {
|
||||
}
|
||||
|
||||
Context& Widget::context() const {
|
||||
chDbgAssert(parent_, "parent_ is null",
|
||||
"Check that parent isn't null before deref.");
|
||||
return parent()->context();
|
||||
}
|
||||
|
||||
|
||||
@@ -345,6 +345,9 @@ class WM8731 : public audio::Codec {
|
||||
|
||||
void speaker_enable(){};
|
||||
void speaker_disable(){};
|
||||
bool speaker_disable_supported() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void microphone_enable(int8_t wm8731_boost_GUI) override {
|
||||
microphone_mute(true); // c/m to reduce "plop noise" when changing wm8731_boost_GUI.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 183 B |
Binary file not shown.
|
After Width: | Height: | Size: 185 B |
Reference in New Issue
Block a user