diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 8f3dfa0ac..941ca62c9 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -184,6 +184,7 @@ set(CPPSRC irq_lcd_frame.cpp irq_rtc.cpp log_file.cpp + metadata_file.cpp portapack.cpp qrcodegen.cpp radio.cpp diff --git a/firmware/application/apps/gps_sim_app.cpp b/firmware/application/apps/gps_sim_app.cpp index c85614ec9..bc8c7e2f8 100644 --- a/firmware/application/apps/gps_sim_app.cpp +++ b/firmware/application/apps/gps_sim_app.cpp @@ -26,6 +26,7 @@ #include "ui_fileman.hpp" #include "io_file.hpp" +#include "metadata_file.hpp" #include "utility.hpp" #include "baseband_api.hpp" @@ -33,6 +34,7 @@ #include "portapack_persistent_memory.hpp" using namespace portapack; +namespace fs = std::filesystem; namespace ui { @@ -40,51 +42,38 @@ void GpsSimAppView::set_ready() { ready_signal = true; } -void GpsSimAppView::on_file_changed(std::filesystem::path new_file_path) { - File data_file, info_file; - char file_data[257]; +void GpsSimAppView::on_file_changed(const fs::path& new_file_path) { + file_path = fs::path(u"/") + new_file_path; + File::Size file_size{}; - // Get file size - auto data_open_error = data_file.open("/" + new_file_path.string()); - if (data_open_error.is_valid()) { - file_error(); - return; - } - - file_path = new_file_path; - - // Get original record frequency if available - std::filesystem::path info_file_path = file_path; - info_file_path.replace_extension(u".TXT"); - - sample_rate = 500000; - - auto info_open_error = info_file.open("/" + info_file_path.string()); - if (!info_open_error.is_valid()) { - memset(file_data, 0, 257); - auto read_size = info_file.read(file_data, 256); - if (!read_size.is_error()) { - auto pos1 = strstr(file_data, "center_frequency="); - if (pos1) { - pos1 += 17; - field_frequency.set_value(strtoll(pos1, nullptr, 10)); - } - - auto pos2 = strstr(file_data, "sample_rate="); - if (pos2) { - pos2 += 12; - sample_rate = strtoll(pos2, nullptr, 10); - } + { // Get the size of the data file. + File data_file; + auto error = data_file.open(file_path); + if (error) { + file_error(); + return; } + + file_size = data_file.size(); } + // Get original record frequency if available. + auto metadata_path = get_metadata_path(file_path); + auto metadata = read_metadata_file(metadata_path); + + if (metadata) { + field_frequency.set_value(metadata->center_frequency); + sample_rate = metadata->sample_rate; + } else { + sample_rate = 500000; + } + + // UI Fixup. text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz"); - - auto file_size = data_file.size(); - auto duration = ms_duration(file_size, sample_rate, 2); - progressbar.set_max(file_size / 1024); - text_filename.set(file_path.filename().string().substr(0, 12)); + text_filename.set(truncate(file_path.filename().string(), 12)); + + auto duration = ms_duration(file_size, sample_rate, 2); text_duration.set(to_string_time_ms(duration)); button_play.focus(); diff --git a/firmware/application/apps/gps_sim_app.hpp b/firmware/application/apps/gps_sim_app.hpp index 8a271959a..399a21d8f 100644 --- a/firmware/application/apps/gps_sim_app.hpp +++ b/firmware/application/apps/gps_sim_app.hpp @@ -71,7 +71,7 @@ class GpsSimAppView : public View { const size_t read_size{16384}; const size_t buffer_count{3}; - void on_file_changed(std::filesystem::path new_file_path); + void on_file_changed(const std::filesystem::path& new_file_path); void on_tx_progress(const uint32_t progress); void toggle(); diff --git a/firmware/application/apps/replay_app.cpp b/firmware/application/apps/replay_app.cpp index 84fcf8831..6eff5834a 100644 --- a/firmware/application/apps/replay_app.cpp +++ b/firmware/application/apps/replay_app.cpp @@ -27,11 +27,13 @@ #include "ui_fileman.hpp" #include "io_file.hpp" #include "baseband_api.hpp" +#include "metadata_file.hpp" #include "portapack.hpp" #include "portapack_persistent_memory.hpp" #include "utility.hpp" using namespace portapack; +namespace fs = std::filesystem; namespace ui { @@ -39,51 +41,41 @@ void ReplayAppView::set_ready() { ready_signal = true; } -void ReplayAppView::on_file_changed(std::filesystem::path new_file_path) { - File data_file, info_file; - char file_data[257]; +void ReplayAppView::on_file_changed(const fs::path& new_file_path) { + file_path = fs::path(u"/") + new_file_path; + File::Size file_size{}; - // Get file size - auto data_open_error = data_file.open("/" + new_file_path.string()); - if (data_open_error.is_valid()) { - file_error(); - return; - } - - file_path = new_file_path; - - // Get original record frequency if available - std::filesystem::path info_file_path = file_path; - info_file_path.replace_extension(u".TXT"); - - sample_rate = 500000; - - auto info_open_error = info_file.open("/" + info_file_path.string()); - if (!info_open_error.is_valid()) { - memset(file_data, 0, 257); - auto read_size = info_file.read(file_data, 256); - if (!read_size.is_error()) { - auto pos1 = strstr(file_data, "center_frequency="); - if (pos1) { - pos1 += 17; - field_frequency.set_value(strtoll(pos1, nullptr, 10)); - } - - auto pos2 = strstr(file_data, "sample_rate="); - if (pos2) { - pos2 += 12; - sample_rate = strtoll(pos2, nullptr, 10); - } + { // Get the size of the data file. + File data_file; + auto error = data_file.open(file_path); + if (error) { + file_error(); + return; } + + file_size = data_file.size(); } + // Get original record frequency if available. + auto metadata_path = get_metadata_path(file_path); + auto metadata = read_metadata_file(metadata_path); + + if (metadata) { + field_frequency.set_value(metadata->center_frequency); + sample_rate = metadata->sample_rate; + } else { + // TODO: This is interesting because it implies that the + // The capture will just be replayed at the freq set on the + // FrequencyField. Is that an intentional behavior? + sample_rate = 500000; + } + + // UI Fixup. text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz"); - - auto file_size = data_file.size(); - auto duration = ms_duration(file_size, sample_rate, 4); - progressbar.set_max(file_size); - text_filename.set(file_path.filename().string().substr(0, 12)); + text_filename.set(truncate(file_path.filename().string(), 12)); + + auto duration = ms_duration(file_size, sample_rate, 4); text_duration.set(to_string_time_ms(duration)); button_play.focus(); @@ -200,7 +192,7 @@ ReplayAppView::ReplayAppView( button_open.on_select = [this, &nav](Button&) { auto open_view = nav.push(".C16"); - open_view->on_changed = [this](std::filesystem::path new_file_path) { + open_view->on_changed = [this](fs::path new_file_path) { on_file_changed(new_file_path); }; }; diff --git a/firmware/application/apps/replay_app.hpp b/firmware/application/apps/replay_app.hpp index 42362055f..3dbabd0b3 100644 --- a/firmware/application/apps/replay_app.hpp +++ b/firmware/application/apps/replay_app.hpp @@ -67,7 +67,7 @@ class ReplayAppView : public View { const size_t read_size{16384}; const size_t buffer_count{3}; - void on_file_changed(std::filesystem::path new_file_path); + void on_file_changed(const std::filesystem::path& new_file_path); void on_tx_progress(const uint32_t progress); void toggle(); diff --git a/firmware/application/apps/ui_playlist.cpp b/firmware/application/apps/ui_playlist.cpp index 46d5f0e9a..9ff1fb6e8 100644 --- a/firmware/application/apps/ui_playlist.cpp +++ b/firmware/application/apps/ui_playlist.cpp @@ -40,7 +40,9 @@ using namespace portapack; namespace fs = std::filesystem; /* TODO: wouldn't it be easier if the playlist were just a list of C16 files - * (and maybe delays) and then read the data file next to the C16 file? */ + * (and maybe delays) and then read the metadata file next to the C16 file? + * TODO: use metadata_file.hpp to read the metadata. + * TODO: change PPL format to only allow paths, and ! for delay. */ namespace ui { diff --git a/firmware/application/metadata_file.cpp b/firmware/application/metadata_file.cpp new file mode 100644 index 000000000..7b606b9d6 --- /dev/null +++ b/firmware/application/metadata_file.cpp @@ -0,0 +1,89 @@ +/* + * 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 "metadata_file.hpp" + +#include "convert.hpp" +#include "file_reader.hpp" +#include "string_format.hpp" +#include + +namespace fs = std::filesystem; +using namespace std::literals; + +const std::string_view center_freq_name = "center_frequency"sv; +const std::string_view sample_rate_name = "sample_rate"sv; + +fs::path get_metadata_path(const fs::path& capture_path) { + auto temp = capture_path; + return temp.replace_extension(u".TXT"); +} + +Optional write_metadata_file(const fs::path& path, capture_metadata metadata) { + File f; + auto error = f.create(path); + + if (error) + return error; + + error = f.write_line(std::string{center_freq_name} + "=" + + to_string_dec_uint(metadata.center_frequency)); + if (error) + return error; + + // TODO: Why does this divide by 8? Leaving as is for back-compat, but it's odd. + error = f.write_line(std::string{sample_rate_name} + "=" + + to_string_dec_uint(metadata.sample_rate / 8)); + if (error) + return error; + + return {}; +} + +Optional read_metadata_file(const fs::path& path) { + File f; + auto error = f.open(path); + + if (error) + return {}; + + capture_metadata metadata{}; + + auto reader = FileLineReader(f); + for (const auto& line : reader) { + auto cols = split_string(line, '='); + + if (cols.size() != 2) + continue; // Bad line. + + if (cols[0] == center_freq_name) + parse_int(cols[1], metadata.center_frequency); + else if (cols[0] == sample_rate_name) + parse_int(cols[1], metadata.sample_rate); + else + continue; + } + + if (metadata.center_frequency == 0 || metadata.sample_rate == 0) + return {}; // Parse failed. + + return metadata; +} diff --git a/firmware/application/metadata_file.hpp b/firmware/application/metadata_file.hpp new file mode 100644 index 000000000..d3e055e4b --- /dev/null +++ b/firmware/application/metadata_file.hpp @@ -0,0 +1,39 @@ +/* + * 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 __METADATA_FILE_HPP__ +#define __METADATA_FILE_HPP__ + +#include "file.hpp" +#include "optional.hpp" +#include "rf_path.hpp" + +struct capture_metadata { + rf::Frequency center_frequency; + uint32_t sample_rate; +}; + +std::filesystem::path get_metadata_path(const std::filesystem::path& capture_path); + +Optional write_metadata_file(const std::filesystem::path& path, capture_metadata metadata); +Optional read_metadata_file(const std::filesystem::path& path); + +#endif // __METADATA_FILE_HPP__ \ No newline at end of file diff --git a/firmware/application/ui_record_view.cpp b/firmware/application/ui_record_view.cpp index 2de67e932..8761ba08e 100644 --- a/firmware/application/ui_record_view.cpp +++ b/firmware/application/ui_record_view.cpp @@ -28,6 +28,7 @@ using namespace portapack; #include "io_wave.hpp" #include "baseband_api.hpp" +#include "metadata_file.hpp" #include "rtc_time.hpp" #include "string_format.hpp" #include "utility.hpp" @@ -194,7 +195,9 @@ void RecordView::start() { } break; case FileType::RawS16: { - const auto metadata_file_error = write_metadata_file(base_path.replace_extension(u".TXT")); + const auto metadata_file_error = + write_metadata_file(get_metadata_path(base_path), + {receiver_model.target_frequency(), sampling_rate}); if (metadata_file_error.is_valid()) { handle_error(metadata_file_error.value()); return; @@ -246,24 +249,6 @@ void RecordView::stop() { update_status_display(); } -Optional RecordView::write_metadata_file(const std::filesystem::path& filename) { - File file; - const auto create_error = file.create(filename); - if (create_error.is_valid()) { - return create_error; - } else { - const auto error_line1 = file.write_line("sample_rate=" + to_string_dec_uint(sampling_rate / 8)); - if (error_line1.is_valid()) { - return error_line1; - } - const auto error_line2 = file.write_line("center_frequency=" + to_string_dec_uint(receiver_model.target_frequency())); - if (error_line2.is_valid()) { - return error_line2; - } - return {}; - } -} - void RecordView::on_tick_second() { update_status_display(); } diff --git a/firmware/application/ui_record_view.hpp b/firmware/application/ui_record_view.hpp index ec3b56e95..8b5c8de7b 100644 --- a/firmware/application/ui_record_view.hpp +++ b/firmware/application/ui_record_view.hpp @@ -68,7 +68,6 @@ class RecordView : public View { private: void toggle(); // void toggle_pitch_rssi(); - Optional write_metadata_file(const std::filesystem::path& filename); void on_tick_second(); void update_status_display();