IQ Trim app and Capture auto-trim (#1456)

* Add IQ Trim app WIP
* Add 'trim' checkbox to capture
* Implement trimming
* Finish auto-trim
* Stray //
This commit is contained in:
Kyle Reed 2023-09-21 08:43:10 -07:00 committed by GitHub
parent 070eb4003e
commit ef03f020ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 537 additions and 51 deletions

View File

@ -238,43 +238,64 @@ set(CPPSRC
ui/ui_textentry.cpp
ui/ui_tone_key.cpp
ui/ui_transmitter.cpp
apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/analog_tv_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
apps/gps_sim_app.cpp
apps/lge_app.cpp
apps/lge_app.cpp
apps/pocsag_app.cpp
# apps/replay_app.cpp
apps/soundboard_app.cpp
apps/tpms_app.cpp
apps/tpms_app.cpp
apps/ui_about_simple.cpp
apps/ui_adsb_rx.cpp
apps/ui_adsb_tx.cpp
apps/ui_afsk_rx.cpp
apps/ui_aprs_rx.cpp
apps/ui_btle_rx.cpp
apps/ui_nrf_rx.cpp
apps/ui_aprs_tx.cpp
apps/ui_bht_tx.cpp
apps/ui_dfu_menu.cpp
apps/ui_btle_rx.cpp
apps/ui_coasterp.cpp
apps/ui_debug.cpp
apps/ui_dfu_menu.cpp
apps/ui_encoders.cpp
apps/ui_fileman.cpp
apps/ui_flash_utility.cpp
apps/ui_sd_over_usb.cpp
apps/ui_freqman.cpp
apps/ui_iq_trim.cpp
apps/ui_jammer.cpp
#apps/ui_keyfob.cpp
# apps/ui_keyfob.cpp
apps/ui_lcr.cpp
apps/lge_app.cpp
apps/ui_level.cpp
apps/ui_looking_glass_app.cpp
apps/ui_mictx.cpp
apps/ui_modemsetup.cpp
apps/ui_morse.cpp
apps/ui_nrf_rx.cpp
# apps/ui_nuoptix.cpp
apps/ui_playlist.cpp
apps/ui_pocsag_tx.cpp
apps/ui_rds.cpp
apps/ui_recon_settings.cpp
apps/ui_recon.cpp
apps/ui_remote.cpp
apps/ui_scanner.cpp
apps/ui_search.cpp
apps/ui_sd_over_usb.cpp
apps/ui_sd_wipe.cpp
apps/ui_search.cpp
apps/ui_settings.cpp
apps/ui_siggen.cpp
apps/ui_sonde.cpp
apps/ui_sstvtx.cpp
apps/ui_spectrum_painter_image.cpp
apps/ui_spectrum_painter_text.cpp
apps/ui_spectrum_painter.cpp
apps/ui_ss_viewer.cpp
apps/ui_sstvtx.cpp
# apps/ui_test.cpp
apps/ui_text_editor.cpp
apps/ui_tone_search.cpp
@ -282,26 +303,6 @@ set(CPPSRC
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
apps/ui_whipcalc.cpp
apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/analog_tv_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
apps/lge_app.cpp
apps/pocsag_app.cpp
# apps/replay_app.cpp
apps/ui_playlist.cpp
apps/gps_sim_app.cpp
apps/soundboard_app.cpp
apps/ui_recon.cpp
apps/ui_recon_settings.cpp
apps/ui_level.cpp
apps/tpms_app.cpp
apps/tpms_app.cpp
apps/ui_spectrum_painter.cpp
apps/ui_spectrum_painter_image.cpp
apps/ui_spectrum_painter_text.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp

View File

@ -44,6 +44,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
&field_vga,
&option_bandwidth,
&option_format,
&check_trim,
&record_view,
&waterfall,
});
@ -59,6 +60,10 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
record_view.set_file_type((RecordView::FileType)file_type);
};
check_trim.on_select = [this](Checkbox&, bool v) {
record_view.set_auto_trim(v);
};
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
@ -92,7 +97,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
};
receiver_model.enable();
option_bandwidth.set_by_value(500000); // better by_value than by option_bandwidth.set_selected_index(4), Preselected default option 500kHz.
option_bandwidth.set_by_value(500000);
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);

View File

