diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index d3e48644..9f1b8373 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -195,7 +195,7 @@ set(CPPSRC ui_rds.cpp ui_receiver.cpp ui_record_view.cpp - ui_replay_view.cpp + # ui_replay_view.cpp ui_rssi.cpp ui_scanner.cpp # ui_script.cpp diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index a02c755d..07a4bb25 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -367,6 +367,28 @@ static constexpr Bitmap bitmap_record { { 16, 16 }, bitmap_record_data }; +static constexpr uint8_t bitmap_icon_file_text_data[] = { + 0x00, 0x20, + 0x00, 0x30, + 0x00, 0x38, + 0x00, 0x38, + 0x00, 0x34, + 0x00, 0x32, + 0x00, 0x31, + 0x80, 0x30, + 0xC0, 0x30, + 0xE0, 0x3F, + 0x30, 0x30, + 0x18, 0x30, + 0x0C, 0x30, + 0x0E, 0x78, + 0x1F, 0xFC, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_icon_file_text { + { 16, 16 }, bitmap_icon_file_text_data +}; + static constexpr uint8_t bitmap_icon_ais_data[] = { 0x00, 0x01, 0x80, 0x01, @@ -449,6 +471,28 @@ static constexpr Bitmap bitmap_icon_nuoptix { { 16, 16 }, bitmap_icon_nuoptix_data }; +static constexpr uint8_t bitmap_icon_file_iq_data[] = { + 0x98, 0x00, + 0x24, 0x06, + 0xA4, 0x08, + 0x34, 0x10, + 0xB8, 0x20, + 0x20, 0x20, + 0x80, 0x00, + 0xD5, 0x55, + 0x80, 0x00, + 0x02, 0x70, + 0x82, 0x20, + 0x04, 0x20, + 0x88, 0x20, + 0x30, 0x70, + 0x80, 0x00, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_icon_file_iq { + { 16, 16 }, bitmap_icon_file_iq_data +}; + static constexpr uint8_t bitmap_icon_closecall_data[] = { 0x00, 0x00, 0x00, 0x10, @@ -1751,6 +1795,28 @@ static constexpr Bitmap bitmap_bulb_off { { 24, 24 }, bitmap_bulb_off_data }; +static constexpr uint8_t bitmap_icon_file_image_data[] = { + 0x00, 0x00, + 0xFF, 0xFF, + 0x01, 0x80, + 0x01, 0x80, + 0x89, 0x80, + 0xC1, 0x81, + 0xE1, 0xA3, + 0xB1, 0xB3, + 0x89, 0xDC, + 0x07, 0x8C, + 0x01, 0x90, + 0x01, 0x80, + 0xAB, 0x82, + 0xFF, 0xD5, + 0xFF, 0xFF, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_icon_file_image { + { 16, 16 }, bitmap_icon_file_image_data +}; + } /* namespace ui */ diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index c5ea1b1e..3e2690bd 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -233,13 +233,14 @@ space_info space(const path& p); } /* namespace filesystem */ } /* namespace std */ -std::vector scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension); -std::vector scan_root_directories(const std::filesystem::path& directory); -std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_stem_pattern); void delete_file(const std::filesystem::path& file_path); void rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name); uint32_t make_new_directory(const std::filesystem::path& dir_path); +std::vector scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension); +std::vector scan_root_directories(const std::filesystem::path& directory); +std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_stem_pattern); + /* Values added to FatFs FRESULT enum, values outside the FRESULT data type */ static_assert(sizeof(FIL::err) == 1, "FatFs FIL::err size not expected."); diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index cf502e43..32eeaded 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -27,13 +27,14 @@ //TEST: Check AFSK transmit end, skips last bits ? //TEST: Imperial in whipcalc -//BUG: Crash on rename file with long filename //BUG: Auto backlight off doesn't work anymore -//BUG: CPLD-related rx ok, tx bad, see portapack.cpp lines 214+ to disable CPLD overlay -//BUG: REPLAY See what's wrong with quality (format, or need for interpolation filter ?) +//BUG: (Workaround ok) CPLD-related rx ok, tx bad, see portapack.cpp lines 214+ to disable CPLD overlay //BUG: SCANNER Lock on frequency, if frequency jump, still locked on first one //BUG: SCANNER Multiple slices +//TODO: Display file creation/modification date in FileLoadView +//TODO: Display recording frequency in Replay (from associated .txt file, if present) +//TODO: Clean up ReplayThread //TODO: Cap Wav viewer position //TODO: Adapt wav viewer position step //TODO: Use unit_auto_scale @@ -80,10 +81,7 @@ Continuous (Fox-oring) // Old or low-priority stuff: //TODO: Bodet :) //TODO: Analog TV tx with camcorder font character generator -//TODO: Show address/data bit fields in OOK TX //TODO: Scan for OOK TX -//TODO: Script engine ? -//TODO: AFSK receiver //TODO: Check more OOK encoders //BUG (fixed ?): No audio in about when shown second time //TODO: Show MD5 mismatches for modules not found, etc... diff --git a/firmware/application/replay_app.cpp b/firmware/application/replay_app.cpp index ed4592cf..118c6201 100644 --- a/firmware/application/replay_app.cpp +++ b/firmware/application/replay_app.cpp @@ -23,6 +23,9 @@ #include "replay_app.hpp" #include "string_format.hpp" +#include "ui_fileman.hpp" +#include "io_file.hpp" + #include "baseband_api.hpp" #include "portapack.hpp" #include "portapack_persistent_memory.hpp" @@ -31,31 +34,132 @@ using namespace portapack; namespace ui { +void ReplayAppView::set_ready() { + ready_signal = true; +} + +void ReplayAppView::on_file_changed(std::filesystem::path new_file_path) { + File bbd_file; + std::string str_duration = ""; + + file_path = new_file_path; + + text_filename.set(new_file_path.string().substr(0, 18)); + + bbd_file.open("/" + new_file_path.string()); + auto file_size = bbd_file.size(); + auto duration = file_size / (2 * 2 * sampling_rate / 8); + + progressbar.set_max(file_size); + + if (duration >= 60) + str_duration = to_string_dec_uint(duration / 60) + "m"; + + text_duration.set(str_duration + to_string_dec_uint(duration % 60) + "s"); + + button_play.focus(); +} + +void ReplayAppView::on_tx_progress(const uint32_t progress) { + progressbar.set_value(progress); +} + +void ReplayAppView::focus() { + button_open.focus(); +} + +bool ReplayAppView::is_active() const { + return (bool)replay_thread; +} + +void ReplayAppView::toggle() { + if( is_active() ) { + stop(); + } else { + start(); + } +} + +void ReplayAppView::start() { + stop(); + + std::unique_ptr reader; + + auto p = std::make_unique(); + auto open_error = p->open(file_path); + if( open_error.is_valid() ) { + handle_error(open_error.value()); + } else { + reader = std::move(p); + } + + if( reader ) { + button_play.set_bitmap(&bitmap_stop); + replay_thread = std::make_unique( + std::move(reader), + read_size, buffer_count, + &ready_signal, + []() { + ReplayThreadDoneMessage message { }; + EventDispatcher::send_message(message); + }, + [](File::Error error) { + ReplayThreadDoneMessage message { error.code() }; + EventDispatcher::send_message(message); + } + ); + } + + radio::enable({ + receiver_model.tuning_frequency(), + sampling_rate, + baseband_bandwidth, + rf::Direction::Transmit, + receiver_model.rf_amp(), + static_cast(receiver_model.lna()), + static_cast(receiver_model.vga()) + }); +} + +void ReplayAppView::stop() { + if( is_active() ) + replay_thread.reset(); + + progressbar.set_value(0); + + radio::disable(); + button_play.set_bitmap(&bitmap_play); +} + +void ReplayAppView::handle_replay_thread_done(const File::Error error) { + stop(); + if( error.code() ) { + handle_error(error); + } +} + +void ReplayAppView::handle_error(const File::Error error) { + nav_.display_modal("Error", error.what()); +} + ReplayAppView::ReplayAppView( NavigationView& nav ) : nav_ (nav) { - std::vector file_list; - - // Search for files with the right extension - file_list = scan_root_files(u"/", u"*.C16"); - if (!file_list.size()) { - file_error = true; - return; - } - baseband::run_image(portapack::spi_flash::image_tag_replay); add_children({ &field_frequency, &field_frequency_step, &field_rf_amp, - &replay_view, + &button_play, + &text_filename, + &text_duration, + &progressbar, + &button_open, &waterfall, }); - replay_view.set_file_list(file_list); - field_frequency.set_value(target_frequency()); field_frequency.set_step(receiver_model.frequency_step()); field_frequency.on_change = [this](rf::Frequency f) { @@ -75,9 +179,16 @@ ReplayAppView::ReplayAppView( receiver_model.set_frequency_step(v); this->field_frequency.set_step(v); }; - - replay_view.on_error = [&nav](std::string message) { - nav.display_modal("Error", message); + + button_play.on_select = [this](ImageButton&) { + this->toggle(); + }; + + button_open.on_select = [this, &nav](Button&) { + auto new_view = nav.push(".C16"); + new_view->on_changed = [this](std::filesystem::path new_file_path) { + on_file_changed(new_file_path); + }; }; } @@ -100,13 +211,6 @@ void ReplayAppView::set_parent_rect(const Rect new_parent_rect) { waterfall.set_parent_rect(waterfall_rect); } -void ReplayAppView::focus() { - if (!file_error) { - field_frequency.focus(); - } else - nav_.display_modal("No files", "No .C16 files in\nSD card root", ABORT, nullptr); -} - void ReplayAppView::on_target_frequency_changed(rf::Frequency f) { set_target_frequency(f); } diff --git a/firmware/application/replay_app.hpp b/firmware/application/replay_app.hpp index b5f4634a..af007f19 100644 --- a/firmware/application/replay_app.hpp +++ b/firmware/application/replay_app.hpp @@ -26,7 +26,7 @@ #include "ui_widget.hpp" #include "ui_navigation.hpp" #include "ui_receiver.hpp" -#include "ui_replay_view.hpp" +#include "replay_thread.hpp" #include "ui_spectrum.hpp" #include @@ -40,24 +40,40 @@ public: ~ReplayAppView(); void on_hide() override; - void set_parent_rect(const Rect new_parent_rect) override; - void focus() override; - std::string title() const override { return "Replay (BETA)"; }; + std::string title() const override { return "Replay"; }; + + void start(); + void stop(); + bool is_active() const; private: NavigationView& nav_; - bool file_error { false }; + static constexpr ui::Dim header_height = 3 * 16; - static constexpr ui::Dim header_height = 2 * 16; + static constexpr uint32_t sampling_rate = 4000000; + static constexpr uint32_t baseband_bandwidth = 2500000; + const size_t read_size { 16384 }; + const size_t buffer_count { 3 }; + void on_file_changed(std::filesystem::path new_file_path); void on_target_frequency_changed(rf::Frequency f); - - rf::Frequency target_frequency() const; + void on_tx_progress(const uint32_t progress); + void set_target_frequency(const rf::Frequency new_value); + rf::Frequency target_frequency() const; + + void toggle(); + void set_ready(); + void handle_replay_thread_done(const File::Error error); + void handle_error(const File::Error error); + + std::filesystem::path file_path { }; + std::unique_ptr replay_thread { }; + bool ready_signal { false }; FrequencyField field_frequency { { 0 * 8, 0 * 16 }, @@ -71,12 +87,57 @@ private: { 16 * 8, 0 * 16 } }; - ReplayView replay_view { - { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, - 16384, 3 + ImageButton button_play { + { 0 * 8, 1 * 16 + 8, 2 * 8, 1 * 16 }, + &bitmap_play, + Color::green(), + Color::black() + }; + + Text text_filename { + { 2 * 8, 1 * 16, 18 * 8, 16 }, + "-" + }; + Text text_duration { + { 2 * 8, 2 * 16, 6 * 8, 16 }, + "-" + }; + ProgressBar progressbar { + { 9 * 8, 2 * 16, 10 * 8, 16 } + }; + + Button button_open { + { 20 * 8, 1 * 16, 10 * 8, 2 * 16 }, + "Open file" }; spectrum::WaterfallWidget waterfall { }; + + MessageHandlerRegistration message_handler_replay_thread_error { + Message::ID::ReplayThreadDone, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->handle_replay_thread_done(message.error); + } + }; + + MessageHandlerRegistration message_handler_fifo_signal { + Message::ID::RequestSignal, + [this](const Message* const p) { + const auto message = static_cast(p); + if (message->signal == RequestSignalMessage::Signal::FillRequest) { + this->set_ready(); + } + } + }; + + MessageHandlerRegistration message_handler_tx_progress { + Message::ID::TXProgress, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.progress); + } + }; }; } /* namespace ui */ diff --git a/firmware/application/replay_thread.cpp b/firmware/application/replay_thread.cpp index 8c43e6eb..5d98c6b6 100644 --- a/firmware/application/replay_thread.cpp +++ b/firmware/application/replay_thread.cpp @@ -41,10 +41,12 @@ ReplayThread::ReplayThread( std::unique_ptr reader, size_t read_size, size_t buffer_count, + bool* ready_signal, std::function success_callback, std::function error_callback ) : config { read_size, buffer_count }, reader { std::move(reader) }, + ready_sig { ready_signal }, success_callback { std::move(success_callback) }, error_callback { std::move(error_callback) } { @@ -79,7 +81,12 @@ Optional ReplayThread::run() { StreamBuffer* prefill_buffer { nullptr }; - // TESTING: Prefill + // Wait for FIFOs to be allocated in baseband + // Wait for ui_replay_view to tell us that the buffers are ready (awful :( ) + while (!(*ready_sig)) { + chThdSleep(100); + }; + // While empty buffers fifo is not empty... while (!buffers.empty()) { prefill_buffer = buffers.get_prefill(); @@ -87,7 +94,7 @@ Optional ReplayThread::run() { if (prefill_buffer == nullptr) { buffers.put_app(prefill_buffer); } else { - size_t blocks = prefill_buffer->capacity() / 512; + size_t blocks = 16384 / 512; for (size_t c = 0; c < blocks; c++) { auto read_result = reader->read(&((uint8_t*)prefill_buffer->data())[c * 512], 512); @@ -96,23 +103,23 @@ Optional ReplayThread::run() { } } - prefill_buffer->set_size(prefill_buffer->capacity()); + prefill_buffer->set_size(16384); buffers.put(prefill_buffer); - //if (!buffers.put(prefill_buffer)) for(;;) {}; - } }; + + baseband::set_fifo_data(nullptr); while( !chThdShouldTerminate() ) { auto buffer = buffers.get(); - size_t blocks = buffer->capacity() / 512; - - for (size_t c = 0; c < blocks; c++) { - auto read_result = reader->read(&((uint8_t*)buffer->data())[c * 512], 512); - if( read_result.is_error() ) { - return read_result.error(); + auto read_result = reader->read(buffer->data(), buffer->capacity()); + if( read_result.is_error() ) { + return read_result.error(); + } else { + if (read_result.value() == 0) { + return { }; } } diff --git a/firmware/application/replay_thread.hpp b/firmware/application/replay_thread.hpp index 1ffbb461..f5c7bd34 100644 --- a/firmware/application/replay_thread.hpp +++ b/firmware/application/replay_thread.hpp @@ -40,6 +40,7 @@ public: std::unique_ptr reader, size_t read_size, size_t buffer_count, + bool* ready_signal, std::function success_callback, std::function error_callback ); @@ -57,6 +58,7 @@ public: private: ReplayConfig config; std::unique_ptr reader; + bool* ready_sig; std::function success_callback; std::function error_callback; Thread* thread { nullptr }; diff --git a/firmware/application/ui_fileman.cpp b/firmware/application/ui_fileman.cpp index b83d6a13..46c9c0b6 100644 --- a/firmware/application/ui_fileman.cpp +++ b/firmware/application/ui_fileman.cpp @@ -32,14 +32,24 @@ namespace ui { void FileManBaseView::load_directory_contents(const std::filesystem::path& dir_path) { current_path = dir_path; - text_current.set(dir_path.string()); + text_current.set(dir_path.string().substr(0, 30 - 8)); entry_list.clear(); - // List all directories and files, put directories up top + auto filtering = (bool)extension_filter.size(); + + // List directories and files, put directories up top for (const auto& entry : std::filesystem::directory_iterator(dir_path, u"*")) { if (std::filesystem::is_regular_file(entry.status())) { - entry_list.push_back({ entry.path(), (uint32_t)entry.size(), false }); + if (entry.path().string().length()) { + auto entry_extension = entry.path().extension().string(); + + for (auto &c: entry_extension) + c = toupper(c); + + if ((entry_extension == extension_filter) || !filtering) + entry_list.push_back({ entry.path(), (uint32_t)entry.size(), false }); + } } else if (std::filesystem::is_directory(entry.status())) { entry_list.insert(entry_list.begin(), { entry.path(), 0, true }); } @@ -57,8 +67,10 @@ std::filesystem::path FileManBaseView::get_selected_path() { } FileManBaseView::FileManBaseView( - NavigationView& nav -) : nav_ (nav) + NavigationView& nav, + std::string filter +) : nav_ (nav), + extension_filter { filter } { load_directory_contents(current_path); @@ -95,10 +107,6 @@ void FileManBaseView::focus() { } void FileManBaseView::refresh_list() { - std::string size_str { }; - uint32_t suffix_index; - size_t file_size; - if (!entry_list.size()) { // Hide widgets, show warning if (on_refresh_widgets) @@ -111,9 +119,10 @@ void FileManBaseView::refresh_list() { menu_view.clear(); for (size_t n = 0; n < entry_list.size(); n++) { - auto entry_name = entry_list[n].entry_path.filename().string().substr(0, 20); + auto entry = &entry_list[n]; + auto entry_name = entry->entry_path.filename().string().substr(0, 20); - if (entry_list[n].is_directory) { + if (entry->is_directory) { menu_view.add_item({ entry_name, @@ -127,8 +136,8 @@ void FileManBaseView::refresh_list() { } else { - file_size = entry_list[n].size; - suffix_index = 0; + auto file_size = entry->size; + size_t suffix_index = 0; while (file_size >= 1024) { file_size /= 1024; @@ -137,12 +146,23 @@ void FileManBaseView::refresh_list() { if (suffix_index > 4) suffix_index = 4; - size_str = to_string_dec_uint(file_size) + suffix[suffix_index]; + std::string size_str = to_string_dec_uint(file_size) + suffix[suffix_index]; + + auto entry_extension = entry->entry_path.extension().string(); + for (auto &c: entry_extension) + c = toupper(c); + + // Associate extension to icon and color + size_t c; + for (c = 0; c < file_types.size() - 1; c++) { + if (entry_extension == file_types[c].extension) + break; + } menu_view.add_item({ entry_name + std::string(21 - entry_name.length(), ' ') + size_str, - ui::Color::white(), - &bitmap_icon_file, + file_types[c].color, + file_types[c].icon, [this](){ if (on_select_entry) on_select_entry(); @@ -186,8 +206,9 @@ void FileLoadView::refresh_widgets(const bool v) { } FileLoadView::FileLoadView( - NavigationView& nav -) : FileManBaseView(nav) + NavigationView& nav, + std::string filter +) : FileManBaseView(nav, filter) { on_refresh_widgets = [this](bool v) { refresh_widgets(v); @@ -216,7 +237,7 @@ FileLoadView::FileLoadView( } void FileManagerView::on_rename(NavigationView& nav) { - text_prompt(nav, &name_buffer, 12, [this](std::string * buffer) { + text_prompt(nav, &name_buffer, max_filename_length, [this](std::string * buffer) { rename_file(get_selected_path(), *buffer); load_directory_contents(current_path); refresh_list(); @@ -244,7 +265,7 @@ FileManagerView::~FileManagerView() { FileManagerView::FileManagerView( NavigationView& nav -) : FileManBaseView(nav) +) : FileManBaseView(nav, "") { on_refresh_widgets = [this](bool v) { refresh_widgets(v); @@ -271,7 +292,7 @@ FileManagerView::FileManagerView( button_new_dir.on_select = [this, &nav](Button&) { name_buffer.clear(); - text_prompt(nav, &name_buffer, 12, [this](std::string * buffer) { + text_prompt(nav, &name_buffer, max_filename_length, [this](std::string * buffer) { std::string path_str = *buffer; make_new_directory(current_path.string() + '/' + path_str); @@ -281,10 +302,8 @@ FileManagerView::FileManagerView( }; button_rename.on_select = [this, &nav](Button&) { - if (!entry_list[menu_view.highlighted()].is_directory) { - name_buffer = entry_list[menu_view.highlighted()].entry_path.filename().string(); - on_rename(nav); - } + name_buffer = entry_list[menu_view.highlighted()].entry_path.filename().string().substr(0, max_filename_length); + on_rename(nav); }; button_delete.on_select = [this, &nav](Button&) { diff --git a/firmware/application/ui_fileman.hpp b/firmware/application/ui_fileman.hpp index bc9f80e9..e081edb4 100644 --- a/firmware/application/ui_fileman.hpp +++ b/firmware/application/ui_fileman.hpp @@ -39,7 +39,8 @@ struct fileman_entry { class FileManBaseView : public View { public: FileManBaseView( - NavigationView& nav + NavigationView& nav, + std::string filter ); void focus() override; @@ -52,13 +53,31 @@ public: protected: NavigationView& nav_; + static constexpr size_t max_filename_length = 30 - 2; + const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" }; + struct file_assoc_t { + std::string extension; + const Bitmap* icon; + ui::Color color; + }; + + const std::vector file_types = { + { ".TXT", &bitmap_icon_file_text, ui::Color::white() }, + { ".PNG", &bitmap_icon_file_image, ui::Color::green() }, + { ".BMP", &bitmap_icon_file_image, ui::Color::green() }, + { ".C16", &bitmap_icon_file_iq, ui::Color::blue() }, + { ".WAV", &bitmap_icon_speaker, ui::Color::dark_magenta() }, + { "", &bitmap_icon_file, ui::Color::light_grey() } + }; + bool empty_root { false }; std::function on_select_entry { nullptr }; std::function on_refresh_widgets { nullptr }; std::vector entry_list { }; std::filesystem::path current_path { u"" }; + std::string extension_filter { "" }; void change_category(int32_t category_id); void refresh_list(); @@ -113,7 +132,7 @@ class FileLoadView : public FileManBaseView { public: std::function on_changed { }; - FileLoadView(NavigationView& nav); + FileLoadView(NavigationView& nav, std::string filter); private: void refresh_widgets(const bool v); diff --git a/firmware/application/ui_replay_view.cpp b/firmware/application/ui_replay_view.cpp deleted file mode 100644 index eba4d816..00000000 --- a/firmware/application/ui_replay_view.cpp +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. - * Copyright (C) 2016 Furrtek - * - * 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_replay_view.hpp" - -#include "portapack.hpp" -#include "message.hpp" -#include "portapack_shared_memory.hpp" -using namespace portapack; - -#include "rtc_time.hpp" -#include "io_file.hpp" - -#include "string_format.hpp" -#include "utility.hpp" - -#include - -namespace ui { - -void ReplayView::on_file_changed(const uint32_t duration) { - std::string str_duration = ""; - - if (duration >= 60) - str_duration = to_string_dec_uint(duration / 60) + "m"; - - text_duration.set(str_duration + to_string_dec_uint(duration % 60) + "s"); -} - -ReplayView::ReplayView( - const Rect parent_rect, - const size_t read_size, - const size_t buffer_count -) : View { parent_rect }, - read_size { read_size }, - buffer_count { buffer_count } -{ - add_children({ - &rect_background, - &button_play, - &options_files, - &text_duration, - //&text_time_seek, - }); - - rect_background.set_parent_rect({ { 0, 0 }, size() }); - - options_files.on_change = [this](size_t, int32_t duration) { - this->on_file_changed(duration); - }; - - button_play.on_select = [this](ImageButton&) { - this->toggle(); - }; - - /*signal_token_tick_second = rtc_time::signal_tick_second += [this]() { - this->on_tick_second(); - };*/ -} - -ReplayView::~ReplayView() { - //rtc_time::signal_tick_second -= signal_token_tick_second; -} - -void ReplayView::focus() { - options_files.focus(); -} - -void ReplayView::set_file_list(const std::vector& file_list) { - File bbd_file; - uint32_t duration; - - for (const auto& file : file_list) { - bbd_file.open("/" + file.string()); - duration = bbd_file.size() / (2 * 2 * sampling_rate / 8); - file_options.emplace_back(file.string().substr(0, 8), duration); - } - options_files.set_options(file_options); - options_files.set_selected_index(0); // First file - on_file_changed(file_options[0].second); -} - -bool ReplayView::is_active() const { - return (bool)replay_thread; -} - -void ReplayView::toggle() { - if( is_active() ) { - stop(); - } else { - start(); - } -} - -void ReplayView::start() { - stop(); - - std::unique_ptr reader; - - auto p = std::make_unique(); - auto create_error = p->open(file_options[options_files.selected_index()].first + ".C16"); - if( create_error.is_valid() ) { - handle_error(create_error.value()); - } else { - reader = std::move(p); - } - - if( reader ) { - button_play.set_bitmap(&bitmap_stop); - replay_thread = std::make_unique( - std::move(reader), - read_size, buffer_count, - []() { - ReplayThreadDoneMessage message { }; - EventDispatcher::send_message(message); - }, - [](File::Error error) { - ReplayThreadDoneMessage message { error.code() }; - EventDispatcher::send_message(message); - } - ); - } - - update_status_display(); - - radio::enable({ - receiver_model.tuning_frequency(), - sampling_rate, - 2500000, //baseband_bandwidth, - rf::Direction::Transmit, - receiver_model.rf_amp(), - static_cast(receiver_model.lna()), - static_cast(receiver_model.vga()) - }); -} - -void ReplayView::stop() { - if( is_active() ) { - replay_thread.reset(); - radio::disable(); - button_play.set_bitmap(&bitmap_play); - } - - update_status_display(); -} - -void ReplayView::on_tick_second() { - update_status_display(); -} - -void ReplayView::update_status_display() { - /*if( sampling_rate ) { - const auto space_info = std::filesystem::space(""); - const uint32_t bytes_per_second = file_type == FileType::WAV ? (sampling_rate * 2) : (sampling_rate * 4); - const uint32_t available_seconds = space_info.free / bytes_per_second; - const uint32_t seconds = available_seconds % 60; - const uint32_t available_minutes = available_seconds / 60; - const uint32_t minutes = available_minutes % 60; - const uint32_t hours = available_minutes / 60; - const std::string available_time = - to_string_dec_uint(hours, 3, ' ') + ":" + - to_string_dec_uint(minutes, 2, '0') + ":" + - to_string_dec_uint(seconds, 2, '0'); - text_time_available.set(available_time); - }*/ -} - -void ReplayView::handle_replay_thread_done(const File::Error error) { - stop(); - if( error.code() ) { - handle_error(error); - } -} - -void ReplayView::handle_error(const File::Error error) { - if( on_error ) { - on_error(error.what()); - } -} - -} /* namespace ui */ diff --git a/firmware/application/ui_replay_view.hpp b/firmware/application/ui_replay_view.hpp deleted file mode 100644 index 1db4330f..00000000 --- a/firmware/application/ui_replay_view.hpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. - * Copyright (C) 2016 Furrtek - * - * 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_REPLAY_VIEW_H__ -#define __UI_REPLAY_VIEW_H__ - -#include "ui_widget.hpp" - -#include "replay_thread.hpp" -#include "signal.hpp" -#include "bitmap.hpp" - -#include -#include -#include - -namespace ui { - -class ReplayView : public View { -public: - std::function on_error { }; - - ReplayView( - const Rect parent_rect, - const size_t read_size, - const size_t buffer_count - ); - ~ReplayView(); - - void focus() override; - - void set_file_list(const std::vector& file_list); - - void start(); - void stop(); - - bool is_active() const; - -private: - using option_t = std::pair; - using options_t = std::vector; - - static constexpr uint32_t sampling_rate = 4000000; - - void toggle(); - - void on_file_changed(const uint32_t duration); - void on_tick_second(); - void update_status_display(); - - void handle_replay_thread_done(const File::Error error); - void handle_error(const File::Error error); - - const size_t read_size; - const size_t buffer_count; - //SignalToken signal_token_tick_second { }; - options_t file_options { }; - //std::filesystem:path file_path { }; - - Rectangle rect_background { - Color::black() - }; - - ImageButton button_play { - { 0 * 8, 0 * 16, 2 * 8, 1 * 16 }, - &bitmap_play, - Color::green(), - Color::black() - }; - - OptionsField options_files { - { 2 * 8, 0 * 8 }, - 8, - { } - }; - - Text text_duration { - { 11 * 8, 0 * 8, 12 * 8, 16 }, - "-" - }; - - /*Text text_time_seek { - { 18 * 8, 0 * 16, 9 * 8, 16 }, - "", - };*/ - - std::unique_ptr replay_thread { }; - - MessageHandlerRegistration message_handler_replay_thread_error { - Message::ID::CaptureThreadDone, - [this](const Message* const p) { - const auto message = *reinterpret_cast(p); - this->handle_replay_thread_done(message.error); - } - }; -}; - -} /* namespace ui */ - -#endif/*__UI_REPLAY_VIEW_H__*/ diff --git a/firmware/application/ui_view_wav.cpp b/firmware/application/ui_view_wav.cpp index 8e403c0e..daa12a8b 100644 --- a/firmware/application/ui_view_wav.cpp +++ b/firmware/application/ui_view_wav.cpp @@ -150,7 +150,7 @@ ViewWavView::ViewWavView( }); button_open.on_select = [this, &nav](Button&) { - auto open_view = nav.push(); + auto open_view = nav.push(".WAV"); open_view->on_changed = [this](std::filesystem::path file_path) { load_wav(file_path); field_pos_seconds.focus(); diff --git a/firmware/baseband/proc_replay.cpp b/firmware/baseband/proc_replay.cpp index 0b3c8235..3b8593b1 100644 --- a/firmware/baseband/proc_replay.cpp +++ b/firmware/baseband/proc_replay.cpp @@ -22,6 +22,7 @@ #include "proc_replay.hpp" #include "sine_table_int8.hpp" +#include "portapack_shared_memory.hpp" #include "event_m4.hpp" @@ -31,16 +32,18 @@ ReplayProcessor::ReplayProcessor() { channel_filter_pass_f = taps_200k_decim_1.pass_frequency_normalized * 1000000; // 162760.416666667 channel_filter_stop_f = taps_200k_decim_1.stop_frequency_normalized * 1000000; // 337239.583333333 - spectrum_interval_samples = (baseband_fs / 8) / spectrum_rate_hz; + spectrum_interval_samples = baseband_fs / spectrum_rate_hz; spectrum_samples = 0; channel_spectrum.set_decimation_factor(1); + + configured = false; } void ReplayProcessor::execute(const buffer_c8_t& buffer) { /* 4MHz, 2048 samples */ - - size_t pos = 0; + + if (!configured) return; // File data is in C16 format, we need C8 // File samplerate is 500kHz, we're at 4MHz @@ -51,37 +54,28 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) { // So 256 * 4 bytes per sample (C16) = 1024 bytes from the file if( stream ) { const size_t bytes_to_read = sizeof(*buffer.p) * 2 * (buffer.count / 8); // *2 (C16), /8 (oversampling) should be == 1024 - const auto result = stream->read(iq_buffer.p, bytes_to_read); + bytes_read += stream->read(iq_buffer.p, bytes_to_read); } - - //feed_channel_stats(channel); - // Zero-stuff + // Fill and "stretch" for (size_t i = 0; i < buffer.count; i++) { - - // DEBUG: This works. Transmits a 1kHz tone - /*sample = (sine_table_i8[(tone_phase & 0xFF000000) >> 24]); - tone_phase += (1000 * ((1ULL << 32) / baseband_fs)); - // Do FM - delta = sample * 30000 * (0xFFFFFFULL / baseband_fs); - phase += delta; - sphase = phase + (64 << 24); - iq_buffer.p[i >> 3] = { (int16_t)(sine_table_i8[(sphase & 0xFF000000) >> 24]) << 8, (int16_t)(sine_table_i8[(phase & 0xFF000000) >> 24]) << 8 }; - */ - - /*if (i & 3) + if (i & 3) { buffer.p[i] = buffer.p[i - 1]; - else {*/ + } else { auto re_out = iq_buffer.p[i >> 3].real() >> 8; auto im_out = iq_buffer.p[i >> 3].imag() >> 8; - buffer.p[i] = { re_out, im_out }; - //} + buffer.p[i] = { (int8_t)re_out, (int8_t)im_out }; + } } spectrum_samples += buffer.count; if( spectrum_samples >= spectrum_interval_samples ) { spectrum_samples -= spectrum_interval_samples; channel_spectrum.feed(iq_buffer, channel_filter_pass_f, channel_filter_stop_f); + + txprogress_message.progress = bytes_read; // Inform UI about progress + txprogress_message.done = false; + shared_memory.application_queue.push(txprogress_message); } } @@ -93,8 +87,15 @@ void ReplayProcessor::on_message(const Message* const message) { break; case Message::ID::ReplayConfig: + configured = false; + bytes_read = 0; replay_config(*reinterpret_cast(message)); break; + + // App has prefilled the buffers, we're ready to go now + case Message::ID::FIFOData: + configured = true; + break; default: break; @@ -104,6 +105,9 @@ void ReplayProcessor::on_message(const Message* const message) { void ReplayProcessor::replay_config(const ReplayConfigMessage& message) { if( message.config ) { stream = std::make_unique(message.config); + + // Tell application that the buffers and FIFO pointers are ready, prefill + shared_memory.application_queue.push(sig_message); } else { stream.reset(); } diff --git a/firmware/baseband/proc_replay.hpp b/firmware/baseband/proc_replay.hpp index eed84209..1ab220f7 100644 --- a/firmware/baseband/proc_replay.hpp +++ b/firmware/baseband/proc_replay.hpp @@ -47,26 +47,29 @@ private: BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit }; - std::array iq { }; // This fits in just right in allocated RAM - Too big ? + std::array iq { }; const buffer_c16_t iq_buffer { iq.data(), - iq.size() + iq.size(), + baseband_fs / 8 }; uint32_t channel_filter_pass_f = 0; uint32_t channel_filter_stop_f = 0; - - // DEBUG - //uint32_t tone_phase { 0 }, phase { 0 }, delta { 0 }, sphase { 0 }; - //int8_t sample { 0 }; std::unique_ptr stream { }; SpectrumCollector channel_spectrum { }; size_t spectrum_interval_samples = 0; size_t spectrum_samples = 0; + + bool configured { false }; + uint32_t bytes_read { 0 }; void replay_config(const ReplayConfigMessage& message); + + TXProgressMessage txprogress_message { }; + RequestSignalMessage sig_message { RequestSignalMessage::Signal::FillRequest }; }; #endif/*__PROC_REPLAY_HPP__*/ diff --git a/firmware/baseband/stream_output.cpp b/firmware/baseband/stream_output.cpp index dbb5f254..9bbf4ece 100644 --- a/firmware/baseband/stream_output.cpp +++ b/firmware/baseband/stream_output.cpp @@ -51,8 +51,6 @@ size_t StreamOutput::read(void* const data, const size_t length) { // We need a full buffer... if( !fifo_buffers_full.out(active_buffer) ) { // ...but none are available. Hole in transmission (inform app and stop ?) - //active_buffer = nullptr; - //creg::m4txevent::assert(); break; } } diff --git a/firmware/common/buffer_exchange.cpp b/firmware/common/buffer_exchange.cpp index 88509805..2b0ae553 100644 --- a/firmware/common/buffer_exchange.cpp +++ b/firmware/common/buffer_exchange.cpp @@ -21,10 +21,6 @@ #include "buffer_exchange.hpp" -// DEBUG: -#include "hackrf_gpio.hpp" -using namespace hackrf::one; - BufferExchange* BufferExchange::obj { nullptr }; BufferExchange::BufferExchange( diff --git a/firmware/common/buffer_exchange.hpp b/firmware/common/buffer_exchange.hpp index 0e41dc6d..8315c308 100644 --- a/firmware/common/buffer_exchange.hpp +++ b/firmware/common/buffer_exchange.hpp @@ -55,7 +55,6 @@ public: return fifo_buffers_for_baseband->in(p); } - // TESTING... bool put_app(StreamBuffer* const p) { return fifo_buffers_for_application->in(p); } diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index 81333cc1..bc2031e0 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -70,49 +70,49 @@ struct Color { return { 255, 0, 0 }; } static constexpr Color dark_red() { - return { 127, 0, 0 }; + return { 191, 0, 0 }; } static constexpr Color orange() { return { 255, 175, 0 }; } static constexpr Color dark_orange() { - return { 127, 88, 0 }; + return { 191, 88, 0 }; } static constexpr Color yellow() { return { 255, 255, 0 }; } static constexpr Color dark_yellow() { - return { 127, 127, 0 }; + return { 191, 191, 0 }; } static constexpr Color green() { return { 0, 255, 0 }; } static constexpr Color dark_green() { - return { 0, 127, 0 }; + return { 0, 191, 0 }; } static constexpr Color blue() { return { 0, 0, 255 }; } static constexpr Color dark_blue() { - return { 0, 0, 127 }; + return { 0, 0, 191 }; } static constexpr Color cyan() { return { 0, 255, 255 }; } static constexpr Color dark_cyan() { - return { 0, 127, 127 }; + return { 0, 191, 191 }; } static constexpr Color magenta() { return { 255, 0, 255 }; } static constexpr Color dark_magenta() { - return { 127, 0, 127 }; + return { 191, 0, 191 }; } static constexpr Color white() { @@ -120,10 +120,10 @@ struct Color { } static constexpr Color light_grey() { - return { 127, 127, 127 }; + return { 191, 191, 191 }; } static constexpr Color grey() { - return { 91, 91, 91 }; + return { 127, 127, 127 }; } static constexpr Color dark_grey() { return { 63, 63, 63 }; diff --git a/firmware/graphics/icon_file_image.png b/firmware/graphics/icon_file_image.png new file mode 100644 index 00000000..0de1926f Binary files /dev/null and b/firmware/graphics/icon_file_image.png differ diff --git a/firmware/graphics/icon_file_iq.png b/firmware/graphics/icon_file_iq.png new file mode 100644 index 00000000..5a3d9d9e Binary files /dev/null and b/firmware/graphics/icon_file_iq.png differ diff --git a/firmware/graphics/icon_file_text.png b/firmware/graphics/icon_file_text.png new file mode 100644 index 00000000..b31f8129 Binary files /dev/null and b/firmware/graphics/icon_file_text.png differ diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index b1bf50e9..828f1d81 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