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:
Mark Thompson 2023-10-19 13:14:25 -05:00 committed by GitHub
parent f6a437f7fb
commit 86d4b17257
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 8 deletions

View File

@ -40,6 +40,7 @@ IQTrimView::IQTrimView(NavigationView& nav)
&text_samples, &text_samples,
&text_max, &text_max,
&field_cutoff, &field_cutoff,
&field_amplify,
&button_trim, &button_trim,
}); });
@ -72,6 +73,11 @@ IQTrimView::IQTrimView(NavigationView& nav)
refresh_ui(); 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&) { button_trim.on_select = [this](Button&) {
if (trim_capture()) { if (trim_capture()) {
profile_capture(); profile_capture();
@ -133,7 +139,18 @@ void IQTrimView::focus() {
void IQTrimView::refresh_ui() { void IQTrimView::refresh_ui() {
field_path.set_text(path_.filename().string()); field_path.set_text(path_.filename().string());
text_samples.set(to_string_dec_uint(info_->sample_count)); 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(); set_dirty();
} }
@ -191,7 +208,7 @@ bool IQTrimView::trim_capture() {
} }
progress_ui.show_trimming(); 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(); progress_ui.clear();
if (!trimmed) if (!trimmed)

View File

@ -107,6 +107,8 @@ class IQTrimView : public View {
{{0 * 8, 9 * 16}, "Max Pwr:", Color::light_grey()}, {{0 * 8, 9 * 16}, "Max Pwr:", Color::light_grey()},
{{0 * 8, 10 * 16}, "Cutoff :", Color::light_grey()}, {{0 * 8, 10 * 16}, "Cutoff :", Color::light_grey()},
{{12 * 8, 10 * 16}, "%", 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{ TextField field_path{
@ -135,7 +137,7 @@ class IQTrimView : public View {
"0"}; "0"};
Text text_max{ Text text_max{
{9 * 8, 9 * 16, 10 * 8, 1 * 16}, {9 * 8, 9 * 16, 20 * 8, 1 * 16},
"0"}; "0"};
NumberField field_cutoff{ NumberField field_cutoff{
@ -145,6 +147,13 @@ class IQTrimView : public View {
1, 1,
' '}; ' '};
NumberField field_amplify{
{9 * 8, 12 * 16},
1,
{1, 9},
1,
' '};
Button button_trim{ Button button_trim{
{20 * 8, 16 * 16, 8 * 8, 2 * 16}, {20 * 8, 16 * 16, 8 * 8, 2 * 16},
"Trim"}; "Trim"};

View File

@ -36,6 +36,13 @@ uint32_t power(T value) {
return (real * real) + (imag * imag); 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. */ /* Collects capture file metadata and sample power buckets. */
template <typename T> template <typename T>
Optional<CaptureInfo> profile_capture( Optional<CaptureInfo> profile_capture(
@ -51,7 +58,8 @@ Optional<CaptureInfo> profile_capture(
.file_size = f.size(), .file_size = f.size(),
.sample_count = f.size() / sizeof(T), .sample_count = f.size() / sizeof(T),
.sample_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 profile_samples = buckets.size * samples_per_bucket;
auto sample_interval = info.sample_count / profile_samples; auto sample_interval = info.sample_count / profile_samples;
@ -67,8 +75,11 @@ Optional<CaptureInfo> profile_capture(
if (*result != info.sample_size) if (*result != info.sample_size)
break; // EOF 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) if (mag_squared > info.max_power)
info.max_power = mag_squared; info.max_power = mag_squared;
@ -133,13 +144,50 @@ TrimRange compute_trim_range(
info.sample_size}; 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( bool trim_capture_with_range(
const fs::path& path, const fs::path& path,
TrimRange range, 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; constexpr size_t buffer_size = std::filesystem::max_file_block_size;
uint8_t buffer[buffer_size]; uint8_t buffer[buffer_size];
auto temp_path = path + u"-tmp"; auto temp_path = path + u"-tmp";
auto sample_size = fs::capture_file_sample_size(path);
// end_sample is the first sample to _not_ include. // end_sample is the first sample to _not_ include.
auto start_byte = range.start_sample * range.sample_size; auto start_byte = range.start_sample * range.sample_size;
@ -168,6 +216,9 @@ bool trim_capture_with_range(
auto remaining = length - processed; auto remaining = length - processed;
auto to_write = std::min(remaining, *result); 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); result = dst->write(buffer, to_write);
if (result.is_error()) return false; if (result.is_error()) return false;

View File

@ -37,6 +37,7 @@ struct CaptureInfo {
uint64_t sample_count; uint64_t sample_count;
uint8_t sample_size; uint8_t sample_size;
uint32_t max_power; uint32_t max_power;
uint32_t max_iq;
}; };
/* Holds sample average power by bucket. */ /* Holds sample average power by bucket. */
@ -82,11 +83,19 @@ TrimRange compute_trim_range(
const PowerBuckets& buckets, const PowerBuckets& buckets,
uint8_t cutoff_percent); 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. */ /* Trims the capture file with the specified range. */
bool trim_capture_with_range( bool trim_capture_with_range(
const std::filesystem::path& path, const std::filesystem::path& path,
TrimRange range, TrimRange range,
const std::function<void(uint8_t)>& on_progress); const std::function<void(uint8_t)>& on_progress,
const uint32_t amplification);
} // namespace iq } // namespace iq

View File

@ -322,7 +322,7 @@ void RecordView::trim_capture() {
auto trim_range = iq::compute_trim_range(*info, power_buckets, 7); auto trim_range = iq::compute_trim_range(*info, power_buckets, 7);
trim_ui.show_trimming(); 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(); trim_ui.clear();