@ -92,6 +92,12 @@ class CaptureAppView : public View {
{{"C16", RecordView::FileType::RawS16},
{"C8", RecordView::FileType::RawS8}}};
Checkbox check_trim{
{23 * 8, 1 * 16},
4,
"Trim",
/*small*/ true};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
u"BBD_????.*",

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_iq_trim.hpp"
#include "complex.hpp"
#include "portapack.hpp"
#include "ui_fileman.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
IQTrimView::IQTrimView(NavigationView& nav) {
add_children({
&labels,
&field_path,
&text_range,
&button_trim,
});
field_path.on_select = [this, &nav](TextField&) {
auto open_view = nav.push<FileLoadView>(".C*");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
read_capture(path);
path_ = std::move(path);
refresh_ui();
};
};
button_trim.on_select = [this, &nav](Button&) {
if (!path_.empty() && trim_range_.file_size > 0) {
progress_ui.show_trimming();
TrimFile(path_, trim_range_);
read_capture(path_);
refresh_ui();
} else {
nav.display_modal("Error", "Select a file first.");
}
};
}
void IQTrimView::paint(Painter& painter) {
if (!path_.empty()) {
// Draw power buckets.
for (size_t i = 0; i < power_buckets_.size(); ++i) {
auto amp = power_buckets_[i].power;
painter.draw_vline(
pos_lines + Point{(int)i, 0},
height_lines,
Color(amp, amp, amp));
}
// Draw trim range edges.
int start_x = screen_width * trim_range_.start / trim_range_.file_size;
int end_x = screen_width * trim_range_.end / trim_range_.file_size;
painter.draw_vline(
pos_lines + Point{start_x, 0},
height_lines,
Color::dark_green());
painter.draw_vline(
pos_lines + Point{end_x, 0},
height_lines,
Color::dark_red());
}
}
void IQTrimView::focus() {
field_path.focus();
}
void IQTrimView::refresh_ui() {
field_path.set_text(path_.filename().string());
text_range.set(to_string_dec_uint(trim_range_.start) + "-" + to_string_dec_uint(trim_range_.end));
set_dirty();
}
bool IQTrimView::read_capture(const fs::path& path) {
power_buckets_ = {};
PowerBuckets buckets{
.p = power_buckets_.data(),
.size = power_buckets_.size()};
progress_ui.show_reading();
auto range = ComputeTrimRange(path, amp_threshold, &buckets, progress_ui.get_callback());
progress_ui.clear();
if (range) {
trim_range_ = *range;
return true;
} else {
trim_range_ = {};
return false;
}
}
} // namespace ui

View File

@ -0,0 +1,108 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_IQ_TRIM_H__
#define __UI_IQ_TRIM_H__
#include "file.hpp"
#include "iq_trim.hpp"
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include <array>
namespace ui {
/* Helper for drawing progress related to IQ trimming. */
/* TODO: Ideally this would all be done on second thread. */
class TrimProgressUI {
public:
void show_reading() {
clear();
p.draw_string({6 * 8, 5 * 16}, Styles::yellow, "Reading Capture...");
}
void show_trimming() {
clear();
p.draw_string({5 * 8, 5 * 16}, Styles::yellow, "Trimming Capture...");
}
void show_progress(uint8_t percent) {
auto width = percent * screen_width / 100;
p.draw_hline({0, 6 * 16}, width, Color::yellow());
}
void clear() {
p.fill_rectangle({0 * 8, 4 * 16, screen_width, 3 * 16}, Color::black());
}
auto get_callback() {
return [this](uint8_t percent) { show_progress(percent); };
}
private:
Painter p{};
};
class IQTrimView : public View {
public:
IQTrimView(NavigationView& nav);
std::string title() const override { return "IQ Trim"; }
void paint(Painter& painter) override;
void focus() override;
private:
void refresh_ui();
bool read_capture(const std::filesystem::path& path);
std::filesystem::path path_{};
TrimRange trim_range_{};
std::array<PowerBuckets::Bucket, screen_width> power_buckets_{};
uint8_t amp_threshold = 5;
TrimProgressUI progress_ui{};
Labels labels{
{{0 * 8, 0 * 16}, "Capture File:", Color::light_grey()},
{{0 * 8, 6 * 16}, "Range:", Color::light_grey()},
};
TextField field_path{
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
"Open File..."};
Point pos_lines{0 * 8, 4 * 16};
Dim height_lines{2 * 16};
Text text_range{
{7 * 8, 6 * 16, 20 * 8, 1 * 16},
{}};
Button button_trim{
{11 * 8, 9 * 16, 8 * 8, 3 * 16},
"Trim"};
};
} /* namespace ui */
#endif /*__UI_IQ_TRIM_H__*/

