Skip to content

Commit

Permalink
C8 capture support (#1286)
Browse files Browse the repository at this point in the history
* C8 conversion

* C8 conversion

* C8 support

* C8 support

* C8 support

* C8 support

* Don't auto-convert GPS C8 files

* C8 support

* C8 support

* C8 support

* Remove hang workaround (different PR)

* Comment change

* Clang

* Clang

* Clang

* Merged change from PR #1287

* C8 support

* C8 support

* Improve bandwidth display

* Merged minor optimization from PR 1289

* Merge change from PR 1289

* Use complex types for C8/C16 conversion

* C8 support

* C8 support

* C8 support

* C8 support

* Roll back changes

* Roll back C8 changes

* C8 support

* C8 support

* C8 support

* C8 support

* C8 support

* Don't transmit samples past EOF

* Don't transmit samples past EOF

* Clang

* Clang attempt

* Clang attempt

* C8 support

* Clang
  • Loading branch information
NotherNgineer committed Jul 22, 2023
1 parent 8eafe27 commit d6b0173
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 46 deletions.
1 change: 1 addition & 0 deletions firmware/application/CMakeLists.txt
Expand Up @@ -179,6 +179,7 @@ set(CPPSRC
file.cpp
freqman_db.cpp
freqman.cpp
io_convert.cpp
io_file.cpp
io_wave.cpp
irq_controls.cpp
Expand Down
6 changes: 6 additions & 0 deletions firmware/application/apps/capture_app.cpp
Expand Up @@ -44,6 +44,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
&field_lna,
&field_vga,
&option_bandwidth,
&option_format,
&record_view,
&waterfall,
});
Expand All @@ -62,6 +63,11 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
this->field_frequency.set_step(v);
};

option_format.set_selected_index(0); // Default to C16
option_format.on_change = [this](size_t, uint32_t file_type) {
record_view.set_file_type((RecordView::FileType)file_type);
};

option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
Expand Down
7 changes: 7 additions & 0 deletions firmware/application/apps/capture_app.hpp
Expand Up @@ -58,6 +58,7 @@ class CaptureAppView : public View {

Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
{{11 * 8, 1 * 16}, "Format:", Color::light_grey()},
};

RSSI rssi{
Expand Down Expand Up @@ -87,6 +88,12 @@ class CaptureAppView : public View {
5,
{}};

OptionsField option_format{
{18 * 8, 1 * 16},
3,
{{"C16", RecordView::FileType::RawS16},
{"C8", RecordView::FileType::RawS8}}};

RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
u"BBD_????.*",
Expand Down
9 changes: 6 additions & 3 deletions firmware/application/apps/replay_app.cpp
Expand Up @@ -26,6 +26,8 @@

#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "io_convert.hpp"

#include "baseband_api.hpp"
#include "metadata_file.hpp"
#include "portapack.hpp"
Expand Down Expand Up @@ -75,7 +77,8 @@ void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
progressbar.set_max(file_size);
text_filename.set(truncate(file_path.filename().string(), 12));

auto duration = ms_duration(file_size, sample_rate, 4);
uint8_t sample_size = capture_file_sample_size(current()->path);
auto duration = ms_duration(file_size, sample_rate, sample_size);
text_duration.set(to_string_time_ms(duration));

button_play.focus();
Expand Down Expand Up @@ -110,7 +113,7 @@ void ReplayAppView::start() {

std::unique_ptr<stream::Reader> reader;

auto p = std::make_unique<FileReader>();
auto p = std::make_unique<FileConvertReader>();
auto open_error = p->open(file_path);
if (open_error.is_valid()) {
file_error();
Expand Down Expand Up @@ -191,7 +194,7 @@ ReplayAppView::ReplayAppView(
};

button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".C16");
auto open_view = nav.push<FileLoadView>(".C*");
open_view->on_changed = [this](fs::path new_file_path) {
on_file_changed(new_file_path);
};
Expand Down
23 changes: 15 additions & 8 deletions firmware/application/apps/ui_fileman.cpp
Expand Up @@ -39,7 +39,10 @@ namespace fs = std::filesystem;
namespace ui {
static const fs::path txt_ext{u".TXT"};
static const fs::path ppl_ext{u".PPL"};
static const fs::path c8_ext{u".C8"};
static const fs::path c16_ext{u".C16"};
static const fs::path c32_ext{u".C32"};
static const fs::path cxx_ext{u".C*"};
static const fs::path png_ext{u".PNG"};
static const fs::path bmp_ext{u".BMP"};
} // namespace ui
Expand Down Expand Up @@ -78,14 +81,17 @@ fs::path get_partner_file(fs::path path) {
return {};
auto ext = path.extension();

if (path_iequal(ext, txt_ext))
ext = c16_ext;
else if (path_iequal(ext, c16_ext))
ext = txt_ext;
else
if (is_cxx_capture_file(path))
path.replace_extension(txt_ext);
else if (path_iequal(ext, txt_ext)) {
path.replace_extension(c8_ext);
if (!fs::file_exists(path))
path.replace_extension(c16_ext);
if (!fs::file_exists(path))
path.replace_extension(c32_ext);
} else
return {};

path.replace_extension(ext);
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{};
}

Expand Down Expand Up @@ -141,6 +147,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
current_path = dir_path;
entry_list.clear();
auto filtering = !extension_filter.empty();
bool cxx_file = path_iequal(cxx_ext, extension_filter);

text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));

