mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
Replay of IQ files ! :D
Added icons and colors for commonly used files in Fileman Fileman can filter by file extension Bugfix: Fileman doesn't crash anymore on renaming long file names Updated binary
This commit is contained in:
parent
3221992ad1
commit
b38adf3769
@ -195,7 +195,7 @@ set(CPPSRC
|
|||||||
ui_rds.cpp
|
ui_rds.cpp
|
||||||
ui_receiver.cpp
|
ui_receiver.cpp
|
||||||
ui_record_view.cpp
|
ui_record_view.cpp
|
||||||
ui_replay_view.cpp
|
# ui_replay_view.cpp
|
||||||
ui_rssi.cpp
|
ui_rssi.cpp
|
||||||
ui_scanner.cpp
|
ui_scanner.cpp
|
||||||
# ui_script.cpp
|
# ui_script.cpp
|
||||||
|
@ -367,6 +367,28 @@ static constexpr Bitmap bitmap_record {
|
|||||||
{ 16, 16 }, bitmap_record_data
|
{ 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[] = {
|
static constexpr uint8_t bitmap_icon_ais_data[] = {
|
||||||
0x00, 0x01,
|
0x00, 0x01,
|
||||||
0x80, 0x01,
|
0x80, 0x01,
|
||||||
@ -449,6 +471,28 @@ static constexpr Bitmap bitmap_icon_nuoptix {
|
|||||||
{ 16, 16 }, bitmap_icon_nuoptix_data
|
{ 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[] = {
|
static constexpr uint8_t bitmap_icon_closecall_data[] = {
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
0x00, 0x10,
|
0x00, 0x10,
|
||||||
@ -1751,6 +1795,28 @@ static constexpr Bitmap bitmap_bulb_off {
|
|||||||
{ 24, 24 }, bitmap_bulb_off_data
|
{ 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 */
|
} /* namespace ui */
|
||||||
|
|
||||||
|
@ -233,13 +233,14 @@ space_info space(const path& p);
|
|||||||
} /* namespace filesystem */
|
} /* namespace filesystem */
|
||||||
} /* namespace std */
|
} /* namespace std */
|
||||||
|
|
||||||
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension);
|
|
||||||
std::vector<std::filesystem::path> 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 delete_file(const std::filesystem::path& file_path);
|
||||||
void rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
|
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);
|
uint32_t make_new_directory(const std::filesystem::path& dir_path);
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension);
|
||||||
|
std::vector<std::filesystem::path> 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 */
|
/* Values added to FatFs FRESULT enum, values outside the FRESULT data type */
|
||||||
static_assert(sizeof(FIL::err) == 1, "FatFs FIL::err size not expected.");
|
static_assert(sizeof(FIL::err) == 1, "FatFs FIL::err size not expected.");
|
||||||
|
|
||||||
|
@ -27,13 +27,14 @@
|
|||||||
//TEST: Check AFSK transmit end, skips last bits ?
|
//TEST: Check AFSK transmit end, skips last bits ?
|
||||||
//TEST: Imperial in whipcalc
|
//TEST: Imperial in whipcalc
|
||||||
|
|
||||||
//BUG: Crash on rename file with long filename
|
|
||||||
//BUG: Auto backlight off doesn't work anymore
|
//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: (Workaround ok) 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: SCANNER Lock on frequency, if frequency jump, still locked on first one
|
//BUG: SCANNER Lock on frequency, if frequency jump, still locked on first one
|
||||||
//BUG: SCANNER Multiple slices
|
//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: Cap Wav viewer position
|
||||||
//TODO: Adapt wav viewer position step
|
//TODO: Adapt wav viewer position step
|
||||||
//TODO: Use unit_auto_scale
|
//TODO: Use unit_auto_scale
|
||||||
@ -80,10 +81,7 @@ Continuous (Fox-oring)
|
|||||||
// Old or low-priority stuff:
|
// Old or low-priority stuff:
|
||||||
//TODO: Bodet :)
|
//TODO: Bodet :)
|
||||||
//TODO: Analog TV tx with camcorder font character generator
|
//TODO: Analog TV tx with camcorder font character generator
|
||||||
//TODO: Show address/data bit fields in OOK TX
|
|
||||||
//TODO: Scan for OOK TX
|
//TODO: Scan for OOK TX
|
||||||
//TODO: Script engine ?
|
|
||||||
//TODO: AFSK receiver
|
|
||||||
//TODO: Check more OOK encoders
|
//TODO: Check more OOK encoders
|
||||||
//BUG (fixed ?): No audio in about when shown second time
|
//BUG (fixed ?): No audio in about when shown second time
|
||||||
//TODO: Show MD5 mismatches for modules not found, etc...
|
//TODO: Show MD5 mismatches for modules not found, etc...
|
||||||
|
@ -23,6 +23,9 @@
|
|||||||
#include "replay_app.hpp"
|
#include "replay_app.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
|
#include "ui_fileman.hpp"
|
||||||
|
#include "io_file.hpp"
|
||||||
|
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
@ -31,31 +34,132 @@ using namespace portapack;
|
|||||||
|
|
||||||
namespace ui {
|
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<stream::Reader> reader;
|
||||||
|
|
||||||
|
auto p = std::make_unique<FileReader>();
|
||||||
|
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<ReplayThread>(
|
||||||
|
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<int8_t>(receiver_model.lna()),
|
||||||
|
static_cast<int8_t>(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(
|
ReplayAppView::ReplayAppView(
|
||||||
NavigationView& nav
|
NavigationView& nav
|
||||||
) : nav_ (nav)
|
) : nav_ (nav)
|
||||||
{
|
{
|
||||||
std::vector<std::filesystem::path> 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);
|
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
&field_frequency,
|
&field_frequency,
|
||||||
&field_frequency_step,
|
&field_frequency_step,
|
||||||
&field_rf_amp,
|
&field_rf_amp,
|
||||||
&replay_view,
|
&button_play,
|
||||||
|
&text_filename,
|
||||||
|
&text_duration,
|
||||||
|
&progressbar,
|
||||||
|
&button_open,
|
||||||
&waterfall,
|
&waterfall,
|
||||||
});
|
});
|
||||||
|
|
||||||
replay_view.set_file_list(file_list);
|
|
||||||
|
|
||||||
field_frequency.set_value(target_frequency());
|
field_frequency.set_value(target_frequency());
|
||||||
field_frequency.set_step(receiver_model.frequency_step());
|
field_frequency.set_step(receiver_model.frequency_step());
|
||||||
field_frequency.on_change = [this](rf::Frequency f) {
|
field_frequency.on_change = [this](rf::Frequency f) {
|
||||||
@ -76,8 +180,15 @@ ReplayAppView::ReplayAppView(
|
|||||||
this->field_frequency.set_step(v);
|
this->field_frequency.set_step(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
replay_view.on_error = [&nav](std::string message) {
|
button_play.on_select = [this](ImageButton&) {
|
||||||
nav.display_modal("Error", message);
|
this->toggle();
|
||||||
|
};
|
||||||
|
|
||||||
|
button_open.on_select = [this, &nav](Button&) {
|
||||||
|
auto new_view = nav.push<FileLoadView>(".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);
|
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) {
|
void ReplayAppView::on_target_frequency_changed(rf::Frequency f) {
|
||||||
set_target_frequency(f);
|
set_target_frequency(f);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
#include "ui_widget.hpp"
|
#include "ui_widget.hpp"
|
||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
#include "ui_replay_view.hpp"
|
#include "replay_thread.hpp"
|
||||||
#include "ui_spectrum.hpp"
|
#include "ui_spectrum.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -40,24 +40,40 @@ public:
|
|||||||
~ReplayAppView();
|
~ReplayAppView();
|
||||||
|
|
||||||
void on_hide() override;
|
void on_hide() override;
|
||||||
|
|
||||||
void set_parent_rect(const Rect new_parent_rect) override;
|
void set_parent_rect(const Rect new_parent_rect) override;
|
||||||
|
|
||||||
void focus() 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:
|
private:
|
||||||
NavigationView& nav_;
|
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);
|
void on_target_frequency_changed(rf::Frequency f);
|
||||||
|
void on_tx_progress(const uint32_t progress);
|
||||||
|
|
||||||
rf::Frequency target_frequency() const;
|
|
||||||
void set_target_frequency(const rf::Frequency new_value);
|
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<ReplayThread> replay_thread { };
|
||||||
|
bool ready_signal { false };
|
||||||
|
|
||||||
FrequencyField field_frequency {
|
FrequencyField field_frequency {
|
||||||
{ 0 * 8, 0 * 16 },
|
{ 0 * 8, 0 * 16 },
|
||||||
@ -71,12 +87,57 @@ private:
|
|||||||
{ 16 * 8, 0 * 16 }
|
{ 16 * 8, 0 * 16 }
|
||||||
};
|
};
|
||||||
|
|
||||||
ReplayView replay_view {
|
ImageButton button_play {
|
||||||
{ 0 * 8, 1 * 16, 30 * 8, 1 * 16 },
|
{ 0 * 8, 1 * 16 + 8, 2 * 8, 1 * 16 },
|
||||||
16384, 3
|
&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 { };
|
spectrum::WaterfallWidget waterfall { };
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_replay_thread_error {
|
||||||
|
Message::ID::ReplayThreadDone,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(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<const RequestSignalMessage*>(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<const TXProgressMessage*>(p);
|
||||||
|
this->on_tx_progress(message.progress);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
@ -41,10 +41,12 @@ ReplayThread::ReplayThread(
|
|||||||
std::unique_ptr<stream::Reader> reader,
|
std::unique_ptr<stream::Reader> reader,
|
||||||
size_t read_size,
|
size_t read_size,
|
||||||
size_t buffer_count,
|
size_t buffer_count,
|
||||||
|
bool* ready_signal,
|
||||||
std::function<void()> success_callback,
|
std::function<void()> success_callback,
|
||||||
std::function<void(File::Error)> error_callback
|
std::function<void(File::Error)> error_callback
|
||||||
) : config { read_size, buffer_count },
|
) : config { read_size, buffer_count },
|
||||||
reader { std::move(reader) },
|
reader { std::move(reader) },
|
||||||
|
ready_sig { ready_signal },
|
||||||
success_callback { std::move(success_callback) },
|
success_callback { std::move(success_callback) },
|
||||||
error_callback { std::move(error_callback) }
|
error_callback { std::move(error_callback) }
|
||||||
{
|
{
|
||||||
@ -79,7 +81,12 @@ Optional<File::Error> ReplayThread::run() {
|
|||||||
|
|
||||||
StreamBuffer* prefill_buffer { nullptr };
|
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 empty buffers fifo is not empty...
|
||||||
while (!buffers.empty()) {
|
while (!buffers.empty()) {
|
||||||
prefill_buffer = buffers.get_prefill();
|
prefill_buffer = buffers.get_prefill();
|
||||||
@ -87,7 +94,7 @@ Optional<File::Error> ReplayThread::run() {
|
|||||||
if (prefill_buffer == nullptr) {
|
if (prefill_buffer == nullptr) {
|
||||||
buffers.put_app(prefill_buffer);
|
buffers.put_app(prefill_buffer);
|
||||||
} else {
|
} else {
|
||||||
size_t blocks = prefill_buffer->capacity() / 512;
|
size_t blocks = 16384 / 512;
|
||||||
|
|
||||||
for (size_t c = 0; c < blocks; c++) {
|
for (size_t c = 0; c < blocks; c++) {
|
||||||
auto read_result = reader->read(&((uint8_t*)prefill_buffer->data())[c * 512], 512);
|
auto read_result = reader->read(&((uint8_t*)prefill_buffer->data())[c * 512], 512);
|
||||||
@ -96,23 +103,23 @@ Optional<File::Error> ReplayThread::run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prefill_buffer->set_size(prefill_buffer->capacity());
|
prefill_buffer->set_size(16384);
|
||||||
|
|
||||||
buffers.put(prefill_buffer);
|
buffers.put(prefill_buffer);
|
||||||
//if (!buffers.put(prefill_buffer)) for(;;) {};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
baseband::set_fifo_data(nullptr);
|
||||||
|
|
||||||
while( !chThdShouldTerminate() ) {
|
while( !chThdShouldTerminate() ) {
|
||||||
auto buffer = buffers.get();
|
auto buffer = buffers.get();
|
||||||
|
|
||||||
size_t blocks = buffer->capacity() / 512;
|
auto read_result = reader->read(buffer->data(), buffer->capacity());
|
||||||
|
if( read_result.is_error() ) {
|
||||||
for (size_t c = 0; c < blocks; c++) {
|
return read_result.error();
|
||||||
auto read_result = reader->read(&((uint8_t*)buffer->data())[c * 512], 512);
|
} else {
|
||||||
if( read_result.is_error() ) {
|
if (read_result.value() == 0) {
|
||||||
return read_result.error();
|
return { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ public:
|
|||||||
std::unique_ptr<stream::Reader> reader,
|
std::unique_ptr<stream::Reader> reader,
|
||||||
size_t read_size,
|
size_t read_size,
|
||||||
size_t buffer_count,
|
size_t buffer_count,
|
||||||
|
bool* ready_signal,
|
||||||
std::function<void()> success_callback,
|
std::function<void()> success_callback,
|
||||||
std::function<void(File::Error)> error_callback
|
std::function<void(File::Error)> error_callback
|
||||||
);
|
);
|
||||||
@ -57,6 +58,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
ReplayConfig config;
|
ReplayConfig config;
|
||||||
std::unique_ptr<stream::Reader> reader;
|
std::unique_ptr<stream::Reader> reader;
|
||||||
|
bool* ready_sig;
|
||||||
std::function<void()> success_callback;
|
std::function<void()> success_callback;
|
||||||
std::function<void(File::Error)> error_callback;
|
std::function<void(File::Error)> error_callback;
|
||||||
Thread* thread { nullptr };
|
Thread* thread { nullptr };
|
||||||
|
@ -32,14 +32,24 @@ namespace ui {
|
|||||||
void FileManBaseView::load_directory_contents(const std::filesystem::path& dir_path) {
|
void FileManBaseView::load_directory_contents(const std::filesystem::path& dir_path) {
|
||||||
current_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();
|
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"*")) {
|
for (const auto& entry : std::filesystem::directory_iterator(dir_path, u"*")) {
|
||||||
if (std::filesystem::is_regular_file(entry.status())) {
|
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())) {
|
} else if (std::filesystem::is_directory(entry.status())) {
|
||||||
entry_list.insert(entry_list.begin(), { entry.path(), 0, true });
|
entry_list.insert(entry_list.begin(), { entry.path(), 0, true });
|
||||||
}
|
}
|
||||||
@ -57,8 +67,10 @@ std::filesystem::path FileManBaseView::get_selected_path() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FileManBaseView::FileManBaseView(
|
FileManBaseView::FileManBaseView(
|
||||||
NavigationView& nav
|
NavigationView& nav,
|
||||||
) : nav_ (nav)
|
std::string filter
|
||||||
|
) : nav_ (nav),
|
||||||
|
extension_filter { filter }
|
||||||
{
|
{
|
||||||
load_directory_contents(current_path);
|
load_directory_contents(current_path);
|
||||||
|
|
||||||
@ -95,10 +107,6 @@ void FileManBaseView::focus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FileManBaseView::refresh_list() {
|
void FileManBaseView::refresh_list() {
|
||||||
std::string size_str { };
|
|
||||||
uint32_t suffix_index;
|
|
||||||
size_t file_size;
|
|
||||||
|
|
||||||
if (!entry_list.size()) {
|
if (!entry_list.size()) {
|
||||||
// Hide widgets, show warning
|
// Hide widgets, show warning
|
||||||
if (on_refresh_widgets)
|
if (on_refresh_widgets)
|
||||||
@ -111,9 +119,10 @@ void FileManBaseView::refresh_list() {
|
|||||||
menu_view.clear();
|
menu_view.clear();
|
||||||
|
|
||||||
for (size_t n = 0; n < entry_list.size(); n++) {
|
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({
|
menu_view.add_item({
|
||||||
entry_name,
|
entry_name,
|
||||||
@ -127,8 +136,8 @@ void FileManBaseView::refresh_list() {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
file_size = entry_list[n].size;
|
auto file_size = entry->size;
|
||||||
suffix_index = 0;
|
size_t suffix_index = 0;
|
||||||
|
|
||||||
while (file_size >= 1024) {
|
while (file_size >= 1024) {
|
||||||
file_size /= 1024;
|
file_size /= 1024;
|
||||||
@ -137,12 +146,23 @@ void FileManBaseView::refresh_list() {
|
|||||||
if (suffix_index > 4)
|
if (suffix_index > 4)
|
||||||
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({
|
menu_view.add_item({
|
||||||
entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||||
ui::Color::white(),
|
file_types[c].color,
|
||||||
&bitmap_icon_file,
|
file_types[c].icon,
|
||||||
[this](){
|
[this](){
|
||||||
if (on_select_entry)
|
if (on_select_entry)
|
||||||
on_select_entry();
|
on_select_entry();
|
||||||
@ -186,8 +206,9 @@ void FileLoadView::refresh_widgets(const bool v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FileLoadView::FileLoadView(
|
FileLoadView::FileLoadView(
|
||||||
NavigationView& nav
|
NavigationView& nav,
|
||||||
) : FileManBaseView(nav)
|
std::string filter
|
||||||
|
) : FileManBaseView(nav, filter)
|
||||||
{
|
{
|
||||||
on_refresh_widgets = [this](bool v) {
|
on_refresh_widgets = [this](bool v) {
|
||||||
refresh_widgets(v);
|
refresh_widgets(v);
|
||||||
@ -216,7 +237,7 @@ FileLoadView::FileLoadView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FileManagerView::on_rename(NavigationView& nav) {
|
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);
|
rename_file(get_selected_path(), *buffer);
|
||||||
load_directory_contents(current_path);
|
load_directory_contents(current_path);
|
||||||
refresh_list();
|
refresh_list();
|
||||||
@ -244,7 +265,7 @@ FileManagerView::~FileManagerView() {
|
|||||||
|
|
||||||
FileManagerView::FileManagerView(
|
FileManagerView::FileManagerView(
|
||||||
NavigationView& nav
|
NavigationView& nav
|
||||||
) : FileManBaseView(nav)
|
) : FileManBaseView(nav, "")
|
||||||
{
|
{
|
||||||
on_refresh_widgets = [this](bool v) {
|
on_refresh_widgets = [this](bool v) {
|
||||||
refresh_widgets(v);
|
refresh_widgets(v);
|
||||||
@ -271,7 +292,7 @@ FileManagerView::FileManagerView(
|
|||||||
button_new_dir.on_select = [this, &nav](Button&) {
|
button_new_dir.on_select = [this, &nav](Button&) {
|
||||||
name_buffer.clear();
|
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;
|
std::string path_str = *buffer;
|
||||||
|
|
||||||
make_new_directory(current_path.string() + '/' + path_str);
|
make_new_directory(current_path.string() + '/' + path_str);
|
||||||
@ -281,10 +302,8 @@ FileManagerView::FileManagerView(
|
|||||||
};
|
};
|
||||||
|
|
||||||
button_rename.on_select = [this, &nav](Button&) {
|
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().substr(0, max_filename_length);
|
||||||
name_buffer = entry_list[menu_view.highlighted()].entry_path.filename().string();
|
on_rename(nav);
|
||||||
on_rename(nav);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
button_delete.on_select = [this, &nav](Button&) {
|
button_delete.on_select = [this, &nav](Button&) {
|
||||||
|
@ -39,7 +39,8 @@ struct fileman_entry {
|
|||||||
class FileManBaseView : public View {
|
class FileManBaseView : public View {
|
||||||
public:
|
public:
|
||||||
FileManBaseView(
|
FileManBaseView(
|
||||||
NavigationView& nav
|
NavigationView& nav,
|
||||||
|
std::string filter
|
||||||
);
|
);
|
||||||
|
|
||||||
void focus() override;
|
void focus() override;
|
||||||
@ -52,13 +53,31 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
|
|
||||||
|
static constexpr size_t max_filename_length = 30 - 2;
|
||||||
|
|
||||||
const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" };
|
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_assoc_t> 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 };
|
bool empty_root { false };
|
||||||
std::function<void(void)> on_select_entry { nullptr };
|
std::function<void(void)> on_select_entry { nullptr };
|
||||||
std::function<void(bool)> on_refresh_widgets { nullptr };
|
std::function<void(bool)> on_refresh_widgets { nullptr };
|
||||||
std::vector<fileman_entry> entry_list { };
|
std::vector<fileman_entry> entry_list { };
|
||||||
std::filesystem::path current_path { u"" };
|
std::filesystem::path current_path { u"" };
|
||||||
|
std::string extension_filter { "" };
|
||||||
|
|
||||||
void change_category(int32_t category_id);
|
void change_category(int32_t category_id);
|
||||||
void refresh_list();
|
void refresh_list();
|
||||||
@ -113,7 +132,7 @@ class FileLoadView : public FileManBaseView {
|
|||||||
public:
|
public:
|
||||||
std::function<void(std::filesystem::path)> on_changed { };
|
std::function<void(std::filesystem::path)> on_changed { };
|
||||||
|
|
||||||
FileLoadView(NavigationView& nav);
|
FileLoadView(NavigationView& nav, std::string filter);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void refresh_widgets(const bool v);
|
void refresh_widgets(const bool v);
|
||||||
|
@ -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 <cstdint>
|
|
||||||
|
|
||||||
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<std::filesystem::path>& 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<stream::Reader> reader;
|
|
||||||
|
|
||||||
auto p = std::make_unique<FileReader>();
|
|
||||||
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<ReplayThread>(
|
|
||||||
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<int8_t>(receiver_model.lna()),
|
|
||||||
static_cast<int8_t>(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 */
|
|
@ -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 <cstddef>
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class ReplayView : public View {
|
|
||||||
public:
|
|
||||||
std::function<void(std::string)> 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<std::filesystem::path>& file_list);
|
|
||||||
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
bool is_active() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
using option_t = std::pair<std::string, int32_t>;
|
|
||||||
using options_t = std::vector<option_t>;
|
|
||||||
|
|
||||||
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<ReplayThread> replay_thread { };
|
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_replay_thread_error {
|
|
||||||
Message::ID::CaptureThreadDone,
|
|
||||||
[this](const Message* const p) {
|
|
||||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
|
||||||
this->handle_replay_thread_done(message.error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
} /* namespace ui */
|
|
||||||
|
|
||||||
#endif/*__UI_REPLAY_VIEW_H__*/
|
|
@ -150,7 +150,7 @@ ViewWavView::ViewWavView(
|
|||||||
});
|
});
|
||||||
|
|
||||||
button_open.on_select = [this, &nav](Button&) {
|
button_open.on_select = [this, &nav](Button&) {
|
||||||
auto open_view = nav.push<FileLoadView>();
|
auto open_view = nav.push<FileLoadView>(".WAV");
|
||||||
open_view->on_changed = [this](std::filesystem::path file_path) {
|
open_view->on_changed = [this](std::filesystem::path file_path) {
|
||||||
load_wav(file_path);
|
load_wav(file_path);
|
||||||
field_pos_seconds.focus();
|
field_pos_seconds.focus();
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "proc_replay.hpp"
|
#include "proc_replay.hpp"
|
||||||
#include "sine_table_int8.hpp"
|
#include "sine_table_int8.hpp"
|
||||||
|
#include "portapack_shared_memory.hpp"
|
||||||
|
|
||||||
#include "event_m4.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_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
|
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;
|
spectrum_samples = 0;
|
||||||
|
|
||||||
channel_spectrum.set_decimation_factor(1);
|
channel_spectrum.set_decimation_factor(1);
|
||||||
|
|
||||||
|
configured = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||||
/* 4MHz, 2048 samples */
|
/* 4MHz, 2048 samples */
|
||||||
|
|
||||||
size_t pos = 0;
|
if (!configured) return;
|
||||||
|
|
||||||
// File data is in C16 format, we need C8
|
// File data is in C16 format, we need C8
|
||||||
// File samplerate is 500kHz, we're at 4MHz
|
// 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
|
// So 256 * 4 bytes per sample (C16) = 1024 bytes from the file
|
||||||
if( stream ) {
|
if( stream ) {
|
||||||
const size_t bytes_to_read = sizeof(*buffer.p) * 2 * (buffer.count / 8); // *2 (C16), /8 (oversampling) should be == 1024
|
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);
|
// Fill and "stretch"
|
||||||
|
|
||||||
// Zero-stuff
|
|
||||||
for (size_t i = 0; i < buffer.count; i++) {
|
for (size_t i = 0; i < buffer.count; i++) {
|
||||||
|
if (i & 3) {
|
||||||
// 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)
|
|
||||||
buffer.p[i] = buffer.p[i - 1];
|
buffer.p[i] = buffer.p[i - 1];
|
||||||
else {*/
|
} else {
|
||||||
auto re_out = iq_buffer.p[i >> 3].real() >> 8;
|
auto re_out = iq_buffer.p[i >> 3].real() >> 8;
|
||||||
auto im_out = iq_buffer.p[i >> 3].imag() >> 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;
|
spectrum_samples += buffer.count;
|
||||||
if( spectrum_samples >= spectrum_interval_samples ) {
|
if( spectrum_samples >= spectrum_interval_samples ) {
|
||||||
spectrum_samples -= spectrum_interval_samples;
|
spectrum_samples -= spectrum_interval_samples;
|
||||||
channel_spectrum.feed(iq_buffer, channel_filter_pass_f, channel_filter_stop_f);
|
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,9 +87,16 @@ void ReplayProcessor::on_message(const Message* const message) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Message::ID::ReplayConfig:
|
case Message::ID::ReplayConfig:
|
||||||
|
configured = false;
|
||||||
|
bytes_read = 0;
|
||||||
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
|
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// App has prefilled the buffers, we're ready to go now
|
||||||
|
case Message::ID::FIFOData:
|
||||||
|
configured = true;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -104,6 +105,9 @@ void ReplayProcessor::on_message(const Message* const message) {
|
|||||||
void ReplayProcessor::replay_config(const ReplayConfigMessage& message) {
|
void ReplayProcessor::replay_config(const ReplayConfigMessage& message) {
|
||||||
if( message.config ) {
|
if( message.config ) {
|
||||||
stream = std::make_unique<StreamOutput>(message.config);
|
stream = std::make_unique<StreamOutput>(message.config);
|
||||||
|
|
||||||
|
// Tell application that the buffers and FIFO pointers are ready, prefill
|
||||||
|
shared_memory.application_queue.push(sig_message);
|
||||||
} else {
|
} else {
|
||||||
stream.reset();
|
stream.reset();
|
||||||
}
|
}
|
||||||
|
@ -47,26 +47,29 @@ private:
|
|||||||
|
|
||||||
BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit };
|
BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit };
|
||||||
|
|
||||||
std::array<complex16_t, 256> iq { }; // This fits in just right in allocated RAM - Too big ?
|
std::array<complex16_t, 256> iq { };
|
||||||
const buffer_c16_t iq_buffer {
|
const buffer_c16_t iq_buffer {
|
||||||
iq.data(),
|
iq.data(),
|
||||||
iq.size()
|
iq.size(),
|
||||||
|
baseband_fs / 8
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t channel_filter_pass_f = 0;
|
uint32_t channel_filter_pass_f = 0;
|
||||||
uint32_t channel_filter_stop_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<StreamOutput> stream { };
|
std::unique_ptr<StreamOutput> stream { };
|
||||||
|
|
||||||
SpectrumCollector channel_spectrum { };
|
SpectrumCollector channel_spectrum { };
|
||||||
size_t spectrum_interval_samples = 0;
|
size_t spectrum_interval_samples = 0;
|
||||||
size_t spectrum_samples = 0;
|
size_t spectrum_samples = 0;
|
||||||
|
|
||||||
|
bool configured { false };
|
||||||
|
uint32_t bytes_read { 0 };
|
||||||
|
|
||||||
void replay_config(const ReplayConfigMessage& message);
|
void replay_config(const ReplayConfigMessage& message);
|
||||||
|
|
||||||
|
TXProgressMessage txprogress_message { };
|
||||||
|
RequestSignalMessage sig_message { RequestSignalMessage::Signal::FillRequest };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif/*__PROC_REPLAY_HPP__*/
|
#endif/*__PROC_REPLAY_HPP__*/
|
||||||
|
@ -51,8 +51,6 @@ size_t StreamOutput::read(void* const data, const size_t length) {
|
|||||||
// We need a full buffer...
|
// We need a full buffer...
|
||||||
if( !fifo_buffers_full.out(active_buffer) ) {
|
if( !fifo_buffers_full.out(active_buffer) ) {
|
||||||
// ...but none are available. Hole in transmission (inform app and stop ?)
|
// ...but none are available. Hole in transmission (inform app and stop ?)
|
||||||
//active_buffer = nullptr;
|
|
||||||
//creg::m4txevent::assert();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,6 @@
|
|||||||
|
|
||||||
#include "buffer_exchange.hpp"
|
#include "buffer_exchange.hpp"
|
||||||
|
|
||||||
// DEBUG:
|
|
||||||
#include "hackrf_gpio.hpp"
|
|
||||||
using namespace hackrf::one;
|
|
||||||
|
|
||||||
BufferExchange* BufferExchange::obj { nullptr };
|
BufferExchange* BufferExchange::obj { nullptr };
|
||||||
|
|
||||||
BufferExchange::BufferExchange(
|
BufferExchange::BufferExchange(
|
||||||
|
@ -55,7 +55,6 @@ public:
|
|||||||
return fifo_buffers_for_baseband->in(p);
|
return fifo_buffers_for_baseband->in(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TESTING...
|
|
||||||
bool put_app(StreamBuffer* const p) {
|
bool put_app(StreamBuffer* const p) {
|
||||||
return fifo_buffers_for_application->in(p);
|
return fifo_buffers_for_application->in(p);
|
||||||
}
|
}
|
||||||
|
@ -70,49 +70,49 @@ struct Color {
|
|||||||
return { 255, 0, 0 };
|
return { 255, 0, 0 };
|
||||||
}
|
}
|
||||||
static constexpr Color dark_red() {
|
static constexpr Color dark_red() {
|
||||||
return { 127, 0, 0 };
|
return { 191, 0, 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr Color orange() {
|
static constexpr Color orange() {
|
||||||
return { 255, 175, 0 };
|
return { 255, 175, 0 };
|
||||||
}
|
}
|
||||||
static constexpr Color dark_orange() {
|
static constexpr Color dark_orange() {
|
||||||
return { 127, 88, 0 };
|
return { 191, 88, 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr Color yellow() {
|
static constexpr Color yellow() {
|
||||||
return { 255, 255, 0 };
|
return { 255, 255, 0 };
|
||||||
}
|
}
|
||||||
static constexpr Color dark_yellow() {
|
static constexpr Color dark_yellow() {
|
||||||
return { 127, 127, 0 };
|
return { 191, 191, 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr Color green() {
|
static constexpr Color green() {
|
||||||
return { 0, 255, 0 };
|
return { 0, 255, 0 };
|
||||||
}
|
}
|
||||||
static constexpr Color dark_green() {
|
static constexpr Color dark_green() {
|
||||||
return { 0, 127, 0 };
|
return { 0, 191, 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr Color blue() {
|
static constexpr Color blue() {
|
||||||
return { 0, 0, 255 };
|
return { 0, 0, 255 };
|
||||||
}
|
}
|
||||||
static constexpr Color dark_blue() {
|
static constexpr Color dark_blue() {
|
||||||
return { 0, 0, 127 };
|
return { 0, 0, 191 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr Color cyan() {
|
static constexpr Color cyan() {
|
||||||
return { 0, 255, 255 };
|
return { 0, 255, 255 };
|
||||||
}
|
}
|
||||||
static constexpr Color dark_cyan() {
|
static constexpr Color dark_cyan() {
|
||||||
return { 0, 127, 127 };
|
return { 0, 191, 191 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr Color magenta() {
|
static constexpr Color magenta() {
|
||||||
return { 255, 0, 255 };
|
return { 255, 0, 255 };
|
||||||
}
|
}
|
||||||
static constexpr Color dark_magenta() {
|
static constexpr Color dark_magenta() {
|
||||||
return { 127, 0, 127 };
|
return { 191, 0, 191 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr Color white() {
|
static constexpr Color white() {
|
||||||
@ -120,10 +120,10 @@ struct Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static constexpr Color light_grey() {
|
static constexpr Color light_grey() {
|
||||||
return { 127, 127, 127 };
|
return { 191, 191, 191 };
|
||||||
}
|
}
|
||||||
static constexpr Color grey() {
|
static constexpr Color grey() {
|
||||||
return { 91, 91, 91 };
|
return { 127, 127, 127 };
|
||||||
}
|
}
|
||||||
static constexpr Color dark_grey() {
|
static constexpr Color dark_grey() {
|
||||||
return { 63, 63, 63 };
|
return { 63, 63, 63 };
|
||||||
|
BIN
firmware/graphics/icon_file_image.png
Normal file
BIN
firmware/graphics/icon_file_image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 146 B |
BIN
firmware/graphics/icon_file_iq.png
Normal file
BIN
firmware/graphics/icon_file_iq.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 148 B |
BIN
firmware/graphics/icon_file_text.png
Normal file
BIN
firmware/graphics/icon_file_text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 143 B |
Binary file not shown.
Loading…
Reference in New Issue
Block a user