View File

@ -279,6 +279,46 @@ std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::
return directory_list;
}
std::filesystem::filesystem_error trim_file(const std::filesystem::path& file_path, uint64_t start, uint64_t length) {
constexpr size_t buffer_size = std::filesystem::max_file_block_size;
uint8_t buffer[buffer_size];
auto temp_path = file_path + u"-tmp";
/* Scope for File instances. */
{
File src;
File dst;
auto error = src.open(file_path);
if (error) return error.value();
error = dst.create(temp_path);
if (error) return error.value();
src.seek(start);
auto remaining = length;
while (true) {
auto result = src.read(buffer, buffer_size);
if (result.is_error()) return result.error();
auto to_write = std::min(remaining, *result);
result = dst.write(buffer, to_write);
if (result.is_error()) return result.error();
remaining -= *result;
if (*result < buffer_size || remaining == 0)
break;
}
}
// Delete original and overwrite with temp file.
delete_file(file_path);
return rename_file(temp_path, file_path);
}
std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path) {
return {f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str()))};
}
@ -292,8 +332,7 @@ std::filesystem::filesystem_error rename_file(
std::filesystem::filesystem_error copy_file(
const std::filesystem::path& file_path,
const std::filesystem::path& dest_path) {
// 512 seems to be the largest block size FatFS likes.
constexpr size_t buffer_size = 512;
constexpr size_t buffer_size = std::filesystem::max_file_block_size;
uint8_t buffer[buffer_size];
File src;
File dst;

View File

@ -173,6 +173,9 @@ uint8_t capture_file_sample_size(const path& filename);
using file_status = BYTE;
/* The largest block that can be read/written to a file. */
constexpr uint16_t max_file_block_size = 512;
static_assert(sizeof(path::value_type) == 2, "sizeof(std::filesystem::path::value_type) != 2");
static_assert(sizeof(path::value_type) == sizeof(TCHAR), "FatFs TCHAR size != std::filesystem::path::value_type");
@ -262,6 +265,7 @@ struct FATTimestamp {
uint16_t FAT_time;
};
std::filesystem::filesystem_error trim_file(const std::filesystem::path& file_path, uint64_t start, uint64_t length);
std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path);
std::filesystem::filesystem_error rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
std::filesystem::filesystem_error copy_file(const std::filesystem::path& file_path, const std::filesystem::path& dest_path);

View File

@ -0,0 +1,174 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __IQ_TRIM_H__
#define __IQ_TRIM_H__
#include "complex.hpp"
#include "file.hpp"
#include "optional.hpp"
#include <functional>
#include <limits>
struct TrimRange {
uint64_t start;
uint64_t end;
uint64_t sample_count;
uint64_t file_size;
uint8_t sample_size;
};
struct PowerBuckets {
struct Bucket {
uint8_t power;
};
Bucket* p = nullptr;
size_t size = 0;
void add(size_t index) {
// This originally was meant to be an average power for the bucket,
// but it was a lot of extra math just for a little bit of UI and
// the math really slowed down processing. Instead, just count the
// number of samples above the threshold.
if (index < size) {
if (p[index].power < 255)
p[index].power++;
}
}
};
inline bool TrimFile(const std::filesystem::path& path, TrimRange range) {
// NB: range.end should be included in the trimmed result, so '+ sample_size'.
auto result = trim_file(path, range.start, (range.end - range.start) + range.sample_size);
return result.ok();
}
template <typename T>
class IQTrimmer {
static constexpr uint8_t max_amp = 0xFF;
static constexpr typename T::value_type max_value = std::numeric_limits<typename T::value_type>::max();
static constexpr uint32_t max_mag_squared{2 * (max_value * max_value)}; // NB: Braces to detect narrowing.
public:
static Optional<TrimRange> ComputeTrimRange(
const std::filesystem::path& path,
uint8_t amp_threshold, // 0 - 255
PowerBuckets* buckets,
std::function<void(uint8_t)> on_progress) {
TrimRange range{};
File f;
auto error = f.open(path);
if (error)
return {};
constexpr size_t buffer_size = std::filesystem::max_file_block_size;
uint8_t buffer[buffer_size];
bool has_start = false;
size_t sample_index = 0;
File::Offset offset = 0;
uint8_t last_progress = 0;
size_t samples_per_bucket = 1;
// Scale from 0-255 to 0-max_mag_squared.
uint32_t threshold = (max_mag_squared * amp_threshold) / max_amp;
T value{};
range.file_size = f.size();
range.sample_size = sizeof(T);
range.sample_count = range.file_size / range.sample_size;
if (buckets)
samples_per_bucket = std::max(1ULL, range.sample_count / buckets->size);
while (true) {
auto result = f.read(buffer, buffer_size);
if (!result)
return {};
for (size_t i = 0; i < *result; i += sizeof(T)) {
++sample_index;
value = *reinterpret_cast<T*>(&buffer[i]);
auto real = value.real();
auto imag = value.imag();
uint32_t mag_squared = (real * real) + (imag * imag);
// Update range if above threshold.
if (mag_squared >= threshold) {
if (has_start) {
range.end = offset + i;
} else {
range.start = offset + i;
range.end = range.start;
has_start = true;
}
// Update the optional power bucket.
if (buckets) {
auto bucket_index = sample_index / samples_per_bucket;
buckets->add(bucket_index);
}
}
}
if (*result < buffer_size)
break;
offset += *result;
if (on_progress) {
uint8_t current_progress = 100 * offset / range.file_size;
if (last_progress != current_progress) {
on_progress(current_progress);
last_progress = current_progress;
}
}
}
return range;
}
};
inline Optional<TrimRange> ComputeTrimRange(
const std::filesystem::path& path,
uint8_t amp_threshold = 5,
PowerBuckets* buckets = nullptr,
std::function<void(uint8_t)> on_progress = nullptr) {
Optional<TrimRange> range;
auto sample_size = std::filesystem::capture_file_sample_size(path);
switch (sample_size) {
case sizeof(complex16_t):
return IQTrimmer<complex16_t>::ComputeTrimRange(path, amp_threshold, buckets, on_progress);
case sizeof(complex8_t):
return IQTrimmer<complex8_t>::ComputeTrimRange(path, amp_threshold, buckets, on_progress);
default:
return {};
};
}
#endif /*__IQ_TRIM_H__*/

View File

@ -24,60 +24,62 @@
// #include "modules.h"
#include "portapack.hpp"
#include "event_m0.hpp"
#include "bmp_splash.hpp"
#include "bmp_modal_warning.hpp"
#include "bmp_splash.hpp"
#include "event_m0.hpp"
#include "portapack_persistent_memory.hpp"
#include "portapack_shared_memory.hpp"
#include "portapack.hpp"
#include "ui_about_simple.hpp"
#include "ui_adsb_rx.hpp"
#include "ui_adsb_tx.hpp"
#include "ui_afsk_rx.hpp"
#include "ui_aprs_rx.hpp"
#include "ui_btle_rx.hpp"
#include "ui_nrf_rx.hpp"
#include "ui_aprs_tx.hpp"
#include "ui_bht_tx.hpp"
#include "ui_btle_rx.hpp"
#include "ui_coasterp.hpp"
#include "ui_debug.hpp"
#include "ui_encoders.hpp"
#include "ui_fileman.hpp"
#include "ui_flash_utility.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_freqman.hpp"
#include "ui_iq_trim.hpp"
#include "ui_jammer.hpp"
// #include "ui_keyfob.hpp"
#include "ui_lcr.hpp"
#include "ui_level.hpp"
#include "ui_looking_glass_app.hpp"
#include "ui_mictx.hpp"
#include "ui_morse.hpp"
#include "ui_nrf_rx.hpp"
// #include "ui_numbers.hpp"
// #include "ui_nuoptix.hpp"
// #include "ui_playdead.hpp"
#include "ui_playlist.hpp"
#include "ui_pocsag_tx.hpp"
#include "ui_rds.hpp"
#include "ui_recon.hpp"
#include "ui_remote.hpp"
#include "ui_scanner.hpp"
#include "ui_search.hpp"
#include "ui_recon.hpp"
#include "ui_level.hpp"
#include "ui_sd_over_usb.hpp"
#include "ui_sd_wipe.hpp"
#include "ui_search.hpp"
#include "ui_settings.hpp"
#include "ui_siggen.hpp"
#include "ui_sonde.hpp"
#include "ui_spectrum_painter.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_sstvtx.hpp"
#include "ui_styles.hpp"
// #include "ui_test.hpp"
#include "ui_text_editor.hpp"
#include "ui_tone_search.hpp"
#include "ui_touchtunes.hpp"
#include "ui_playlist.hpp"
#include "ui_view_wav.hpp"
#include "ui_whipcalc.hpp"
#include "ui_flash_utility.hpp"
#include "ui_sd_over_usb.hpp"
#include "ui_spectrum_painter.hpp"
#include "ui_ss_viewer.hpp"
// #include "acars_app.hpp"
#include "ais_app.hpp"
@ -85,15 +87,14 @@
#include "analog_tv_app.hpp"
#include "capture_app.hpp"
#include "ert_app.hpp"
#include "gps_sim_app.hpp"
#include "lge_app.hpp"
#include "pocsag_app.hpp"
#include "replay_app.hpp"
#include "gps_sim_app.hpp"
#include "soundboard_app.hpp"
#include "tpms_app.hpp"
#include "core_control.hpp"
#include "ui_looking_glass_app.hpp"
#include "file.hpp"
#include "png_writer.hpp"
@ -562,6 +563,7 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
{"File Manager", Color::green(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }},
{"Freq. Manager", Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }},
{"Notepad", Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<TextEditorView>(); }},
{"IQ Trim", Color::orange(), &bitmap_icon_cut, [&nav]() { nav.push<IQTrimView>(); }},
{"SD Over USB", Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
{"Signal Gen", Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }},
// {"Test App", Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); }},
@ -597,7 +599,6 @@ SystemMenuView::SystemMenuView(NavigationView& nav) {
{"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<PlaylistView>(); }},
// {"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
{"Remote", ui::Color::green(), &bitmap_icon_remote, [&nav]() { nav.push<RemoteView>(); }},
{"Scanner", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ScannerView>(); }},
{"Microphone", Color::green(), &bitmap_icon_microphone, [&nav]() { nav.push<MicTXView>(); }},

View File

@ -163,6 +163,7 @@ void RecordView::start() {
text_record_filename.set("");
text_record_dropped.set("");
trim_path = {};
if (sampling_rate == 0) {
return;
@ -217,7 +218,8 @@ void RecordView::start() {
}
auto p = std::make_unique<FileConvertWriter>();
auto create_error = p->create(base_path.replace_extension((file_type == FileType::RawS8) ? u".C8" : u".C16"));
trim_path = base_path.replace_extension((file_type == FileType::RawS8) ? u".C8" : u".C16");
auto create_error = p->create(trim_path);
if (create_error.is_valid()) {
handle_error(create_error.value());
} else {
@ -257,6 +259,7 @@ void RecordView::stop() {
if (is_active()) {
capture_thread.reset();
button_record.set_bitmap(&bitmap_record);
trim_capture();
}
update_status_display();
@ -299,6 +302,26 @@ void RecordView::update_status_display() {
}
}
void RecordView::trim_capture() {
if (file_type != FileType::WAV && auto_trim && !trim_path.empty()) {
trim_ui.show_reading();
auto range = ComputeTrimRange(
trim_path,
/*threshold*/ 5,
/*power array*/ nullptr,
trim_ui.get_callback());
if (range) {
trim_ui.show_trimming();
TrimFile(trim_path, *range);
}
trim_ui.clear();
}
trim_path = {};
}
void RecordView::handle_capture_thread_done(const File::Error error) {
stop();
if (error.code()) {

View File

@ -22,12 +22,13 @@
#ifndef __UI_RECORD_VIEW_H__
#define __UI_RECORD_VIEW_H__
#include "ui_iq_trim.hpp"
#include "ui_widget.hpp"
#include "capture_thread.hpp"
#include "signal.hpp"
#include "bitmap.hpp"
#include "capture_thread.hpp"
#include "iq_trim.hpp"
#include "signal.hpp"
#include <cstddef>
#include <string>
@ -63,6 +64,7 @@ class RecordView : public View {
uint32_t set_sampling_rate(uint32_t new_sampling_rate);
void set_file_type(const FileType v) { file_type = v; }
void set_auto_trim(bool v) { auto_trim = v; }
void start();
void stop();
@ -78,6 +80,7 @@ class RecordView : public View {
void on_tick_second();
void update_status_display();
void trim_capture();
void handle_capture_thread_done(const File::Error error);
void handle_error(const File::Error error);
@ -98,6 +101,10 @@ class RecordView : public View {
uint32_t sampling_rate{0};
SignalToken signal_token_tick_second{};
bool auto_trim = false;
std::filesystem::path trim_path{};
TrimProgressUI trim_ui{};
Rectangle rect_background{
Color::black()};