diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 2b64c292..8aa4ef9c 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -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 diff --git a/firmware/application/apps/capture_app.cpp b/firmware/application/apps/capture_app.cpp index e331d26f..901c6e83 100644 --- a/firmware/application/apps/capture_app.cpp +++ b/firmware/application/apps/capture_app.cpp @@ -44,6 +44,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav) &field_lna, &field_vga, &option_bandwidth, + &option_format, &record_view, &waterfall, }); @@ -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. */ diff --git a/firmware/application/apps/capture_app.hpp b/firmware/application/apps/capture_app.hpp index 27a194a6..03ddfcb0 100644 --- a/firmware/application/apps/capture_app.hpp +++ b/firmware/application/apps/capture_app.hpp @@ -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{ @@ -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_????.*", diff --git a/firmware/application/apps/replay_app.cpp b/firmware/application/apps/replay_app.cpp index 6eff5834..31809dbf 100644 --- a/firmware/application/apps/replay_app.cpp +++ b/firmware/application/apps/replay_app.cpp @@ -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" @@ -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(); @@ -110,7 +113,7 @@ void ReplayAppView::start() { std::unique_ptr reader; - auto p = std::make_unique(); + auto p = std::make_unique(); auto open_error = p->open(file_path); if (open_error.is_valid()) { file_error(); @@ -191,7 +194,7 @@ ReplayAppView::ReplayAppView( }; button_open.on_select = [this, &nav](Button&) { - auto open_view = nav.push(".C16"); + auto open_view = nav.push(".C*"); open_view->on_changed = [this](fs::path new_file_path) { on_file_changed(new_file_path); }; diff --git a/firmware/application/apps/ui_fileman.cpp b/firmware/application/apps/ui_fileman.cpp index f964d231..5d9c3438 100644 --- a/firmware/application/apps/ui_fileman.cpp +++ b/firmware/application/apps/ui_fileman.cpp @@ -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 @@ -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{}; } @@ -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)); @@ -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}); @@ -497,7 +504,7 @@ bool FileManagerView::handle_file_open() { if (path_iequal(txt_ext, ext)) { nav_.push(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(path); return true; diff --git a/firmware/application/apps/ui_playlist.cpp b/firmware/application/apps/ui_playlist.cpp index 8e1f6644..ecc37d7a 100644 --- a/firmware/application/apps/ui_playlist.cpp +++ b/firmware/application/apps/ui_playlist.cpp @@ -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" @@ -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) { @@ -258,7 +259,7 @@ void PlaylistView::send_current_track() { chThdSleepMilliseconds(current()->ms_delay); // Open the sample file to send. - auto reader = std::make_unique(); + auto reader = std::make_unique(); auto error = reader->open(current()->path); if (error) { show_file_error(current()->path, "Can't open file to send."); @@ -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); @@ -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); @@ -406,7 +408,7 @@ PlaylistView::PlaylistView( button_add.on_select = [this, &nav]() { if (is_active()) return; - auto open_view = nav_.push(".C16"); + auto open_view = nav_.push(".C*"); open_view->push_dir(u"CAPTURES"); open_view->on_changed = [this](fs::path path) { add_entry(std::move(path)); @@ -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}); } diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp index 40f1285c..21df1e08 100644 --- a/firmware/application/file.cpp +++ b/firmware/application/file.cpp @@ -21,12 +21,18 @@ */ #include "file.hpp" +#include "complex.hpp" #include #include #include #include +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::open_fatfs(const std::filesystem::path& filename, BYTE mode) { auto result = f_open(&f, reinterpret_cast(filename.c_str()), mode); if (result == FR_OK) { @@ -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) diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index 94474780..6d321442 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -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; diff --git a/firmware/application/io_convert.cpp b/firmware/application/io_convert.cpp new file mode 100644 index 00000000..2c780804 --- /dev/null +++ b/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 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 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 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 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; +} diff --git a/firmware/application/io_convert.hpp b/firmware/application/io_convert.hpp new file mode 100644 index 00000000..0de482fe --- /dev/null +++ b/firmware/application/io_convert.hpp @@ -0,0 +1,81 @@ +/* + * 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. + */ + +#pragma once + +#include "io_file.hpp" + +#include "io.hpp" +#include "file.hpp" +#include "optional.hpp" + +#include + +namespace file_convert { + +void c8_to_c16(const void* buffer, File::Size bytes); +void c32_to_c16(const void* buffer, File::Size bytes); +void c16_to_c8(const void* buffer, File::Size bytes); + +} /* namespace file_convert */ + +class FileConvertReader : public stream::Reader { + public: + FileConvertReader() = default; + + FileConvertReader(const FileConvertReader&) = delete; + FileConvertReader& operator=(const FileConvertReader&) = delete; + FileConvertReader(FileConvertReader&& file) = delete; + FileConvertReader& operator=(FileConvertReader&&) = delete; + + Optional open(const std::filesystem::path& filename); + + File::Result read(void* const buffer, const File::Size bytes) override; + const File& file() const& { return file_; } + + bool convert_c8_to_c16{}; + bool convert_c32_to_c16{}; + + protected: + File file_{}; + uint64_t bytes_read_{0}; +}; + +class FileConvertWriter : public stream::Writer { + public: + FileConvertWriter() = default; + + FileConvertWriter(const FileConvertWriter&) = delete; + FileConvertWriter& operator=(const FileConvertWriter&) = delete; + FileConvertWriter(FileConvertWriter&& file) = delete; + FileConvertWriter& operator=(FileConvertWriter&&) = delete; + + Optional create(const std::filesystem::path& filename); + + File::Result write(const void* const buffer, const File::Size bytes) override; + const File& file() const& { return file_; } + + bool convert_c16_to_c8{}; + + protected: + File file_{}; + uint64_t bytes_written_{0}; +}; diff --git a/firmware/application/ui_record_view.cpp b/firmware/application/ui_record_view.cpp index 2aab55f4..3ce95fb4 100644 --- a/firmware/application/ui_record_view.cpp +++ b/firmware/application/ui_record_view.cpp @@ -26,6 +26,7 @@ using namespace portapack; #include "io_file.hpp" #include "io_wave.hpp" +#include "io_convert.hpp" #include "baseband_api.hpp" #include "metadata_file.hpp" @@ -194,6 +195,7 @@ void RecordView::start() { } } break; + case FileType::RawS8: case FileType::RawS16: { const auto metadata_file_error = write_metadata_file(get_metadata_path(base_path), @@ -204,8 +206,8 @@ void RecordView::start() { return; } - auto p = std::make_unique(); - auto create_error = p->create(base_path.replace_extension(u".C16")); + auto p = std::make_unique(); + auto create_error = p->create(base_path.replace_extension((file_type == FileType::RawS8) ? u".C8" : u".C16")); if (create_error.is_valid()) { handle_error(create_error.value()); } else { diff --git a/firmware/application/ui_record_view.hpp b/firmware/application/ui_record_view.hpp index 8b5c8de7..16d00e38 100644 --- a/firmware/application/ui_record_view.hpp +++ b/firmware/application/ui_record_view.hpp @@ -40,8 +40,10 @@ class RecordView : public View { std::function on_error{}; enum FileType { + RawS8 = 1, RawS16 = 2, - WAV = 3, + RawS32 = 3, + WAV = 4, }; RecordView( @@ -57,6 +59,8 @@ class RecordView : public View { void set_sampling_rate(const size_t new_sampling_rate); + void set_file_type(const FileType v) { file_type = v; } + void start(); void stop(); void on_hide() override; @@ -83,7 +87,7 @@ class RecordView : public View { const std::filesystem::path filename_stem_pattern; const std::filesystem::path folder; - const FileType file_type; + FileType file_type; const size_t write_size; const size_t buffer_count; size_t sampling_rate{0}; diff --git a/firmware/baseband/proc_gps_sim.cpp b/firmware/baseband/proc_gps_sim.cpp index d4ab50d2..bcb38b91 100644 --- a/firmware/baseband/proc_gps_sim.cpp +++ b/firmware/baseband/proc_gps_sim.cpp @@ -44,27 +44,27 @@ ReplayProcessor::ReplayProcessor() { void ReplayProcessor::execute(const buffer_c8_t& buffer) { /* 2.6MHz, 2048 samples */ - if (!configured) return; + if (!configured || !stream) return; // File data is in C8 format, which is what we need // File samplerate is 2.6MHz, which is what we need // To fill up the 2048-sample C8 buffer @ 2 bytes per sample = 4096 bytes - if (stream) { - const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count); - size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read); - bytes_read += bytes_read_this_iteration; + const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count); + size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read); + size_t samples_read_this_iteration = bytes_read_this_iteration / sizeof(*buffer.p); - // NB: Couldn't we have just read the data into buffer.p to start with, or some DMA/cache coherency concern? - // - // for (size_t i = 0; i < buffer.count; i++) { - // auto re_out = iq_buffer.p[i].real(); - // auto im_out = iq_buffer.p[i].imag(); - // buffer.p[i] = {(int8_t)re_out, (int8_t)im_out}; - // } - memcpy(buffer.p, iq_buffer.p, bytes_read_this_iteration); // memcpy should be more efficient than 1 byte at a time - } + bytes_read += bytes_read_this_iteration; - spectrum_samples += buffer.count; + // NB: Couldn't we have just read the data into buffer.p to start with, or some DMA/cache coherency concern? + // + // for (size_t i = 0; i < buffer.count; i++) { + // auto re_out = iq_buffer.p[i].real(); + // auto im_out = iq_buffer.p[i].imag(); + // buffer.p[i] = {(int8_t)re_out, (int8_t)im_out}; + // } + memcpy(buffer.p, iq_buffer.p, bytes_read_this_iteration); // memcpy should be more efficient than 1 byte at a time + + spectrum_samples += samples_read_this_iteration; if (spectrum_samples >= spectrum_interval_samples) { spectrum_samples -= spectrum_interval_samples; diff --git a/firmware/baseband/proc_replay.cpp b/firmware/baseband/proc_replay.cpp index 33bebc3a..1f70fe04 100644 --- a/firmware/baseband/proc_replay.cpp +++ b/firmware/baseband/proc_replay.cpp @@ -43,7 +43,7 @@ ReplayProcessor::ReplayProcessor() { void ReplayProcessor::execute(const buffer_c8_t& buffer) { /* 4MHz, 2048 samples */ - if (!configured) return; + if (!configured || !stream) return; // File data is in C16 format, we need C8 // File samplerate is 500kHz, we're at 4MHz @@ -52,13 +52,14 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) { // 2048 samples * 2 bytes per sample = 4096 bytes // Since we're oversampling by 4M/500k = 8, we only need 2048/8 = 256 samples from the file and duplicate them 8 times each // So 256 * 4 bytes per sample (C16) = 1024 bytes from the file - if (stream) { - const size_t bytes_to_read = sizeof(*buffer.p) * 2 * (buffer.count / 8); // *2 (C16), /8 (oversampling) should be == 1024 - bytes_read += stream->read(iq_buffer.p, bytes_to_read); - } + const size_t bytes_to_read = sizeof(*buffer.p) * 2 * (buffer.count / 8); // *2 (C16), /8 (oversampling) should be == 1024 + size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read); + size_t oversamples_this_iteration = bytes_read_this_iteration * 8 / (sizeof(*buffer.p) * 2); + + bytes_read += bytes_read_this_iteration; // Fill and "stretch" - for (size_t i = 0; i < buffer.count; i++) { + for (size_t i = 0; i < oversamples_this_iteration; i++) { if (i & 7) { buffer.p[i] = buffer.p[i - 1]; } else { @@ -68,7 +69,7 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) { } } - spectrum_samples += buffer.count; + spectrum_samples += oversamples_this_iteration; if (spectrum_samples >= spectrum_interval_samples) { spectrum_samples -= spectrum_interval_samples; channel_spectrum.feed(iq_buffer, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);