mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-04 23:45:26 +00:00
Simple amplification option in IQ Trim app (#1506)
* Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload
This commit is contained in:
parent
f6a437f7fb
commit
86d4b17257
@ -40,6 +40,7 @@ IQTrimView::IQTrimView(NavigationView& nav)
|
||||
&text_samples,
|
||||
&text_max,
|
||||
&field_cutoff,
|
||||
&field_amplify,
|
||||
&button_trim,
|
||||
});
|
||||
|
||||
@ -72,6 +73,11 @@ IQTrimView::IQTrimView(NavigationView& nav)
|
||||
refresh_ui();
|
||||
};
|
||||
|
||||
field_amplify.set_value(1); // 1X is default (no amplification)
|
||||
field_amplify.on_change = [this](int32_t) {
|
||||
refresh_ui();
|
||||
};
|
||||
|
||||
button_trim.on_select = [this](Button&) {
|
||||
if (trim_capture()) {
|
||||
profile_capture();
|
||||
@ -133,7 +139,18 @@ void IQTrimView::focus() {
|
||||
void IQTrimView::refresh_ui() {
|
||||
field_path.set_text(path_.filename().string());
|
||||
text_samples.set(to_string_dec_uint(info_->sample_count));
|
||||
text_max.set(to_string_dec_uint(info_->max_power));
|
||||
|
||||
// show max power after amplification applied
|
||||
uint64_t power_amp = field_amplify.value() * field_amplify.value() * field_amplify.value() * field_amplify.value();
|
||||
text_max.set(to_string_dec_uint(info_->max_power * power_amp));
|
||||
|
||||
// show max power in red if amplification is too high, causing clipping
|
||||
uint32_t clipping_limit = (fs::capture_file_sample_size(path_) == sizeof(complex8_t)) ? 0x80 : 0x8000;
|
||||
if ((field_amplify.value() * info_->max_iq) > clipping_limit)
|
||||
text_max.set_style(&Styles::red);
|
||||
else
|
||||
text_max.set_style(&Styles::light_grey);
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@ -191,7 +208,7 @@ bool IQTrimView::trim_capture() {
|
||||
}
|
||||
|
||||
progress_ui.show_trimming();
|
||||
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback());
|
||||
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback(), field_amplify.value());
|
||||
progress_ui.clear();
|
||||
|
||||
if (!trimmed)
|
||||
|
@ -107,6 +107,8 @@ class IQTrimView : public View {
|
||||
{{0 * 8, 9 * 16}, "Max Pwr:", Color::light_grey()},
|
||||
{{0 * 8, 10 * 16}, "Cutoff :", Color::light_grey()},
|
||||
{{12 * 8, 10 * 16}, "%", Color::light_grey()},
|
||||
{{0 * 8, 12 * 16}, "Amplify:", Color::light_grey()},
|
||||
{{10 * 8, 12 * 16}, "x", Color::light_grey()},
|
||||
};
|
||||
|
||||
TextField field_path{
|
||||
@ -135,7 +137,7 @@ class IQTrimView : public View {
|
||||
"0"};
|
||||
|
||||
Text text_max{
|
||||
{9 * 8, 9 * 16, 10 * 8, 1 * 16},
|
||||
{9 * 8, 9 * 16, 20 * 8, 1 * 16},
|
||||
"0"};
|
||||
|
||||
NumberField field_cutoff{
|
||||
@ -145,6 +147,13 @@ class IQTrimView : public View {
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_amplify{
|
||||
{9 * 8, 12 * 16},
|
||||
1,
|
||||
{1, 9},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Button button_trim{
|
||||
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
|
||||
"Trim"};
|
||||
|
@ -36,6 +36,13 @@ uint32_t power(T value) {
|
||||
return (real * real) + (imag * imag);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
uint32_t iq_max(T value) {
|
||||
auto real = abs(value.real());
|
||||
auto imag = abs(value.imag());
|
||||
return (real > imag) ? real : imag;
|
||||
}
|
||||
|
||||
/* Collects capture file metadata and sample power buckets. */
|
||||
template <typename T>
|
||||
Optional<CaptureInfo> profile_capture(
|
||||
@ -51,7 +58,8 @@ Optional<CaptureInfo> profile_capture(
|
||||
.file_size = f.size(),
|
||||
.sample_count = f.size() / sizeof(T),
|
||||
.sample_size = sizeof(T),
|
||||
.max_power = 0};
|
||||
.max_power = 0,
|
||||
.max_iq = 0};
|
||||
|
||||
auto profile_samples = buckets.size * samples_per_bucket;
|
||||
auto sample_interval = info.sample_count / profile_samples;
|
||||
@ -67,8 +75,11 @@ Optional<CaptureInfo> profile_capture(
|
||||
if (*result != info.sample_size)
|
||||
break; // EOF
|
||||
|
||||
auto mag_squared = power(value);
|
||||
auto max_iq = iq_max(value);
|
||||
if (max_iq > info.max_iq)
|
||||
info.max_iq = max_iq;
|
||||
|
||||
auto mag_squared = power(value);
|
||||
if (mag_squared > info.max_power)
|
||||
info.max_power = mag_squared;
|
||||
|
||||
@ -133,13 +144,50 @@ TrimRange compute_trim_range(
|
||||
info.sample_size};
|
||||
}
|
||||
|
||||
void amplify_iq_buffer(uint8_t* buffer, uint32_t length, uint32_t amplification, uint8_t sample_size) {
|
||||
uint32_t mult_count = length / sample_size / 2;
|
||||
|
||||
switch (sample_size) {
|
||||
case sizeof(complex16_t): {
|
||||
int16_t* buf_ptr = (int16_t*)buffer;
|
||||
for (uint32_t i = 0; i < mult_count; i++) {
|
||||
int32_t val = *buf_ptr * amplification;
|
||||
if (val > 0x7FFF)
|
||||
val = 0x7FFF;
|
||||
else if (val < -0x7FFF)
|
||||
val = -0x7FFF;
|
||||
*buf_ptr++ = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case sizeof(complex8_t): {
|
||||
int8_t* buf_ptr = (int8_t*)buffer;
|
||||
for (uint32_t i = 0; i < mult_count; i++) {
|
||||
int32_t val = *buf_ptr * amplification;
|
||||
if (val > 0x7F)
|
||||
val = 0x7F;
|
||||
else if (val < -0x7F)
|
||||
val = -0x7F;
|
||||
*buf_ptr++ = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool trim_capture_with_range(
|
||||
const fs::path& path,
|
||||
TrimRange range,
|
||||
const std::function<void(uint8_t)>& on_progress) {
|
||||
const std::function<void(uint8_t)>& on_progress,
|
||||
const uint32_t amplification) {
|
||||
constexpr size_t buffer_size = std::filesystem::max_file_block_size;
|
||||
uint8_t buffer[buffer_size];
|
||||
auto temp_path = path + u"-tmp";
|
||||
auto sample_size = fs::capture_file_sample_size(path);
|
||||
|
||||
// end_sample is the first sample to _not_ include.
|
||||
auto start_byte = range.start_sample * range.sample_size;
|
||||
@ -168,6 +216,9 @@ bool trim_capture_with_range(
|
||||
auto remaining = length - processed;
|
||||
auto to_write = std::min(remaining, *result);
|
||||
|
||||
if (amplification > 1)
|
||||
amplify_iq_buffer(buffer, to_write, amplification, sample_size);
|
||||
|
||||
result = dst->write(buffer, to_write);
|
||||
if (result.is_error()) return false;
|
||||
|
||||
|
@ -37,6 +37,7 @@ struct CaptureInfo {
|
||||
uint64_t sample_count;
|
||||
uint8_t sample_size;
|
||||
uint32_t max_power;
|
||||
uint32_t max_iq;
|
||||
};
|
||||
|
||||
/* Holds sample average power by bucket. */
|
||||
@ -82,11 +83,19 @@ TrimRange compute_trim_range(
|
||||
const PowerBuckets& buckets,
|
||||
uint8_t cutoff_percent);
|
||||
|
||||
/* Multiplies samples in an IQ buffer by amplification value */
|
||||
void amplify_iq_buffer(
|
||||
uint8_t* buffer,
|
||||
uint32_t length,
|
||||
uint32_t amplification,
|
||||
uint8_t sample_size);
|
||||
|
||||
/* Trims the capture file with the specified range. */
|
||||
bool trim_capture_with_range(
|
||||
const std::filesystem::path& path,
|
||||
TrimRange range,
|
||||
const std::function<void(uint8_t)>& on_progress);
|
||||
const std::function<void(uint8_t)>& on_progress,
|
||||
const uint32_t amplification);
|
||||
|
||||
} // namespace iq
|
||||
|
||||
|
@ -322,7 +322,7 @@ void RecordView::trim_capture() {
|
||||
auto trim_range = iq::compute_trim_range(*info, power_buckets, 7);
|
||||
|
||||
trim_ui.show_trimming();
|
||||
iq::trim_capture_with_range(trim_path, trim_range, trim_ui.get_callback());
|
||||
iq::trim_capture_with_range(trim_path, trim_range, trim_ui.get_callback(), 1);
|
||||
}
|
||||
|
||||
trim_ui.clear();
|
||||
|
Loading…
Reference in New Issue
Block a user