From 5743d3a3b9664361a35c52ba9fb2911fb2e21277 Mon Sep 17 00:00:00 2001 From: Kyle Reed <3761006+kallanreed@users.noreply.github.com> Date: Sat, 17 Jun 2023 13:01:46 -0700 Subject: [PATCH] Playlist cleanup (#1163) * Functional rewrite of playlist --- firmware/application/apps/gps_sim_app.cpp | 3 +- firmware/application/apps/replay_app.cpp | 3 +- firmware/application/apps/ui_playlist.cpp | 392 ++++++++++----------- firmware/application/apps/ui_playlist.hpp | 122 +++---- firmware/application/io_file.cpp | 8 +- firmware/application/io_file.hpp | 14 +- firmware/application/io_wave.cpp | 31 +- firmware/common/utility.hpp | 15 + firmware/test/application/test_utility.cpp | 12 + 9 files changed, 304 insertions(+), 296 deletions(-) diff --git a/firmware/application/apps/gps_sim_app.cpp b/firmware/application/apps/gps_sim_app.cpp index 089f7a22..332fa70f 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 "utility.hpp" #include "baseband_api.hpp" #include "portapack.hpp" @@ -80,7 +81,7 @@ void GpsSimAppView::on_file_changed(std::filesystem::path new_file_path) { text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz"); auto file_size = data_file.size(); - auto duration = (file_size * 1000) / (1 * 2 * sample_rate); + 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)); diff --git a/firmware/application/apps/replay_app.cpp b/firmware/application/apps/replay_app.cpp index 2202f88b..295d547b 100644 --- a/firmware/application/apps/replay_app.cpp +++ b/firmware/application/apps/replay_app.cpp @@ -29,6 +29,7 @@ #include "baseband_api.hpp" #include "portapack.hpp" #include "portapack_persistent_memory.hpp" +#include "utility.hpp" using namespace portapack; @@ -79,7 +80,7 @@ void ReplayAppView::on_file_changed(std::filesystem::path new_file_path) { text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz"); auto file_size = data_file.size(); - auto duration = (file_size * 1000) / (2 * 2 * sample_rate); + auto duration = ms_duration(file_size, sample_rate, 4); progressbar.set_max(file_size); text_filename.set(file_path.filename().string().substr(0, 12)); diff --git a/firmware/application/apps/ui_playlist.cpp b/firmware/application/apps/ui_playlist.cpp index e01ccc24..3f700603 100644 --- a/firmware/application/apps/ui_playlist.cpp +++ b/firmware/application/apps/ui_playlist.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2016 Furrtek * Copyleft (ↄ) 2022 NotPike + * Copyright (C) 2023 Kyle Reed, zxkmm * * This file is part of PortaPack. * @@ -25,9 +26,9 @@ #include "convert.hpp" #include "file_reader.hpp" #include "string_format.hpp" - #include "ui_fileman.hpp" #include "io_file.hpp" +#include "utility.hpp" #include "baseband_api.hpp" #include "portapack.hpp" @@ -36,219 +37,209 @@ #include 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? */ namespace ui { -void PlaylistView::set_ready() { - ready_signal = true; -} - -void PlaylistView::load_file(std::filesystem::path playlist_path) { +void PlaylistView::load_file(const fs::path& playlist_path) { File playlist_file; auto error = playlist_file.open(playlist_path.string()); - if (!error) { - auto reader = FileLineReader(playlist_file); - for (const auto& line : reader) { - if (line.length() == 0 || line[0] == '#') - continue; // Empty or comment line. + if (error) + return; - auto cols = split_string(line, ','); - if (cols.size() < 3) - continue; // Line doesn't have enough columns. + auto reader = FileLineReader(playlist_file); + for (const auto& line : reader) { + if (line.length() == 0 || line[0] == '#') + continue; // Empty or comment line. - playlist_entry entry{}; + auto cols = split_string(line, ','); + if (cols.size() < 3) + continue; // Line doesn't have enough columns. - parse_int(cols[0], entry.replay_frequency); - parse_int(cols[2], entry.sample_rate); - if (entry.replay_frequency == 0 || entry.sample_rate == 0) - continue; // Invalid freq or rate. + playlist_entry entry{}; - entry.replay_file = std::string{"/"} + std::string{cols[1]}; + parse_int(cols[0], entry.replay_frequency); + parse_int(cols[2], entry.sample_rate); + if (entry.replay_frequency == 0 || entry.sample_rate == 0) + continue; // Invalid freq or rate. - if (cols.size() == 4) // Optional delay value. - parse_int(cols[3], entry.next_delay); + entry.replay_file = fs::path{u"/"} + std::string{cols[1]}; - playlist_db.emplace_back(std::move(entry)); - } + if (cols.size() == 4) // Optional delay value. + parse_int(cols[3], entry.initial_delay); + + playlist_db_.emplace_back(std::move(entry)); } +} - total_tracks = playlist_db.size(); - playlist_masterdb = playlist_db; - text_track.set(to_string_dec_uint(track_number) + "/" + to_string_dec_uint(total_tracks) + " " + now_play_list_file.string()); - tracks_progressbar.set_max(total_tracks); +void PlaylistView::on_file_changed(const fs::path& new_file_path) { + stop(); + reset(); + + playlist_path_ = new_file_path; + playlist_db_.clear(); + load_file(playlist_path_); + + update_ui(); button_play.focus(); } -void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate, uint32_t next_delay) { - File data_file; - - // Get file size - auto error = data_file.open("/" + new_file_path.string()); - - if (error) { - file_error("C16 file\n" + new_file_path.string() + "\nread error."); - return; - } - - track_number = track_number >= total_tracks ? 1 : track_number + 1; // prevent track_number out of range - - file_path = new_file_path; - field_frequency.set_value(replay_frequency); - - sample_rate = replay_sample_rate; - - text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz"); - - now_delay = next_delay; - - auto file_size = data_file.size(); - auto duration = (file_size * 1000) / (2 * 2 * sample_rate); - - on_track_progressbar.set_max(file_size); - text_filename.set(file_path.filename().string().substr(0, 12)); - text_duration.set(to_string_time_ms(duration)); - text_track.set(to_string_dec_uint(track_number) + "/" + to_string_dec_uint(total_tracks) + " " + now_play_list_file.string()); - tracks_progressbar.set_value(track_number); +void PlaylistView::reset() { + current_track_ = 0; + current_entry_ = nullptr; + current_entry_size_ = 0; } -void PlaylistView::on_tx_progress(const uint32_t progress) { - on_track_progressbar.set_value(progress); -} - -void PlaylistView::focus() { - button_open.focus(); -} - -void PlaylistView::file_error(std::string error_message) { - clean_playlist(); - stop(false); - nav_.display_modal("Error", "Error for \n" + file_path.string() + "\n" + error_message); -} - -void PlaylistView::clean_playlist() { - total_tracks = 0; - track_number = 0; - playlist_db.clear(); - playlist_masterdb.clear(); +void PlaylistView::show_file_error(const fs::path& path, const std::string& message) { + nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message); } bool PlaylistView::is_active() const { - return (bool)replay_thread; + return replay_thread_ != nullptr; } bool PlaylistView::loop() const { - return (bool)playlist_db.size(); + return check_loop.value(); +} + +bool PlaylistView::is_done() const { + return current_track_ >= playlist_db_.size(); } void PlaylistView::toggle() { - if (is_active()) { - stop(false); - clean_playlist(); - } else { - clean_playlist(); - if (std::filesystem::file_exists(now_play_list_file.string())) { - load_file(now_play_list_file); - if (!playlist_db.empty()) { - start(); - } - } else { - text_track.set("0/0 no input playlist file"); - } - } + if (is_active()) + stop(); + else + start(); } void PlaylistView::start() { - stop(false); + reset(); - playlist_entry item = playlist_db.front(); - playlist_db.pop_front(); - on_file_changed(item.replay_file, item.replay_frequency, item.sample_rate, item.next_delay); - transmitter_model.set_target_frequency(item.replay_frequency); + if (playlist_db_.empty()) + return; // Nothing to do. - std::unique_ptr reader; - - auto p = std::make_unique(); - auto open_error = p->open(file_path); - if (open_error.is_valid()) { - file_error("Illegal grammar"); - return; // Fixes TX bug if there's a file error - } else { - reader = std::move(p); - } - - if (reader) { - button_play.set_bitmap(&bitmap_stop); - if (now_delay) { // this `if` is because, if the delay is 0, it will sleep forever - chThdSleepMilliseconds(now_delay); - } - - replay_thread = std::make_unique( - std::move(reader), - read_size, buffer_count, - &ready_signal, - [](uint32_t return_code) { - ReplayThreadDoneMessage message{return_code}; - EventDispatcher::send_message(message); - }); - } - - transmitter_model.set_sampling_rate(sample_rate * 8); - transmitter_model.set_baseband_bandwidth(baseband_bandwidth); - transmitter_model.enable(); + if (next_track()) + send_current_track(); } -void PlaylistView::stop(const bool do_loop) { - if (is_active()) { - replay_thread.reset(); +/* Advance to the next track in the playlist. */ +bool PlaylistView::next_track() { + if (is_done()) { + if (loop()) + current_track_ = 0; + else + return false; // All done. } - - // TODO: the logic here could be more beautiful but maybe they are all same for compiler anyway.... - // Notes of the logic here in case if it needed to be changed in the future: - // 1. check_loop.value() is for the LOOP checkbox - // 2. do_loop is a part of the replay thread, not a user - control thing. - // 3. when (total_tracks > track_number) is true, it means that the current track is not the last track. - // Thus, (do_loop && (total_tracks != track_number)) is for the case when the start() func were called with true AND not the last track. - // Which means it do loop until the last track. - - if (check_loop.value()) { - if (do_loop) { - if (playlist_db.size() > 0) { - start(); - } else { - playlist_db = playlist_masterdb; - start(); - } - } else { - transmitter_model.disable(); - button_play.set_bitmap(&bitmap_play); - } - } else if (!check_loop.value()) { - if (do_loop && (total_tracks > track_number)) { - if (playlist_db.size() > 0) { - start(); - } else { - playlist_db = playlist_masterdb; - start(); - } - } else { - transmitter_model.disable(); - button_play.set_bitmap(&bitmap_play); - } - } - - ready_signal = false; + current_entry_ = &playlist_db_[current_track_]; + current_track_++; + return true; } -void PlaylistView::handle_replay_thread_done(const uint32_t return_code) { - if (return_code == ReplayThread::END_OF_FILE) { - stop(true); - } else if (return_code == ReplayThread::READ_ERROR) { - file_error("Replay thread read error"); +/* Transmits the current_entry_ */ +void PlaylistView::send_current_track() { + replay_thread_.reset(); + ready_signal_ = false; + + if (!current_entry_) + return; + + // Open the sample file to send. + auto reader = std::make_unique(); + auto error = reader->open(current_entry_->replay_file); + if (error) { + show_file_error(current_entry_->replay_file, "Can't open file to send."); return; } - on_track_progressbar.set_value(0); + // Get the sample file size for the progress bar. + current_entry_size_ = reader->file().size(); + progressbar_transmit.set_value(0); + + // Wait a bit before sending if specified. + // TODO: this pauses the main thread, move into ReplayThread instead. + if (current_entry_->initial_delay > 0) + chThdSleepMilliseconds(current_entry_->initial_delay); + + // ReplayThread starts immediately on contruction so + // these need to be set before creating the ReplayThread. + transmitter_model.set_target_frequency(current_entry_->replay_frequency); + transmitter_model.set_sampling_rate(current_entry_->sample_rate * 8); + transmitter_model.set_baseband_bandwidth(baseband_bandwidth); + transmitter_model.enable(); + + // Use the ReplayThread class to send the data. + replay_thread_ = std::make_unique( + std::move(reader), + /* read_size */ 0x4000, + /* buffer_count */ 3, + &ready_signal_, + [](uint32_t return_code) { + ReplayThreadDoneMessage message{return_code}; + EventDispatcher::send_message(message); + }); + + // Now it's sending, update the UI. + update_ui(); +} + +void PlaylistView::stop() { + // This terminates the underlying chThread. + replay_thread_.reset(); + transmitter_model.disable(); + update_ui(); +} + +void PlaylistView::update_ui() { + if (playlist_db_.empty()) { + text_track.set("Open a playlist file."); + } else { + text_track.set( + to_string_dec_uint(current_track_) + "/" + + to_string_dec_uint(playlist_db_.size()) + " " + + playlist_path_.filename().string()); + } + + button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play); + + progressbar_track.set_value(current_track_); + progressbar_track.set_max(playlist_db_.size()); + progressbar_transmit.set_max(current_entry_size_); + + if (current_entry_) { + auto duration = ms_duration(current_entry_size_, current_entry_->sample_rate, 4); + text_filename.set(truncate(current_entry_->replay_file.filename().string(), 12)); + text_sample_rate.set(unit_auto_scale(current_entry_->sample_rate, 3, 0) + "Hz"); + text_duration.set(to_string_time_ms(duration)); + text_frequency.set(to_string_short_freq(current_entry_->replay_frequency)); + } else { + text_filename.set("-"); + text_sample_rate.set("-"); + text_duration.set("-"); + text_frequency.set("-"); + } +} + +void PlaylistView::on_tx_progress(uint32_t progress) { + progressbar_transmit.set_value(progress); +} + +void PlaylistView::handle_replay_thread_done(uint32_t return_code) { + if (return_code == ReplayThread::END_OF_FILE) { + if (next_track()) { + send_current_track(); + return; + } + } + + if (return_code == ReplayThread::READ_ERROR) + show_file_error(current_entry_->replay_file, "Replay read failed."); + + stop(); } PlaylistView::PlaylistView( @@ -261,10 +252,10 @@ PlaylistView::PlaylistView( &text_filename, &text_sample_rate, &text_duration, - &tracks_progressbar, - &on_track_progressbar, - &field_frequency, - &tx_view, // this handles now the previous rfgain, rfamp + &progressbar_track, + &progressbar_transmit, + &text_frequency, + &tx_view, &check_loop, &button_play, &text_track, @@ -273,36 +264,18 @@ PlaylistView::PlaylistView( waterfall.show_audio_spectrum_view(false); - field_frequency.set_value(transmitter_model.target_frequency()); - field_frequency.set_step(receiver_model.frequency_step()); - field_frequency.on_change = [this](rf::Frequency f) { - transmitter_model.set_target_frequency(f); - }; - field_frequency.on_edit = [this, &nav]() { - // TODO: Provide separate modal method/scheme? - auto new_view = nav.push(transmitter_model.target_frequency()); - new_view->on_changed = [this](rf::Frequency f) { - this->field_frequency.set_value(f); - }; - }; - - field_frequency.set_step(5000); - button_play.on_select = [this](ImageButton&) { - this->toggle(); + toggle(); }; button_open.on_select = [this, &nav](Button&) { auto open_view = nav.push(".PPL"); open_view->on_changed = [this](std::filesystem::path new_file_path) { - now_play_list_file = new_file_path; - if (std::filesystem::file_exists(now_play_list_file.string())) { - load_file(now_play_list_file); - } else { - text_track.set("0/0 no input playlist file"); - } + on_file_changed(new_file_path); }; }; + + update_ui(); } PlaylistView::~PlaylistView() { @@ -310,20 +283,23 @@ PlaylistView::~PlaylistView() { baseband::shutdown(); } +void PlaylistView::set_parent_rect(Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + + const ui::Rect waterfall_rect{ + 0, header_height, new_parent_rect.width(), + new_parent_rect.height() - header_height}; + waterfall.set_parent_rect(waterfall_rect); +} + void PlaylistView::on_hide() { - stop(false); - // TODO: Terrible kludge because widget system doesn't notify Waterfall that - // it's being shown or hidden. + stop(); waterfall.on_hide(); View::on_hide(); } -void PlaylistView::set_parent_rect(const Rect new_parent_rect) { - View::set_parent_rect(new_parent_rect); - - const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), - new_parent_rect.height() - header_height}; - waterfall.set_parent_rect(waterfall_rect); +void PlaylistView::focus() { + button_open.focus(); } } /* namespace ui */ diff --git a/firmware/application/apps/ui_playlist.hpp b/firmware/application/apps/ui_playlist.hpp index 1fe1e6b3..699bcd65 100644 --- a/firmware/application/apps/ui_playlist.hpp +++ b/firmware/application/apps/ui_playlist.hpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2016 Furrtek + * Copyright (C) 2023 Kyle Reed, zxkmm * * This file is part of PortaPack. * @@ -34,20 +35,21 @@ #include #include -#include +#include namespace ui { class PlaylistView : public View { public: PlaylistView(NavigationView& nav); - ~PlaylistView(); + // Disable copy to make -Weffc++ happy. + PlaylistView(const PlaylistView&) = delete; + PlaylistView& operator=(const PlaylistView&) = delete; + + void set_parent_rect(Rect new_parent_rect) override; void on_hide() override; - - void set_parent_rect(const Rect new_parent_rect) override; - void focus() override; std::string title() const override { return "Playlist"; }; @@ -58,75 +60,73 @@ class PlaylistView : public View { app_settings::SettingsManager settings_{ "tx_playlist", app_settings::Mode::TX}; - // add or remove lines here to allow more header and less spectrum view, & vice versa + // More header == less spectrum view. static constexpr ui::Dim header_height = 4 * 16; + static constexpr uint32_t baseband_bandwidth = 2500000; struct playlist_entry { rf::Frequency replay_frequency{0}; - std::string replay_file{}; + std::filesystem::path replay_file{}; uint32_t sample_rate{}; - uint32_t next_delay{}; + uint32_t initial_delay{}; }; - std::deque playlist_db{}; - std::deque playlist_masterdb{}; - uint32_t sample_rate = 0; - int32_t tx_gain{47}; - bool rf_amp{true}; // aux private var to store temporal, Replay App rf_amp user selection. - static constexpr uint32_t baseband_bandwidth = 2500000; - const size_t read_size{16384}; - const size_t buffer_count{3}; - void load_file(std::filesystem::path playlist_path); - void on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate, uint32_t next_delay); - 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 start(); - void stop(const bool do_loop); + std::unique_ptr replay_thread_{}; + bool ready_signal_{}; // Used to signal the ReplayThread. + + size_t current_track_{0}; + const playlist_entry* current_entry_{}; + size_t current_entry_size_{0}; + std::vector playlist_db_{}; + std::filesystem::path playlist_path_{}; + + void load_file(const std::filesystem::path& path); + void on_file_changed(const std::filesystem::path& path); + void reset(); + void show_file_error( + const std::filesystem::path& path, + const std::string& message); + bool is_active() const; bool loop() const; - void set_ready(); - void handle_replay_thread_done(const uint32_t return_code); - void file_error(std::string error_message); - void clean_playlist(); + bool is_done() const; - std::filesystem::path file_path{}; - std::unique_ptr replay_thread{}; - bool ready_signal{false}; - size_t track_number{0}; - size_t total_tracks{0}; - uint32_t now_delay{0}; - std::filesystem::path now_play_list_file{}; + void toggle(); + void start(); + bool next_track(); + void send_current_track(); + void stop(); + + void update_ui(); + + /* There are called by Message handlers. */ + void on_tx_progress(uint32_t progress); + void handle_replay_thread_done(uint32_t return_code); Button button_open{ {0 * 8, 0 * 16, 10 * 8, 2 * 16}, - "Open file"}; + "Open PPL"}; Text text_filename{ - {11 * 8, 0 * 16, 12 * 8, 16}, - "-"}; + {11 * 8, 0 * 16, 12 * 8, 16}}; + Text text_sample_rate{ - {24 * 8, 0 * 16, 6 * 8, 16}, - "-"}; + {24 * 8, 0 * 16, 6 * 8, 16}}; Text text_duration{ - {11 * 8, 1 * 16, 6 * 8, 16}, - "-"}; - ProgressBar tracks_progressbar{ + {11 * 8, 1 * 16, 6 * 8, 16}}; + + ProgressBar progressbar_track{ {18 * 8, 1 * 16, 12 * 8, 8}}; - ProgressBar on_track_progressbar{ + ProgressBar progressbar_transmit{ {18 * 8, 3 * 8, 12 * 8, 8}}; - FrequencyField field_frequency{ - {0 * 8, 2 * 16}, - }; + Text text_frequency{ + {0 * 8, 2 * 16, 9 * 8, 16}}; TransmitterView2 tx_view{ - // new handling of NumberField field_rfgain, NumberField field_rfamp - 74, 1 * 8, SHORT_UI // x(columns), y (line) position. (Used in Repay / GPS Simul / Play list App) - // 10*8, 2*8, NORMAL_UI // x(columns), y (line) position. (Used in Mic App) + 74, 1 * 8, SHORT_UI // x(columns), y (line) position. }; Checkbox check_loop{ @@ -134,6 +134,7 @@ class PlaylistView : public View { 4, "Loop", true}; + ImageButton button_play{ {28 * 8, 2 * 16, 2 * 8, 1 * 16}, &bitmap_play, @@ -141,32 +142,31 @@ class PlaylistView : public View { Color::black()}; Text text_track{ - {0 * 8, 3 * 16, 16 * 8, 16}, - "0/0 no input playlist file"}; + {0 * 8, 3 * 16, 30 * 8, 16}}; 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.return_code); + [this](const Message* p) { + auto message = *reinterpret_cast(p); + handle_replay_thread_done(message.return_code); }}; MessageHandlerRegistration message_handler_fifo_signal{ Message::ID::RequestSignal, - [this](const Message* const p) { - const auto message = static_cast(p); + [this](const Message* p) { + auto message = static_cast(p); if (message->signal == RequestSignalMessage::Signal::FillRequest) { - this->set_ready(); + ready_signal_ = true; } }}; 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); + [this](const Message* p) { + auto message = *reinterpret_cast(p); + on_tx_progress(message.progress); }}; }; diff --git a/firmware/application/io_file.cpp b/firmware/application/io_file.cpp index f30e5db3..c3fbab4b 100644 --- a/firmware/application/io_file.cpp +++ b/firmware/application/io_file.cpp @@ -22,17 +22,17 @@ #include "io_file.hpp" File::Result FileReader::read(void* const buffer, const File::Size bytes) { - auto read_result = file.read(buffer, bytes); + auto read_result = file_.read(buffer, bytes); if (read_result.is_ok()) { - bytes_read += read_result.value(); + bytes_read_ += read_result.value(); } return read_result; } File::Result FileWriter::write(const void* const buffer, const File::Size bytes) { - auto write_result = file.write(buffer, bytes); + auto write_result = file_.write(buffer, bytes); if (write_result.is_ok()) { - bytes_written += write_result.value(); + bytes_written_ += write_result.value(); } return write_result; } diff --git a/firmware/application/io_file.hpp b/firmware/application/io_file.hpp index a65c1cd8..5dffa789 100644 --- a/firmware/application/io_file.hpp +++ b/firmware/application/io_file.hpp @@ -38,14 +38,15 @@ class FileReader : public stream::Reader { FileReader& operator=(FileReader&&) = delete; Optional open(const std::filesystem::path& filename) { - return file.open(filename); + return file_.open(filename); } File::Result read(void* const buffer, const File::Size bytes) override; + const File& file() const& { return file_; } protected: - File file{}; - uint64_t bytes_read{0}; + File file_{}; + uint64_t bytes_read_{0}; }; class FileWriter : public stream::Writer { @@ -58,14 +59,15 @@ class FileWriter : public stream::Writer { FileWriter& operator=(FileWriter&&) = delete; Optional create(const std::filesystem::path& filename) { - return file.create(filename); + return file_.create(filename); } File::Result write(const void* const buffer, const File::Size bytes) override; + const File& file() const& { return file_; } protected: - File file{}; - uint64_t bytes_written{0}; + File file_{}; + uint64_t bytes_written_{0}; }; using RawFileWriter = FileWriter; diff --git a/firmware/application/io_wave.cpp b/firmware/application/io_wave.cpp index 337820e0..e72302e3 100644 --- a/firmware/application/io_wave.cpp +++ b/firmware/application/io_wave.cpp @@ -21,6 +21,7 @@ */ #include "io_wave.hpp" +#include "utility.hpp" bool WAVFileReader::open(const std::filesystem::path& path) { size_t i = 0; @@ -36,10 +37,10 @@ bool WAVFileReader::open(const std::filesystem::path& path) { return true; } - auto error = file.open(path); + auto error = file_.open(path); if (!error.is_valid()) { - file.read((void*)&header, sizeof(header)); // Read header (RIFF and WAVE) + file_.read((void*)&header, sizeof(header)); // Read header (RIFF and WAVE) riff_size = header.cksize + 8; data_start = header.fmt.cksize + 28; @@ -48,14 +49,14 @@ bool WAVFileReader::open(const std::filesystem::path& path) { // Look for INAM (title) tag if (data_end < riff_size) { - file.seek(data_end); - while (file.read((void*)&ch, 1).is_ok()) { + file_.seek(data_end); + while (file_.read((void*)&ch, 1).is_ok()) { if (ch == tag_INAM[i++]) { if (i == 4) { // Tag found, copy title - file.read((void*)&title_size, sizeof(uint32_t)); + file_.read((void*)&title_size, sizeof(uint32_t)); if (title_size > 32) title_size = 32; - file.read((void*)&title_buffer, title_size); + file_.read((void*)&title_buffer, title_size); title_string = title_buffer; break; } @@ -86,7 +87,7 @@ bool WAVFileReader::open(const std::filesystem::path& path) { } void WAVFileReader::rewind() { - file.seek(data_start); + file_.seek(data_start); } std::string WAVFileReader::title() { @@ -94,15 +95,15 @@ std::string WAVFileReader::title() { } uint32_t WAVFileReader::ms_duration() { - return ((data_size_ * 1000) / sample_rate_) / bytes_per_sample; + return ::ms_duration(data_size_, sample_rate_, bytes_per_sample); } void WAVFileReader::data_seek(const uint64_t Offset) { - file.seek(data_start + (Offset * bytes_per_sample)); + file_.seek(data_start + (Offset * bytes_per_sample)); } /*int WAVFileReader::seek_mss(const uint16_t minutes, const uint8_t seconds, const uint32_t samples) { - const auto result = file.seek(data_start + ((((minutes * 60) + seconds) * sample_rate_) + samples) * bytes_per_sample); + const auto result = file_.seek(data_start + ((((minutes * 60) + seconds) * sample_rate_) + samples) * bytes_per_sample); if (result.is_error()) return 0; @@ -145,21 +146,21 @@ Optional WAVFileWriter::create( } Optional WAVFileWriter::update_header() { - header_t header{sampling_rate, (uint32_t)bytes_written - sizeof(header_t), info_chunk_size}; + header_t header{sampling_rate, (uint32_t)bytes_written_ - sizeof(header_t), info_chunk_size}; - const auto seek_0_result = file.seek(0); + const auto seek_0_result = file_.seek(0); if (seek_0_result.is_error()) { return seek_0_result.error(); } const auto old_position = seek_0_result.value(); - const auto write_result = file.write(&header, sizeof(header)); + const auto write_result = file_.write(&header, sizeof(header)); if (write_result.is_error()) { return write_result.error(); } - const auto seek_old_result = file.seek(old_position); + const auto seek_old_result = file_.seek(old_position); if (seek_old_result.is_error()) { return seek_old_result.error(); } @@ -170,7 +171,7 @@ Optional WAVFileWriter::update_header() { Optional WAVFileWriter::write_tags() { tags_t tags{title}; - const auto write_result = file.write(&tags, sizeof(tags)); + const auto write_result = file_.write(&tags, sizeof(tags)); if (write_result.is_error()) { return write_result.error(); } diff --git a/firmware/common/utility.hpp b/firmware/common/utility.hpp index 28082593..7e289347 100644 --- a/firmware/common/utility.hpp +++ b/firmware/common/utility.hpp @@ -90,6 +90,21 @@ inline float magnitude_squared(const std::complex c) { return r2 + i2; } +/* Compute the duration in ms of a buffer. */ +inline uint32_t ms_duration( + uint64_t buffer_size, + uint32_t sample_rate, + uint32_t bytes_per_sample) { + /* bytes * sample * second * ms + * ------ ------- ------ + * bytes samples seconds + */ + if (sample_rate == 0 || bytes_per_sample == 0) + return 0; + + return buffer_size / bytes_per_sample / sample_rate * 1000; +} + int fast_int_magnitude(int y, int x); int int_atan2(int y, int x); int32_t int16_sin_s4(int32_t x); diff --git a/firmware/test/application/test_utility.cpp b/firmware/test/application/test_utility.cpp index c646c029..aea4c423 100644 --- a/firmware/test/application/test_utility.cpp +++ b/firmware/test/application/test_utility.cpp @@ -60,3 +60,15 @@ TEST_CASE("Stash should save and restore value.") { CHECK_EQ(val, 10); } + +TEST_CASE("ms_duration should return duration.") { + auto sample_rate = 48'000; + auto bytes_per_sample = 2; // 16 bit. + auto seconds = 5; + auto size = seconds * sample_rate * bytes_per_sample; + CHECK_EQ(ms_duration(size, sample_rate, bytes_per_sample), 5 * 1000); +} + +TEST_CASE("ms_duration not fault when passed zero.") { + CHECK_EQ(ms_duration(0, 0, 0), 0); +} \ No newline at end of file