Expand All @@ -150,7 +157,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
continue;

if (fs::is_regular_file(entry.status())) {
if (!filtering || path_iequal(entry.path().extension(), extension_filter))
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
} else if (fs::is_directory(entry.status())) {
insert_sorted(entry_list, {entry.path(), 0, true});
Expand Down Expand Up @@ -497,7 +504,7 @@ bool FileManagerView::handle_file_open() {
if (path_iequal(txt_ext, ext)) {
nav_.push<TextEditorView>(path);
return true;
} else if (path_iequal(c16_ext, ext) || path_iequal(ppl_ext, ext)) {
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
// TODO: Enough memory to push?
nav_.push<PlaylistView>(path);
return true;
Expand Down
16 changes: 9 additions & 7 deletions firmware/application/apps/ui_playlist.cpp
Expand Up @@ -27,6 +27,8 @@
#include "convert.hpp"
#include "file_reader.hpp"
#include "io_file.hpp"
#include "io_convert.hpp"

#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "utility.hpp"
Expand All @@ -48,7 +50,6 @@ namespace fs = std::filesystem;
namespace ui {

// TODO: consolidate extesions into a shared header?
static const fs::path c16_ext = u".C16";
static const fs::path ppl_ext = u".PPL";

void PlaylistView::load_file(const fs::path& playlist_path) {
Expand Down Expand Up @@ -258,7 +259,7 @@ void PlaylistView::send_current_track() {
chThdSleepMilliseconds(current()->ms_delay);

// Open the sample file to send.
auto reader = std::make_unique<FileReader>();
auto reader = std::make_unique<FileConvertReader>();
auto error = reader->open(current()->path);
if (error) {
show_file_error(current()->path, "Can't open file to send.");
Expand Down Expand Up @@ -323,9 +324,10 @@ void PlaylistView::update_ui() {
chDbgAssert(!at_end(), "update_ui #1", "current_index_ invalid");

text_filename.set(current()->path.filename().string());
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, 0) + "Hz");
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, (current()->metadata.sample_rate > 1000000) ? 2 : 0) + "Hz");

auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, 4);
uint8_t sample_size = capture_file_sample_size(current()->path);
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, sample_size);
text_duration.set(to_string_time_ms(duration));
field_frequency.set_value(current()->metadata.center_frequency);

Expand All @@ -336,7 +338,7 @@ void PlaylistView::update_ui() {

progressbar_track.set_max(playlist_db_.size() - 1);
progressbar_track.set_value(current_index_);
progressbar_transmit.set_max(current()->file_size);
progressbar_transmit.set_max(current()->file_size * sizeof(complex16_t) / sample_size);
}

button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
Expand Down Expand Up @@ -406,7 +408,7 @@ PlaylistView::PlaylistView(
button_add.on_select = [this, &nav]() {
if (is_active())
return;
auto open_view = nav_.push<FileLoadView>(".C16");
auto open_view = nav_.push<FileLoadView>(".C*");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
add_entry(std::move(path));
Expand Down Expand Up @@ -459,7 +461,7 @@ PlaylistView::PlaylistView(
auto ext = path.extension();
if (path_iequal(ext, ppl_ext))
on_file_changed(path);
else if (path_iequal(ext, c16_ext))
else if (is_cxx_capture_file(path))
add_entry(fs::path{path});
}

Expand Down
21 changes: 21 additions & 0 deletions firmware/application/file.cpp
Expand Up @@ -21,12 +21,18 @@
*/

#include "file.hpp"
#include "complex.hpp"

#include <algorithm>
#include <codecvt>
#include <cstring>
#include <locale>

namespace fs = std::filesystem;
static const fs::path c8_ext{u".C8"};
static const fs::path c16_ext{u".C16"};
static const fs::path c32_ext{u".C32"};

Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BYTE mode) {
auto result = f_open(&f, reinterpret_cast<const TCHAR*>(filename.c_str()), mode);
if (result == FR_OK) {
Expand Down Expand Up @@ -507,6 +513,21 @@ bool path_iequal(
return false;
}

bool is_cxx_capture_file(const path& filename) {
auto ext = filename.extension();
return path_iequal(c8_ext, ext) || path_iequal(c16_ext, ext) || path_iequal(c32_ext, ext);
}

uint8_t capture_file_sample_size(const path& filename) {
if (path_iequal(filename.extension(), c8_ext))
return sizeof(complex8_t);
if (path_iequal(filename.extension(), c16_ext))
return sizeof(complex16_t);
if (path_iequal(filename.extension(), c32_ext))
return sizeof(complex32_t);
return 0;
}

directory_iterator::directory_iterator(
const std::filesystem::path& path,
const std::filesystem::path& wild)
Expand Down
2 changes: 2 additions & 0 deletions firmware/application/file.hpp
Expand Up @@ -168,6 +168,8 @@ path operator/(const path& lhs, const path& rhs);

/* Case insensitive path equality on underlying "native" string. */
bool path_iequal(const path& lhs, const path& rhs);
bool is_cxx_capture_file(const path& filename);
uint8_t capture_file_sample_size(const path& filename);

using file_status = BYTE;

Expand Down
119 changes: 119 additions & 0 deletions firmware/application/io_convert.cpp
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2023 Mark Thompson
*
* 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 "io_convert.hpp"
#include "complex.hpp"

namespace fs = std::filesystem;
static const fs::path c8_ext = u".C8";
static const fs::path c32_ext = u".C32";

namespace file_convert {

// Convert buffer contents from c16 to c8.
// Same buffer used for input & output; input size is bytes; output size is bytes/2.
void c16_to_c8(const void* buffer, File::Size bytes) {
complex16_t* src = (complex16_t*)buffer;
complex8_t* dest = (complex8_t*)buffer;

for (File::Size i = 0; i < bytes / sizeof(complex16_t); i++) {
auto re_out = src[i].real() >> 8;
auto im_out = src[i].imag() >> 8;
dest[i] = {(int8_t)re_out, (int8_t)im_out};
}
}

// Convert c8 buffer to c16 buffer.
// Same buffer used for input & output; input size is bytes; output size is 2*bytes.
void c8_to_c16(const void* buffer, File::Size bytes) {
complex8_t* src = (complex8_t*)buffer;
complex16_t* dest = (complex16_t*)buffer;
uint32_t i = bytes / sizeof(complex8_t);

if (i != 0) {
do {
i--;
auto re_out = (int16_t)src[i].real() * 256; // C8 to C16 conversion;
auto im_out = (int16_t)src[i].imag() * 256; // Can't shift signed numbers left, so using multiply
dest[i] = {(int16_t)re_out, (int16_t)im_out};
} while (i != 0);
}
}

// Convert c32 buffer to c16 buffer.
// Same buffer used for input & output; input size is bytes; output size is bytes/2.
void c32_to_c16(const void* buffer, File::Size bytes) {
complex32_t* src = (complex32_t*)buffer;
complex16_t* dest = (complex16_t*)buffer;

for (File::Size i = 0; i < bytes / sizeof(complex32_t); i++) {
auto re_out = src[i].real() >> 16;
auto im_out = src[i].imag() >> 16;
dest[i] = {(int8_t)re_out, (int8_t)im_out};
}
}

} /* namespace file_convert */

// Automatically enables C8/C16 or C32/C16 conversion based on file extension
Optional<File::Error> FileConvertReader::open(const std::filesystem::path& filename) {
convert_c8_to_c16 = path_iequal(filename.extension(), c8_ext);
convert_c32_to_c16 = path_iequal(filename.extension(), c32_ext);
return file_.open(filename);
}

// If C8 conversion enabled, half the number of bytes are read from the file & expanded to fill the whole buffer.
// If C32 conversion enabled, the full byte count is read from the file, and compressed to half the buffer size.
File::Result<File::Size> FileConvertReader::read(void* const buffer, const File::Size bytes) {
auto read_result = file_.read(buffer, convert_c8_to_c16 ? bytes / 2 : bytes);
if (read_result.is_ok()) {
if (convert_c8_to_c16) {
file_convert::c8_to_c16(buffer, read_result.value());
read_result = read_result.value() * 2;
} else if (convert_c32_to_c16) {
file_convert::c32_to_c16(buffer, read_result.value());
read_result = read_result.value() / 2;
}
bytes_read_ += read_result.value();
}
return read_result;
}

// Automatically enables C8/C16 conversion based on file extension
Optional<File::Error> FileConvertWriter::create(const std::filesystem::path& filename) {
convert_c16_to_c8 = path_iequal(filename.extension(), c8_ext);
return file_.create(filename);
}

// If C8 conversion is enabled, half the number of bytes are written to the file.
File::Result<File::Size> FileConvertWriter::write(const void* const buffer, const File::Size bytes) {
if (convert_c16_to_c8) {
file_convert::c16_to_c8(buffer, bytes);
}
auto write_result = file_.write(buffer, convert_c16_to_c8 ? bytes / 2 : bytes);
if (write_result.is_ok()) {
if (convert_c16_to_c8) {
write_result = write_result.value() * 2;
}
bytes_written_ += write_result.value();
}
return write_result;
}

0 comments on commit d6b0173

Please sign in to comment.