diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index e003acbf8..653b1d5b4 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -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 diff --git a/firmware/application/apps/capture_app.cpp b/firmware/application/apps/capture_app.cpp index bb613bc12..999d4176e 100644 --- a/firmware/application/apps/capture_app.cpp +++ b/firmware/application/apps/capture_app.cpp @@ -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); diff --git a/firmware/application/apps/capture_app.hpp b/firmware/application/apps/capture_app.hpp index 627d2b2f0..d643bb435 100644 --- a/firmware/application/apps/capture_app.hpp +++ b/firmware/application/apps/capture_app.hpp @@ -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_????.*", diff --git a/firmware/application/apps/ui_iq_trim.cpp b/firmware/application/apps/ui_iq_trim.cpp new file mode 100644 index 000000000..1a9f25df4 --- /dev/null +++ b/firmware/application/apps/ui_iq_trim.cpp @@ -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(".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 \ No newline at end of file diff --git a/firmware/application/apps/ui_iq_trim.hpp b/firmware/application/apps/ui_iq_trim.hpp new file mode 100644 index 000000000..345ca1da0 --- /dev/null +++ b/firmware/application/apps/ui_iq_trim.hpp @@ -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 + +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 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__*/ diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp index e44db141a..709f1a466 100644 --- a/firmware/application/file.cpp +++ b/firmware/application/file.cpp @@ -279,6 +279,46 @@ std::vector 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(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; diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index d2f839623..eb94712bf 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -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); diff --git a/firmware/application/iq_trim.hpp b/firmware/application/iq_trim.hpp new file mode 100644 index 000000000..cc53f2366 --- /dev/null +++ b/firmware/application/iq_trim.hpp @@ -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 +#include + +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 +class IQTrimmer { + static constexpr uint8_t max_amp = 0xFF; + static constexpr typename T::value_type max_value = std::numeric_limits::max(); + static constexpr uint32_t max_mag_squared{2 * (max_value * max_value)}; // NB: Braces to detect narrowing. + + public: + static Optional ComputeTrimRange( + const std::filesystem::path& path, + uint8_t amp_threshold, // 0 - 255 + PowerBuckets* buckets, + std::function 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(&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 ComputeTrimRange( + const std::filesystem::path& path, + uint8_t amp_threshold = 5, + PowerBuckets* buckets = nullptr, + std::function on_progress = nullptr) { + Optional range; + auto sample_size = std::filesystem::capture_file_sample_size(path); + + switch (sample_size) { + case sizeof(complex16_t): + return IQTrimmer::ComputeTrimRange(path, amp_threshold, buckets, on_progress); + + case sizeof(complex8_t): + return IQTrimmer::ComputeTrimRange(path, amp_threshold, buckets, on_progress); + + default: + return {}; + }; +} + +#endif /*__IQ_TRIM_H__*/ \ No newline at end of file diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index ee881c311..196dedb2a 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -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(); }}, {"Freq. Manager", Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push(); }}, {"Notepad", Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push(); }}, + {"IQ Trim", Color::orange(), &bitmap_icon_cut, [&nav]() { nav.push(); }}, {"SD Over USB", Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push(); }}, {"Signal Gen", Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push(); }}, // {"Test App", Color::dark_grey(), nullptr, [&nav](){ nav.push(); }}, @@ -597,7 +599,6 @@ SystemMenuView::SystemMenuView(NavigationView& nav) { {"Transmit", Color::cyan(), &bitmap_icon_transmit, [&nav]() { nav.push(); }}, {"Capture", Color::red(), &bitmap_icon_capture, [&nav]() { nav.push(); }}, {"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push(); }}, - // {"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push(); }}, {"Remote", ui::Color::green(), &bitmap_icon_remote, [&nav]() { nav.push(); }}, {"Scanner", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push(); }}, {"Microphone", Color::green(), &bitmap_icon_microphone, [&nav]() { nav.push(); }}, diff --git a/firmware/application/ui_record_view.cpp b/firmware/application/ui_record_view.cpp index 83b9e5f70..2ad9f71aa 100644 --- a/firmware/application/ui_record_view.cpp +++ b/firmware/application/ui_record_view.cpp @@ -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(); - 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()) { diff --git a/firmware/application/ui_record_view.hpp b/firmware/application/ui_record_view.hpp index 710ff8862..c062a4544 100644 --- a/firmware/application/ui_record_view.hpp +++ b/firmware/application/ui_record_view.hpp @@ -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 #include @@ -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()};