mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 02:29:29 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3db2053c21 | |||
| ccd7bd6fc2 |
@@ -189,10 +189,17 @@ ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
|
||||
void copy_to_radio_model(const AppSettings& settings) {
|
||||
// NB: Don't actually adjust the radio here or it will hang.
|
||||
|
||||
if (flags_enabled(settings.mode, Mode::TX))
|
||||
if (flags_enabled(settings.mode, Mode::TX)) {
|
||||
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
|
||||
transmitter_model.set_target_frequency(settings.tx_frequency);
|
||||
|
||||
transmitter_model.configure_from_app_settings(settings);
|
||||
}
|
||||
|
||||
if (flags_enabled(settings.mode, Mode::RX)) {
|
||||
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
|
||||
transmitter_model.set_target_frequency(settings.rx_frequency);
|
||||
|
||||
receiver_model.configure_from_app_settings(settings);
|
||||
receiver_model.set_configuration_without_init(
|
||||
static_cast<ReceiverModel::Mode>(settings.modulation),
|
||||
@@ -241,11 +248,15 @@ void copy_from_radio_model(AppSettings& settings) {
|
||||
}
|
||||
|
||||
/* SettingsManager *************************************************/
|
||||
SettingsManager::SettingsManager(std::string app_name, Mode mode)
|
||||
SettingsManager::SettingsManager(
|
||||
std::string app_name,
|
||||
Mode mode,
|
||||
Options options)
|
||||
: app_name_{std::move(app_name)},
|
||||
settings_{},
|
||||
loaded_{false} {
|
||||
settings_.mode = mode;
|
||||
settings_.options = options;
|
||||
auto result = load_settings(app_name_, settings_);
|
||||
|
||||
if (result == ResultCode::Ok) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "file.hpp"
|
||||
#include "max283x.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace app_settings {
|
||||
@@ -47,25 +48,34 @@ enum class Mode : uint8_t {
|
||||
RX_TX = 0x03, // Both TX/RX
|
||||
};
|
||||
|
||||
enum class Options {
|
||||
None = 0x0000,
|
||||
|
||||
/* Don't use target frequency from app settings. */
|
||||
UseGlobalTargetFrequency = 0x0001,
|
||||
};
|
||||
|
||||
// TODO: separate types for TX/RX or union?
|
||||
/* NB: See RX/TX model headers for default values. */
|
||||
struct AppSettings {
|
||||
Mode mode;
|
||||
uint32_t baseband_bandwidth;
|
||||
uint32_t sampling_rate;
|
||||
uint8_t lna;
|
||||
uint8_t vga;
|
||||
uint8_t rx_amp;
|
||||
uint8_t tx_amp;
|
||||
uint8_t tx_gain;
|
||||
uint32_t channel_bandwidth;
|
||||
Mode mode = Mode::RX;
|
||||
Options options = Options::None;
|
||||
uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum;
|
||||
uint32_t sampling_rate = 3072000; // Good for 48k audio.
|
||||
uint8_t lna = 32;
|
||||
uint8_t vga = 32;
|
||||
uint8_t rx_amp = 0;
|
||||
uint8_t tx_amp = 0;
|
||||
uint8_t tx_gain = 35;
|
||||
uint32_t channel_bandwidth = 1;
|
||||
uint32_t rx_frequency;
|
||||
uint32_t tx_frequency;
|
||||
uint32_t step;
|
||||
uint8_t modulation;
|
||||
uint8_t am_config_index;
|
||||
uint8_t nbfm_config_index;
|
||||
uint8_t wfm_config_index;
|
||||
uint8_t squelch;
|
||||
uint32_t step = 25000;
|
||||
uint8_t modulation = 1; // NFM
|
||||
uint8_t am_config_index = 0;
|
||||
uint8_t nbfm_config_index = 0;
|
||||
uint8_t wfm_config_index = 0;
|
||||
uint8_t squelch = 80;
|
||||
|
||||
uint8_t volume;
|
||||
};
|
||||
@@ -84,7 +94,7 @@ void copy_from_radio_model(AppSettings& settings);
|
||||
* the receiver/transmitter models are set before the control ctors run. */
|
||||
class SettingsManager {
|
||||
public:
|
||||
SettingsManager(std::string app_name, Mode mode);
|
||||
SettingsManager(std::string app_name, Mode mode, Options options = Options::None);
|
||||
~SettingsManager();
|
||||
|
||||
SettingsManager(const SettingsManager&) = delete;
|
||||
@@ -106,4 +116,7 @@ class SettingsManager {
|
||||
|
||||
} // namespace app_settings
|
||||
|
||||
ENABLE_FLAGS_OPERATORS(app_settings::Mode);
|
||||
ENABLE_FLAGS_OPERATORS(app_settings::Options);
|
||||
|
||||
#endif /*__APP_SETTINGS_H__*/
|
||||
|
||||
@@ -156,7 +156,8 @@ class AnalogAudioView : public View {
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_audio", app_settings::Mode::RX};
|
||||
"rx_audio", app_settings::Mode::RX,
|
||||
app_settings::Options::UseGlobalTargetFrequency};
|
||||
|
||||
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
|
||||
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
|
||||
|
||||
@@ -81,7 +81,8 @@ class MicTXView : public View {
|
||||
void set_ptt_visibility(bool v);
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_mic", app_settings::Mode::RX_TX};
|
||||
"tx_mic", app_settings::Mode::RX_TX,
|
||||
app_settings::Options::UseGlobalTargetFrequency};
|
||||
|
||||
bool transmitting{false};
|
||||
bool va_enabled{false};
|
||||
|
||||
@@ -54,18 +54,16 @@ void PlaylistView::load_file(std::filesystem::path playlist_path) {
|
||||
if ((int)one_char[0] >= ' ') {
|
||||
line += one_char[0];
|
||||
} else if (one_char[0] == '\n') {
|
||||
total_tracks++;
|
||||
txtline_process(line);
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
if (line.length() > 0) {
|
||||
total_tracks++;
|
||||
txtline_process(line);
|
||||
}
|
||||
}
|
||||
playlist_masterdb = playlist_db;
|
||||
// text_track.set(to_string_dec_uint(track_number) + "/" + to_string_dec_uint(total_tracks)); //removed because there's no space for it
|
||||
text_track.set(to_string_dec_uint(track_number) + "/" + to_string_dec_uint(total_tracks) + " " + now_play_list_file.string());
|
||||
tracks_progressbar.set_max(total_tracks);
|
||||
button_play.focus();
|
||||
return;
|
||||
@@ -73,35 +71,38 @@ void PlaylistView::load_file(std::filesystem::path playlist_path) {
|
||||
|
||||
void PlaylistView::txtline_process(std::string& line) {
|
||||
playlist_entry new_item;
|
||||
rf::Frequency f;
|
||||
size_t previous = 0;
|
||||
auto current = std::string::npos;
|
||||
// read freq
|
||||
current = line.find(',');
|
||||
std::string freqs = line.substr(0, current);
|
||||
if (current == std::string::npos) return;
|
||||
errno = 0;
|
||||
new_item.replay_frequency = strtoll(line.substr(0, current).c_str(), nullptr, 0);
|
||||
if (new_item.replay_frequency == 0 || errno == EINVAL || errno == ERANGE)
|
||||
return;
|
||||
// read file
|
||||
previous = current + 1;
|
||||
current = line.find(',', previous);
|
||||
std::string file = line.substr(previous, current - previous);
|
||||
if ((current = line.find(',', previous)) == std::string::npos) return;
|
||||
new_item.replay_file = "/" + line.substr(previous, current - previous);
|
||||
// read samplerate
|
||||
previous = current + 1;
|
||||
errno = 0;
|
||||
new_item.sample_rate = strtoll(line.substr(previous).c_str(), nullptr, 10);
|
||||
if (new_item.sample_rate == 0 || errno == EINVAL || errno == ERANGE)
|
||||
return;
|
||||
// optionnal read delay
|
||||
current = line.find(',', previous);
|
||||
uint32_t sample_rate = strtoll(line.substr(previous).c_str(), nullptr, 10);
|
||||
previous = current + 1;
|
||||
|
||||
uint32_t item_delay;
|
||||
|
||||
current = line.find(',', previous);
|
||||
if (current == std::string::npos) { // compatibility with old PPL grammar
|
||||
item_delay = strtoll(line.substr(previous).c_str(), nullptr, 10);
|
||||
if (current == std::string::npos) {
|
||||
new_item.next_delay = 0;
|
||||
} else {
|
||||
item_delay = 0;
|
||||
errno = 0;
|
||||
previous = current + 1;
|
||||
new_item.next_delay = strtoll(line.substr(previous).c_str(), nullptr, 10);
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
return;
|
||||
}
|
||||
|
||||
f = strtoll(freqs.c_str(), nullptr, 0);
|
||||
new_item.replay_frequency = f;
|
||||
new_item.replay_file = "/" + file;
|
||||
new_item.sample_rate = sample_rate;
|
||||
new_item.next_delay = item_delay;
|
||||
|
||||
playlist_db.push_back(std::move(new_item));
|
||||
total_tracks++;
|
||||
}
|
||||
|
||||
void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate, uint32_t next_delay) {
|
||||
@@ -130,9 +131,7 @@ void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Freq
|
||||
on_track_progressbar.set_max(file_size);
|
||||
text_filename.set(file_path.filename().string().substr(0, 12));
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
// text_track.set(to_string_dec_uint(track_number) + "/" + to_string_dec_uint(total_tracks));
|
||||
// ^Thanks @kallanreed @bernd-herzog @u-foka for this line
|
||||
// ^^removed because there's no space for it.
|
||||
text_track.set(to_string_dec_uint(track_number) + "/" + to_string_dec_uint(total_tracks) + " " + now_play_list_file.string());
|
||||
tracks_progressbar.set_value(track_number);
|
||||
}
|
||||
|
||||
@@ -171,9 +170,13 @@ void PlaylistView::toggle() {
|
||||
clean_playlist();
|
||||
} else {
|
||||
clean_playlist();
|
||||
load_file(now_play_list_file);
|
||||
if (!playlist_db.empty()) {
|
||||
start();
|
||||
if (std::filesystem::file_exists(now_play_list_file.string())) {
|
||||
load_file(now_play_list_file);
|
||||
if (!playlist_db.empty()) {
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
text_track.set("0/0 no input playlist file");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,6 +202,7 @@ void PlaylistView::start() {
|
||||
|
||||
if (reader) {
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
baseband::set_sample_rate(sample_rate * 8);
|
||||
|
||||
if (now_delay) { // this `if` is because, if the delay is 0, it will sleep forever
|
||||
@@ -289,10 +293,12 @@ PlaylistView::PlaylistView(
|
||||
&tx_view, // this handles now the previous rfgain, rfamp
|
||||
&check_loop,
|
||||
&button_play,
|
||||
// &text_track, // removed because there's no space for it
|
||||
&text_track, // removed because there's no space for it
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
@@ -316,7 +322,11 @@ PlaylistView::PlaylistView(
|
||||
auto open_view = nav.push<FileLoadView>(".PPL");
|
||||
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||
now_play_list_file = new_file_path;
|
||||
load_file(new_file_path);
|
||||
if (std::filesystem::file_exists(now_play_list_file.string())) {
|
||||
load_file(now_play_list_file);
|
||||
} else {
|
||||
text_track.set("0/0 no input playlist file");
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -330,7 +340,6 @@ void PlaylistView::on_hide() {
|
||||
stop(false);
|
||||
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
|
||||
// it's being shown or hidden.
|
||||
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ class PlaylistView : public View {
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_playlist", app_settings::Mode::TX};
|
||||
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
// add or remove lines here to allow more header and less spectrum view, & vice versa
|
||||
static constexpr ui::Dim header_height = 4 * 16;
|
||||
|
||||
struct playlist_entry {
|
||||
rf::Frequency replay_frequency{0};
|
||||
@@ -137,10 +138,10 @@ class PlaylistView : public View {
|
||||
&bitmap_play,
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
// TODO: add track number
|
||||
// Text text_track{ //removed because there's no space for it
|
||||
// {13 * 8, 20 * 8, 16 * 8, 16},
|
||||
// "0/0"};
|
||||
|
||||
Text text_track{
|
||||
{0 * 8, 3 * 16, 16 * 8, 16},
|
||||
"0/0 no input playlist file"};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
|
||||
|
||||
@@ -265,7 +265,6 @@ void ReceiverModel::set_configuration_without_init(
|
||||
|
||||
void ReceiverModel::configure_from_app_settings(
|
||||
const app_settings::AppSettings& settings) {
|
||||
set_target_frequency(settings.rx_frequency);
|
||||
baseband_bandwidth_ = settings.baseband_bandwidth;
|
||||
sampling_rate_ = settings.sampling_rate;
|
||||
lna_gain_db_ = settings.lna;
|
||||
|
||||
@@ -149,8 +149,6 @@ void TransmitterModel::disable() {
|
||||
|
||||
void TransmitterModel::configure_from_app_settings(
|
||||
const app_settings::AppSettings& settings) {
|
||||
set_target_frequency(settings.tx_frequency);
|
||||
|
||||
baseband_bandwidth_ = settings.baseband_bandwidth;
|
||||
channel_bandwidth_ = settings.channel_bandwidth;
|
||||
tx_gain_db_ = settings.tx_gain;
|
||||
@@ -163,7 +161,7 @@ void TransmitterModel::configure_from_app_settings(
|
||||
}
|
||||
|
||||
void TransmitterModel::update_tuning_frequency() {
|
||||
radio::set_tuning_frequency(persistent_memory::target_frequency());
|
||||
radio::set_tuning_frequency(target_frequency());
|
||||
}
|
||||
|
||||
void TransmitterModel::update_antenna_bias() {
|
||||
|
||||
@@ -104,7 +104,6 @@ void TransmitterView::set_transmitting(const bool transmitting) {
|
||||
|
||||
void TransmitterView::on_show() {
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
field_frequency_step.set_by_value(receiver_model.frequency_step());
|
||||
|
||||
field_gain.set_value(transmitter_model.tx_gain());
|
||||
field_amp.set_value(transmitter_model.rf_amp() ? 14 : 0);
|
||||
@@ -152,7 +151,6 @@ TransmitterView::TransmitterView(
|
||||
}
|
||||
}
|
||||
|
||||
field_frequency.set_step(frequency_step);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
on_target_frequency_changed(f);
|
||||
};
|
||||
@@ -162,8 +160,11 @@ TransmitterView::TransmitterView(
|
||||
};
|
||||
|
||||
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
receiver_model.set_frequency_step(v);
|
||||
this->field_frequency.set_step(v);
|
||||
};
|
||||
// TODO: Shouldn't be a ctor parameter because it doesn't work with app settings.
|
||||
field_frequency_step.set_by_value(frequency_step);
|
||||
|
||||
field_gain.on_change = [this](uint32_t tx_gain) {
|
||||
on_tx_gain_changed(tx_gain);
|
||||
|
||||
@@ -95,7 +95,25 @@ int int_atan2(int y, int x);
|
||||
int32_t int16_sin_s4(int32_t x);
|
||||
|
||||
template <typename TEnum>
|
||||
bool flags_enabled(TEnum value, TEnum flags) {
|
||||
struct is_flags_type {
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
|
||||
template <typename TEnum>
|
||||
constexpr bool is_flags_type_v = is_flags_type<TEnum>::value;
|
||||
|
||||
#define ENABLE_FLAGS_OPERATORS(type) \
|
||||
template <> \
|
||||
struct is_flags_type<type> { static constexpr bool value = true; };
|
||||
|
||||
template <typename TEnum>
|
||||
constexpr std::enable_if_t<is_flags_type_v<TEnum>, TEnum> operator|(TEnum a, TEnum b) {
|
||||
using under_t = std::underlying_type_t<TEnum>;
|
||||
return static_cast<TEnum>(static_cast<under_t>(a) | static_cast<under_t>(b));
|
||||
}
|
||||
|
||||
template <typename TEnum>
|
||||
constexpr std::enable_if_t<is_flags_type_v<TEnum>, bool> flags_enabled(TEnum value, TEnum flags) {
|
||||
auto i_value = static_cast<std::underlying_type_t<TEnum>>(value);
|
||||
auto i_flags = static_cast<std::underlying_type_t<TEnum>>(flags);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "doctest.h"
|
||||
#include "utility.hpp"
|
||||
|
||||
TEST_SUITE_BEGIN("flags_enabled");
|
||||
TEST_SUITE_BEGIN("Flags operators");
|
||||
|
||||
enum class Flags : uint8_t {
|
||||
A = 0x1,
|
||||
@@ -30,14 +30,21 @@ enum class Flags : uint8_t {
|
||||
C = 0x4,
|
||||
};
|
||||
|
||||
ENABLE_FLAGS_OPERATORS(Flags);
|
||||
|
||||
TEST_CASE("operator| should combine flags.") {
|
||||
constexpr Flags f = Flags::A | Flags::C;
|
||||
static_assert(static_cast<int>(f) == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("When flag set, flags_enabled should be true.") {
|
||||
Flags f = Flags::A;
|
||||
CHECK(flags_enabled(f, Flags::A));
|
||||
constexpr Flags f = Flags::A;
|
||||
static_assert(flags_enabled(f, Flags::A));
|
||||
}
|
||||
|
||||
TEST_CASE("When flag not set, flags_enabled should be false.") {
|
||||
Flags f = Flags::B;
|
||||
CHECK(flags_enabled(f, Flags::A) == false);
|
||||
constexpr Flags f = Flags::B;
|
||||
static_assert(flags_enabled(f, Flags::A) == false);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
Reference in New Issue
Block a user