mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-12-17 19:54:35 -05:00
Oversample (#1336)
* WIP Oversample cleanup * WIP * WIP * WIP dynamic interpolation * WIP cleanup * Fix math errors * Add some optional assertions * Add support for x32 interpolation * Update proc_replay.cpp Typo
This commit is contained in:
parent
e2ad0a1b1a
commit
37386c29cb
@ -60,25 +60,28 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
|
|
||||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||||
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
|
option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
|
||||||
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
|
||||||
|
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
|
||||||
|
auto sample_rate = bandwidth;
|
||||||
|
|
||||||
|
/* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
||||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card */
|
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
|
||||||
|
|
||||||
// For lower bandwidths, (12k5, 16k, 20k), increase the oversample rate to get a higher sample rate.
|
|
||||||
OversampleRate oversample_rate = base_rate >= 25'000 ? OversampleRate::Rate8x : OversampleRate::Rate16x;
|
|
||||||
|
|
||||||
// HackRF suggests a minimum sample rate of 2M.
|
|
||||||
// Oversampling helps get to higher sample rates when recording lower bandwidths.
|
|
||||||
uint32_t sampling_rate = toUType(oversample_rate) * base_rate;
|
|
||||||
|
|
||||||
// Set up proper anti aliasing BPF bandwidth in MAX2837 before ADC sampling according to the new added BW Options.
|
|
||||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
|
||||||
|
|
||||||
waterfall.stop();
|
waterfall.stop();
|
||||||
record_view.set_sampling_rate(sampling_rate, oversample_rate); // NB: Actually updates the baseband.
|
|
||||||
receiver_model.set_sampling_rate(sampling_rate);
|
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
||||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
// NB: record_view is what actually updates proc_capture baseband settings.
|
||||||
|
auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
|
||||||
|
|
||||||
|
// Update the radio model with the actual sampling rate.
|
||||||
|
receiver_model.set_sampling_rate(actual_sample_rate);
|
||||||
|
|
||||||
|
// Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate.
|
||||||
|
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
|
||||||
|
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
||||||
|
|
||||||
waterfall.start();
|
waterfall.start();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ void ReplayAppView::start() {
|
|||||||
|
|
||||||
if (reader) {
|
if (reader) {
|
||||||
button_play.set_bitmap(&bitmap_stop);
|
button_play.set_bitmap(&bitmap_stop);
|
||||||
baseband::set_sample_rate(sample_rate * 8);
|
baseband::set_sample_rate(sample_rate, OversampleRate::x8);
|
||||||
|
|
||||||
replay_thread = std::make_unique<ReplayThread>(
|
replay_thread = std::make_unique<ReplayThread>(
|
||||||
std::move(reader),
|
std::move(reader),
|
||||||
@ -136,7 +136,7 @@ void ReplayAppView::start() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
transmitter_model.set_sampling_rate(sample_rate * 8);
|
transmitter_model.set_sampling_rate(sample_rate * toUType(OversampleRate::x8));
|
||||||
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
|
@ -24,18 +24,18 @@
|
|||||||
|
|
||||||
#include "ui_playlist.hpp"
|
#include "ui_playlist.hpp"
|
||||||
|
|
||||||
|
#include "baseband_api.hpp"
|
||||||
#include "convert.hpp"
|
#include "convert.hpp"
|
||||||
#include "file_reader.hpp"
|
#include "file_reader.hpp"
|
||||||
#include "io_file.hpp"
|
#include "io_file.hpp"
|
||||||
#include "io_convert.hpp"
|
#include "io_convert.hpp"
|
||||||
|
#include "oversample.hpp"
|
||||||
|
#include "portapack.hpp"
|
||||||
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "ui_fileman.hpp"
|
#include "ui_fileman.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
|
||||||
#include "baseband_api.hpp"
|
|
||||||
#include "portapack.hpp"
|
|
||||||
#include "portapack_persistent_memory.hpp"
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
@ -266,17 +266,16 @@ void PlaylistView::send_current_track() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplayThread starts immediately on construction so
|
// Update the sample rate in proc_replay baseband.
|
||||||
// these need to be set before creating the ReplayThread.
|
baseband::set_sample_rate(current()->metadata.sample_rate,
|
||||||
|
get_oversample_rate(current()->metadata.sample_rate));
|
||||||
|
|
||||||
|
// ReplayThread starts immediately on construction; must be set before creating.
|
||||||
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
|
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
|
||||||
transmitter_model.set_sampling_rate(current()->metadata.sample_rate * 8);
|
transmitter_model.set_sampling_rate(get_actual_sample_rate(current()->metadata.sample_rate));
|
||||||
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
// Set baseband sample rate too for waterfall to be correct.
|
|
||||||
// TODO: Why doesn't the transmitter_model just handle this?
|
|
||||||
baseband::set_sample_rate(transmitter_model.sampling_rate());
|
|
||||||
|
|
||||||
// Reset the transmit progress bar.
|
// Reset the transmit progress bar.
|
||||||
progressbar_transmit.set_value(0);
|
progressbar_transmit.set_value(0);
|
||||||
|
|
||||||
|
@ -347,13 +347,8 @@ void spectrum_streaming_stop() {
|
|||||||
send_message(&message);
|
send_message(&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_sample_rate(const uint32_t sample_rate) {
|
void set_sample_rate(uint32_t sample_rate, OversampleRate oversample_rate) {
|
||||||
SamplerateConfigMessage message{sample_rate};
|
SampleRateConfigMessage message{sample_rate, oversample_rate};
|
||||||
send_message(&message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_oversample_rate(OversampleRate oversample_rate) {
|
|
||||||
OversampleRateConfigMessage message{oversample_rate};
|
|
||||||
send_message(&message);
|
send_message(&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@ void shutdown();
|
|||||||
void spectrum_streaming_start();
|
void spectrum_streaming_start();
|
||||||
void spectrum_streaming_stop();
|
void spectrum_streaming_stop();
|
||||||
|
|
||||||
void set_sample_rate(const uint32_t sample_rate);
|
/* NB: sample_rate should be desired rate. Don't pre-scale. */
|
||||||
void set_oversample_rate(OversampleRate oversample_rate);
|
void set_sample_rate(uint32_t sample_rate, OversampleRate oversample_rate = OversampleRate::None);
|
||||||
void capture_start(CaptureConfig* const config);
|
void capture_start(CaptureConfig* const config);
|
||||||
void capture_stop();
|
void capture_stop();
|
||||||
void replay_start(ReplayConfig* const config);
|
void replay_start(ReplayConfig* const config);
|
||||||
|
@ -80,6 +80,8 @@ uint32_t ReplayThread::run() {
|
|||||||
chThdSleep(100);
|
chThdSleep(100);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr size_t block_size = 512;
|
||||||
|
|
||||||
// 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,10 +89,10 @@ uint32_t 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 = config.read_size / 512;
|
size_t blocks = config.read_size / block_size;
|
||||||
|
|
||||||
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 * block_size], block_size);
|
||||||
if (read_result.is_error()) {
|
if (read_result.is_error()) {
|
||||||
return READ_ERROR;
|
return READ_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -380,33 +380,34 @@ void WaterfallView::on_audio_spectrum() {
|
|||||||
|
|
||||||
} /* namespace spectrum */
|
} /* namespace spectrum */
|
||||||
|
|
||||||
|
// TODO: Comments below refer to a fixed oversample rate (8x), cleanup.
|
||||||
uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate) {
|
uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate) {
|
||||||
switch (sampling_rate) { // Use the var fs (sampling_rate) to set up BPF aprox < fs_max / 2 by Nyquist theorem.
|
switch (sampling_rate) { // Use the var fs (sampling_rate) to set up BPF aprox < fs_max / 2 by Nyquist theorem.
|
||||||
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max) fs = 8 x 250 kHz.
|
case 0 ... 2'000'000: // BW Captured range (0 <= 250kHz max) fs = 8 x 250 kHz.
|
||||||
return 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
return 1'750'000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||||
|
|
||||||
case 4000000 ... 6000000: // BW capture range (500k...750kHz max) fs_max = 8 x 750kHz = 6Mhz
|
case 4'000'000 ... 6'000'000: // BW capture range (500k...750kHz max) fs_max = 8 x 750kHz = 6Mhz
|
||||||
// BW 500k...750kHz, ex. 500kHz (fs = 8 x BW = 4Mhz), BW 600kHz (fs = 4,8Mhz), BW 750 kHz (fs = 6Mhz).
|
// BW 500k...750kHz, ex. 500kHz (fs = 8 x BW = 4Mhz), BW 600kHz (fs = 4,8Mhz), BW 750 kHz (fs = 6Mhz).
|
||||||
return 2500000; // In some IC, MAX2837 appears as 2250000, but both work similarly.
|
return 2'500'000; // In some IC, MAX2837 appears as 2250000, but both work similarly.
|
||||||
|
|
||||||
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz. (1Mhz showed slightly higher noise background).
|
case 8'800'000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz. (1Mhz showed slightly higher noise background).
|
||||||
return 3500000;
|
return 3'500'000;
|
||||||
|
|
||||||
case 14000000: // BW capture 1,75Mhz, fs = 8 x 1,75Mhz = 14Mhz
|
case 14'000'000: // BW capture 1,75Mhz, fs = 8 x 1,75Mhz = 14Mhz
|
||||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||||
return 5000000;
|
return 5'000'000;
|
||||||
|
|
||||||
case 16000000: // BW capture 2Mhz, fs = 8 x 2Mhz = 16Mhz
|
case 16'000'000: // BW capture 2Mhz, fs = 8 x 2Mhz = 16Mhz
|
||||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||||
return 6000000;
|
return 6'000'000;
|
||||||
|
|
||||||
case 20000000: // BW capture 2,5Mhz, fs = 8 x 2,5 Mhz = 20Mhz
|
case 20'000'000: // BW capture 2,5Mhz, fs = 8 x 2,5 Mhz = 20Mhz
|
||||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||||
return 7000000;
|
return 7'000'000;
|
||||||
|
|
||||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz = 22Mhz max ADC sampling and others.
|
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz = 22Mhz max ADC sampling and others.
|
||||||
// We tested also 9Mhz FPB slightly too much noise floor, better at 8Mhz.
|
// We tested also 9Mhz FPB slightly too much noise floor, better at 8Mhz.
|
||||||
return 8000000;
|
return 8'000'000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ using namespace portapack;
|
|||||||
|
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "metadata_file.hpp"
|
#include "metadata_file.hpp"
|
||||||
|
#include "oversample.hpp"
|
||||||
#include "rtc_time.hpp"
|
#include "rtc_time.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
@ -101,27 +102,28 @@ void RecordView::focus() {
|
|||||||
button_record.focus();
|
button_record.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecordView::set_sampling_rate(size_t new_sampling_rate, OversampleRate new_oversample_rate) {
|
uint32_t RecordView::set_sampling_rate(uint32_t new_sampling_rate) {
|
||||||
/* We are changing "REC" icon background to yellow in BW rec Options >600kHz
|
// Determine the oversampling needed (if any) and the actual sampling rate.
|
||||||
where we are NOT recording full IQ .C16 files (recorded files are decimated ones).
|
auto oversample_rate = get_oversample_rate(new_sampling_rate);
|
||||||
Those decimated recorded files, has not the full IQ samples.
|
auto actual_sampling_rate = new_sampling_rate * toUType(oversample_rate);
|
||||||
are ok as recorded spectrum indication, but they should not be used by Replay app.
|
|
||||||
|
|
||||||
We keep original black background in all the correct IQ .C16 files BW's Options */
|
/* We are changing "REC" icon background to yellow in BW rec Options >600kHz
|
||||||
if (new_sampling_rate > 4'800'000) { // > BW >600kHz (fs=8*BW), (750kHz...2750kHz)
|
* where we are NOT recording full IQ .C16 files (recorded files are decimated ones).
|
||||||
|
* Those decimated recorded files, has not the full IQ samples.
|
||||||
|
* are ok as recorded spectrum indication, but they should not be used by Replay app.
|
||||||
|
|
||||||
|
* We keep original black background in all the correct IQ .C16 files BW's Options. */
|
||||||
|
if (actual_sampling_rate > 4'800'000) { // > BW >600kHz (fs=8*BW), (750kHz...2750kHz)
|
||||||
button_record.set_background(ui::Color::yellow());
|
button_record.set_background(ui::Color::yellow());
|
||||||
} else {
|
} else {
|
||||||
button_record.set_background(ui::Color::black());
|
button_record.set_background(ui::Color::black());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_sampling_rate != sampling_rate ||
|
if (sampling_rate != new_sampling_rate) {
|
||||||
new_oversample_rate != oversample_rate) {
|
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
sampling_rate = new_sampling_rate;
|
sampling_rate = new_sampling_rate;
|
||||||
oversample_rate = new_oversample_rate;
|
baseband::set_sample_rate(sampling_rate, oversample_rate);
|
||||||
baseband::set_sample_rate(sampling_rate);
|
|
||||||
baseband::set_oversample_rate(oversample_rate);
|
|
||||||
|
|
||||||
button_record.hidden(sampling_rate == 0);
|
button_record.hidden(sampling_rate == 0);
|
||||||
text_record_filename.hidden(sampling_rate == 0);
|
text_record_filename.hidden(sampling_rate == 0);
|
||||||
@ -131,6 +133,24 @@ void RecordView::set_sampling_rate(size_t new_sampling_rate, OversampleRate new_
|
|||||||
|
|
||||||
update_status_display();
|
update_status_display();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return actual_sampling_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
OversampleRate RecordView::get_oversample_rate(uint32_t sample_rate) {
|
||||||
|
// No oversampling necessary for baseband audio processors.
|
||||||
|
if (file_type == FileType::WAV)
|
||||||
|
return OversampleRate::None;
|
||||||
|
|
||||||
|
auto rate = ::get_oversample_rate(sample_rate);
|
||||||
|
|
||||||
|
// Currently proc_capture only supports x8 and x16 for decimation.
|
||||||
|
if (rate < OversampleRate::x8)
|
||||||
|
rate = OversampleRate::x8;
|
||||||
|
else if (rate > OversampleRate::x16)
|
||||||
|
rate = OversampleRate::x16;
|
||||||
|
|
||||||
|
return rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setter for datetime and frequency filename
|
// Setter for datetime and frequency filename
|
||||||
@ -202,8 +222,7 @@ void RecordView::start() {
|
|||||||
case FileType::RawS8:
|
case FileType::RawS8:
|
||||||
case FileType::RawS16: {
|
case FileType::RawS16: {
|
||||||
const auto metadata_file_error = write_metadata_file(
|
const auto metadata_file_error = write_metadata_file(
|
||||||
get_metadata_path(base_path),
|
get_metadata_path(base_path), {receiver_model.target_frequency(), sampling_rate});
|
||||||
{receiver_model.target_frequency(), sampling_rate / toUType(oversample_rate)});
|
|
||||||
if (metadata_file_error.is_valid()) {
|
if (metadata_file_error.is_valid()) {
|
||||||
handle_error(metadata_file_error.value());
|
handle_error(metadata_file_error.value());
|
||||||
return;
|
return;
|
||||||
@ -278,12 +297,7 @@ void RecordView::update_status_display() {
|
|||||||
// - C8 captures 2 (I,Q) int8_t per sample or '2' bytes per sample.
|
// - C8 captures 2 (I,Q) int8_t per sample or '2' bytes per sample.
|
||||||
// - C16 captures 2 (I,Q) int16_t per sample or '4' bytes per sample.
|
// - C16 captures 2 (I,Q) int16_t per sample or '4' bytes per sample.
|
||||||
const auto bytes_per_sample = file_type == FileType::RawS16 ? 4 : 2;
|
const auto bytes_per_sample = file_type == FileType::RawS16 ? 4 : 2;
|
||||||
// WAV files are not oversampled, but C8 and C16 are. Divide by the
|
const uint32_t bytes_per_second = sampling_rate * bytes_per_sample;
|
||||||
// oversample rate to get the effective sample rate.
|
|
||||||
const auto effective_sampling_rate = file_type == FileType::WAV
|
|
||||||
? sampling_rate
|
|
||||||
: sampling_rate / toUType(oversample_rate);
|
|
||||||
const uint32_t bytes_per_second = effective_sampling_rate * bytes_per_sample;
|
|
||||||
const uint32_t available_seconds = space_info.free / bytes_per_second;
|
const uint32_t available_seconds = space_info.free / bytes_per_second;
|
||||||
const uint32_t seconds = available_seconds % 60;
|
const uint32_t seconds = available_seconds % 60;
|
||||||
const uint32_t available_minutes = available_seconds / 60;
|
const uint32_t available_minutes = available_seconds / 60;
|
||||||
|
@ -56,16 +56,11 @@ class RecordView : public View {
|
|||||||
|
|
||||||
void focus() override;
|
void focus() override;
|
||||||
|
|
||||||
/* Sets the sampling rate and the oversampling "decimation" rate.
|
/* Sets the sampling rate for the baseband.
|
||||||
* These values are passed down to the baseband proc_capture. For
|
* NB: Do not pre-apply any oversampling. This function will determine
|
||||||
* Audio (WAV) recording, the OversampleRate should not be
|
* the correct amount of oversampling and return the actual sample rate
|
||||||
* specified and the default will be used. */
|
* that can be used to configure the radio or other UI element. */
|
||||||
/* TODO: Currently callers are expected to have already multiplied the
|
uint32_t set_sampling_rate(uint32_t new_sampling_rate);
|
||||||
* sample_rate with the oversample rate. It would be better move that
|
|
||||||
* logic to a single place. */
|
|
||||||
void set_sampling_rate(
|
|
||||||
size_t new_sampling_rate,
|
|
||||||
OversampleRate new_oversample_rate = OversampleRate::Rate8x);
|
|
||||||
|
|
||||||
void set_file_type(const FileType v) { file_type = v; }
|
void set_file_type(const FileType v) { file_type = v; }
|
||||||
|
|
||||||
@ -87,6 +82,8 @@ class RecordView : public View {
|
|||||||
void handle_capture_thread_done(const File::Error error);
|
void handle_capture_thread_done(const File::Error error);
|
||||||
void handle_error(const File::Error error);
|
void handle_error(const File::Error error);
|
||||||
|
|
||||||
|
OversampleRate get_oversample_rate(uint32_t sample_rate);
|
||||||
|
|
||||||
// bool pitch_rssi_enabled = false;
|
// bool pitch_rssi_enabled = false;
|
||||||
|
|
||||||
// Time Stamp
|
// Time Stamp
|
||||||
@ -98,8 +95,7 @@ class RecordView : public View {
|
|||||||
FileType file_type;
|
FileType file_type;
|
||||||
const size_t write_size;
|
const size_t write_size;
|
||||||
const size_t buffer_count;
|
const size_t buffer_count;
|
||||||
size_t sampling_rate{0};
|
uint32_t sampling_rate{0};
|
||||||
OversampleRate oversample_rate{OversampleRate::Rate8x};
|
|
||||||
SignalToken signal_token_tick_second{};
|
SignalToken signal_token_tick_second{};
|
||||||
|
|
||||||
Rectangle rect_background{
|
Rectangle rect_background{
|
||||||
|
@ -77,8 +77,8 @@ void AudioTXProcessor::on_message(const Message* const message) {
|
|||||||
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
|
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Message::ID::SamplerateConfig:
|
case Message::ID::SampleRateConfig:
|
||||||
samplerate_config(*reinterpret_cast<const SamplerateConfigMessage*>(message));
|
sample_rate_config(*reinterpret_cast<const SampleRateConfigMessage*>(message));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Message::ID::FIFOData:
|
case Message::ID::FIFOData:
|
||||||
@ -108,7 +108,7 @@ void AudioTXProcessor::replay_config(const ReplayConfigMessage& message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioTXProcessor::samplerate_config(const SamplerateConfigMessage& message) {
|
void AudioTXProcessor::sample_rate_config(const SampleRateConfigMessage& message) {
|
||||||
resample_inc = (((uint64_t)message.sample_rate) << 16) / baseband_fs; // 16.16 fixed point message.sample_rate
|
resample_inc = (((uint64_t)message.sample_rate) << 16) / baseband_fs; // 16.16 fixed point message.sample_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class AudioTXProcessor : public BasebandProcessor {
|
|||||||
bool configured{false};
|
bool configured{false};
|
||||||
uint32_t bytes_read{0};
|
uint32_t bytes_read{0};
|
||||||
|
|
||||||
void samplerate_config(const SamplerateConfigMessage& message);
|
void sample_rate_config(const SampleRateConfigMessage& message);
|
||||||
void audio_config(const AudioTXConfigMessage& message);
|
void audio_config(const AudioTXConfigMessage& message);
|
||||||
void replay_config(const ReplayConfigMessage& message);
|
void replay_config(const ReplayConfigMessage& message);
|
||||||
|
|
||||||
|
@ -66,19 +66,9 @@ void CaptureProcessor::on_message(const Message* const message) {
|
|||||||
channel_spectrum.on_message(message);
|
channel_spectrum.on_message(message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Message::ID::SamplerateConfig: {
|
case Message::ID::SampleRateConfig:
|
||||||
auto config = reinterpret_cast<const SamplerateConfigMessage*>(message);
|
sample_rate_config(*reinterpret_cast<const SampleRateConfigMessage*>(message));
|
||||||
baseband_fs = config->sample_rate;
|
|
||||||
update_for_rate_change();
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case Message::ID::OversampleRateConfig: {
|
|
||||||
auto config = reinterpret_cast<const OversampleRateConfigMessage*>(message);
|
|
||||||
oversample_rate = config->oversample_rate;
|
|
||||||
update_for_rate_change();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Message::ID::CaptureConfig:
|
case Message::ID::CaptureConfig:
|
||||||
capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
|
capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
|
||||||
@ -89,13 +79,15 @@ void CaptureProcessor::on_message(const Message* const message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CaptureProcessor::update_for_rate_change() {
|
void CaptureProcessor::sample_rate_config(const SampleRateConfigMessage& message) {
|
||||||
|
baseband_fs = message.sample_rate * toUType(message.oversample_rate);
|
||||||
|
oversample_rate = message.oversample_rate;
|
||||||
|
|
||||||
baseband_thread.set_sampling_rate(baseband_fs);
|
baseband_thread.set_sampling_rate(baseband_fs);
|
||||||
|
|
||||||
auto decim_0_factor = oversample_rate == OversampleRate::Rate8x
|
auto decim_0_factor = oversample_rate == OversampleRate::x8
|
||||||
? decim_0_4.decimation_factor
|
? decim_0_4.decimation_factor
|
||||||
: decim_0_8.decimation_factor;
|
: decim_0_8.decimation_factor;
|
||||||
|
|
||||||
size_t decim_0_output_fs = baseband_fs / decim_0_factor;
|
size_t decim_0_output_fs = baseband_fs / decim_0_factor;
|
||||||
|
|
||||||
size_t decim_1_input_fs = decim_0_output_fs;
|
size_t decim_1_input_fs = decim_0_output_fs;
|
||||||
@ -119,10 +111,10 @@ void CaptureProcessor::capture_config(const CaptureConfigMessage& message) {
|
|||||||
|
|
||||||
buffer_c16_t CaptureProcessor::decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst) {
|
buffer_c16_t CaptureProcessor::decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst) {
|
||||||
switch (oversample_rate) {
|
switch (oversample_rate) {
|
||||||
case OversampleRate::Rate8x:
|
case OversampleRate::x8:
|
||||||
return decim_0_4.execute(src, dst);
|
return decim_0_4.execute(src, dst);
|
||||||
|
|
||||||
case OversampleRate::Rate16x:
|
case OversampleRate::x16:
|
||||||
return decim_0_8.execute(src, dst);
|
return decim_0_8.execute(src, dst);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -42,7 +42,7 @@ class CaptureProcessor : public BasebandProcessor {
|
|||||||
void on_message(const Message* const message) override;
|
void on_message(const Message* const message) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t baseband_fs = 3072000;
|
size_t baseband_fs = 3072000; // aka: sample_rate
|
||||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||||
|
|
||||||
std::array<complex16_t, 512> dst{};
|
std::array<complex16_t, 512> dst{};
|
||||||
@ -67,15 +67,14 @@ class CaptureProcessor : public BasebandProcessor {
|
|||||||
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;
|
||||||
OversampleRate oversample_rate{OversampleRate::Rate8x};
|
OversampleRate oversample_rate{OversampleRate::x8};
|
||||||
|
|
||||||
/* NB: Threads should be the last members in the class definition. */
|
/* NB: Threads should be the last members in the class definition. */
|
||||||
BasebandThread baseband_thread{
|
BasebandThread baseband_thread{
|
||||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||||
RSSIThread rssi_thread{};
|
RSSIThread rssi_thread{};
|
||||||
|
|
||||||
/* Called to update members when the sample rate or oversample rate is changed. */
|
void sample_rate_config(const SampleRateConfigMessage& message);
|
||||||
void update_for_rate_change();
|
|
||||||
void capture_config(const CaptureConfigMessage& message);
|
void capture_config(const CaptureConfigMessage& message);
|
||||||
|
|
||||||
/* Dispatch to the correct decim_0 based on oversample rate. */
|
/* Dispatch to the correct decim_0 based on oversample rate. */
|
||||||
|
@ -83,8 +83,8 @@ void GPSReplayProcessor::on_message(const Message* const message) {
|
|||||||
channel_spectrum.on_message(message);
|
channel_spectrum.on_message(message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Message::ID::SamplerateConfig:
|
case Message::ID::SampleRateConfig:
|
||||||
samplerate_config(*reinterpret_cast<const SamplerateConfigMessage*>(message));
|
sample_rate_config(*reinterpret_cast<const SampleRateConfigMessage*>(message));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Message::ID::ReplayConfig:
|
case Message::ID::ReplayConfig:
|
||||||
@ -103,7 +103,7 @@ void GPSReplayProcessor::on_message(const Message* const message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPSReplayProcessor::samplerate_config(const SamplerateConfigMessage& message) {
|
void GPSReplayProcessor::sample_rate_config(const SampleRateConfigMessage& message) {
|
||||||
baseband_fs = message.sample_rate;
|
baseband_fs = message.sample_rate;
|
||||||
baseband_thread.set_sampling_rate(baseband_fs);
|
baseband_thread.set_sampling_rate(baseband_fs);
|
||||||
spectrum_interval_samples = baseband_fs / spectrum_rate_hz;
|
spectrum_interval_samples = baseband_fs / spectrum_rate_hz;
|
||||||
|
@ -64,7 +64,7 @@ class GPSReplayProcessor : public BasebandProcessor {
|
|||||||
bool configured{false};
|
bool configured{false};
|
||||||
uint32_t bytes_read{0};
|
uint32_t bytes_read{0};
|
||||||
|
|
||||||
void samplerate_config(const SamplerateConfigMessage& message);
|
void sample_rate_config(const SampleRateConfigMessage& message);
|
||||||
void replay_config(const ReplayConfigMessage& message);
|
void replay_config(const ReplayConfigMessage& message);
|
||||||
|
|
||||||
TXProgressMessage txprogress_message{};
|
TXProgressMessage txprogress_message{};
|
||||||
|
@ -41,43 +41,75 @@ ReplayProcessor::ReplayProcessor() {
|
|||||||
baseband_thread.start();
|
baseband_thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
// Change to 1 to enable buffer assertions in replay.
|
||||||
/* 4MHz, 2048 samples */
|
#define BUFFER_SIZE_ASSERT 0
|
||||||
|
|
||||||
|
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||||
if (!configured || !stream) return;
|
if (!configured || !stream) return;
|
||||||
|
|
||||||
buffer_c16_t iq_buffer{iq.data(), iq.size(), baseband_fs / 8};
|
// Because this is actually adding samples, alias
|
||||||
|
// oversample_rate so the math below is more clear.
|
||||||
|
const size_t interpolation_factor = toUType(oversample_rate);
|
||||||
|
|
||||||
// File data is in C16 format, we need C8
|
// Wrap the IQ data array in a buffer with the correct sample_rate.
|
||||||
// File samplerate is 500kHz, we're at 4MHz
|
buffer_c16_t iq_buffer{iq.data(), iq.size(), baseband_fs / interpolation_factor};
|
||||||
// iq_buffer can only be 512 C16 samples (RAM limitation)
|
|
||||||
// To fill up the 2048-sample C8 buffer, we need:
|
|
||||||
// 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
|
|
||||||
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;
|
// The IQ data in stream is C16 format and needs to be converted to C8 (N * 2).
|
||||||
|
// The data also needs to be interpolated so the effective sample rate is closer
|
||||||
|
// to 4Mhz. Because interpolation repeats a sample multiple times, fewer bytes
|
||||||
|
// are needed from the source stream in order to fill the buffer (count / oversample).
|
||||||
|
// Together the C16->C8 conversion and the interpolation give the number of
|
||||||
|
// bytes that need to be read from the source stream.
|
||||||
|
const size_t samples_to_read = buffer.count / interpolation_factor;
|
||||||
|
const size_t bytes_to_read = samples_to_read * sizeof(buffer_c16_t::Type);
|
||||||
|
|
||||||
// Fill and "stretch"
|
#if BUFFER_SIZE_ASSERT
|
||||||
for (size_t i = 0; i < oversamples_this_iteration; i++) {
|
// Verify the output buffer size is divisible by the interpolation factor.
|
||||||
if (i & 7) {
|
if (samples_to_read * interpolation_factor != buffer.count)
|
||||||
buffer.p[i] = buffer.p[i - 1];
|
chDbgPanic("Output not div.");
|
||||||
} else {
|
|
||||||
auto re_out = iq_buffer.p[i >> 3].real() >> 8;
|
// Is the input smaple buffer big enough?
|
||||||
auto im_out = iq_buffer.p[i >> 3].imag() >> 8;
|
if (samples_to_read > iq_buffer.size())
|
||||||
buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
|
chDbgPanic("IQ buf ovf.");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Read the C16 IQ data from the source stream.
|
||||||
|
size_t current_bytes_read = stream->read(iq_buffer.p, bytes_to_read);
|
||||||
|
|
||||||
|
// Compute the number of samples were actually read from the source.
|
||||||
|
size_t samples_read = current_bytes_read / sizeof(buffer_c16_t::Type);
|
||||||
|
|
||||||
|
// Write converted source samples to the output buffer with interpolation.
|
||||||
|
for (auto i = 0u; i < samples_read; ++i) {
|
||||||
|
int8_t re_out = iq_buffer.p[i].real() >> 8;
|
||||||
|
int8_t im_out = iq_buffer.p[i].imag() >> 8;
|
||||||
|
auto out_value = buffer_c8_t::Type{re_out, im_out};
|
||||||
|
|
||||||
|
// Interpolate sample.
|
||||||
|
for (auto j = 0u; j < interpolation_factor; ++j) {
|
||||||
|
size_t index = i * interpolation_factor + j;
|
||||||
|
buffer.p[index] = out_value;
|
||||||
|
|
||||||
|
#if BUFFER_SIZE_ASSERT
|
||||||
|
// Verify the index is within bounds.
|
||||||
|
if (index >= buffer.count)
|
||||||
|
chDbgPanic("Output bounds");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spectrum_samples += oversamples_this_iteration;
|
// Update tracking stats.
|
||||||
|
bytes_read += current_bytes_read;
|
||||||
|
spectrum_samples += samples_read * interpolation_factor;
|
||||||
|
|
||||||
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_low_f, channel_filter_high_f, channel_filter_transition);
|
channel_spectrum.feed(
|
||||||
|
iq_buffer, channel_filter_low_f,
|
||||||
|
channel_filter_high_f, channel_filter_transition);
|
||||||
|
|
||||||
txprogress_message.progress = bytes_read; // Inform UI about progress
|
// Inform UI about progress.
|
||||||
|
txprogress_message.progress = bytes_read;
|
||||||
txprogress_message.done = false;
|
txprogress_message.done = false;
|
||||||
shared_memory.application_queue.push(txprogress_message);
|
shared_memory.application_queue.push(txprogress_message);
|
||||||
}
|
}
|
||||||
@ -90,8 +122,8 @@ void ReplayProcessor::on_message(const Message* const message) {
|
|||||||
channel_spectrum.on_message(message);
|
channel_spectrum.on_message(message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Message::ID::SamplerateConfig:
|
case Message::ID::SampleRateConfig:
|
||||||
samplerate_config(*reinterpret_cast<const SamplerateConfigMessage*>(message));
|
sample_rate_config(*reinterpret_cast<const SampleRateConfigMessage*>(message));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Message::ID::ReplayConfig:
|
case Message::ID::ReplayConfig:
|
||||||
@ -110,9 +142,11 @@ void ReplayProcessor::on_message(const Message* const message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplayProcessor::samplerate_config(const SamplerateConfigMessage& message) {
|
void ReplayProcessor::sample_rate_config(const SampleRateConfigMessage& message) {
|
||||||
baseband_fs = message.sample_rate;
|
baseband_fs = message.sample_rate * toUType(message.oversample_rate);
|
||||||
|
oversample_rate = message.oversample_rate;
|
||||||
baseband_thread.set_sampling_rate(baseband_fs);
|
baseband_thread.set_sampling_rate(baseband_fs);
|
||||||
|
|
||||||
spectrum_interval_samples = baseband_fs / spectrum_rate_hz;
|
spectrum_interval_samples = baseband_fs / spectrum_rate_hz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ class ReplayProcessor : public BasebandProcessor {
|
|||||||
size_t baseband_fs = 3072000;
|
size_t baseband_fs = 3072000;
|
||||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||||
|
|
||||||
|
// Holds the read IQ data chunk from the file to send.
|
||||||
std::array<complex16_t, 256> iq{};
|
std::array<complex16_t, 256> iq{};
|
||||||
|
|
||||||
int32_t channel_filter_low_f = 0;
|
int32_t channel_filter_low_f = 0;
|
||||||
@ -58,8 +59,9 @@ class ReplayProcessor : public BasebandProcessor {
|
|||||||
|
|
||||||
bool configured{false};
|
bool configured{false};
|
||||||
uint32_t bytes_read{0};
|
uint32_t bytes_read{0};
|
||||||
|
OversampleRate oversample_rate = OversampleRate::x8;
|
||||||
|
|
||||||
void samplerate_config(const SamplerateConfigMessage& message);
|
void sample_rate_config(const SampleRateConfigMessage& message);
|
||||||
void replay_config(const ReplayConfigMessage& message);
|
void replay_config(const ReplayConfigMessage& message);
|
||||||
|
|
||||||
TXProgressMessage txprogress_message{};
|
TXProgressMessage txprogress_message{};
|
||||||
|
@ -49,6 +49,7 @@ using Timestamp = lpc43xx::rtc::RTC;
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct buffer_t {
|
struct buffer_t {
|
||||||
|
using Type = T;
|
||||||
T* const p;
|
T* const p;
|
||||||
const size_t count;
|
const size_t count;
|
||||||
const uint32_t sampling_rate;
|
const uint32_t sampling_rate;
|
||||||
|
@ -78,7 +78,7 @@ class Message {
|
|||||||
ReplayThreadDone = 21,
|
ReplayThreadDone = 21,
|
||||||
AFSKRxConfigure = 22,
|
AFSKRxConfigure = 22,
|
||||||
StatusRefresh = 23,
|
StatusRefresh = 23,
|
||||||
SamplerateConfig = 24,
|
SampleRateConfig = 24,
|
||||||
BTLERxConfigure = 25,
|
BTLERxConfigure = 25,
|
||||||
NRFRxConfigure = 26,
|
NRFRxConfigure = 26,
|
||||||
TXProgress = 27,
|
TXProgress = 27,
|
||||||
@ -111,7 +111,6 @@ class Message {
|
|||||||
APRSRxConfigure = 54,
|
APRSRxConfigure = 54,
|
||||||
SpectrumPainterBufferRequestConfigure = 55,
|
SpectrumPainterBufferRequestConfigure = 55,
|
||||||
SpectrumPainterBufferResponseConfigure = 56,
|
SpectrumPainterBufferResponseConfigure = 56,
|
||||||
OversampleRateConfig = 57,
|
|
||||||
MAX
|
MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -799,32 +798,37 @@ class RetuneMessage : public Message {
|
|||||||
uint32_t range = 0;
|
uint32_t range = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SamplerateConfigMessage : public Message {
|
/* Oversample/Interpolation sample rate multipliers. */
|
||||||
public:
|
|
||||||
constexpr SamplerateConfigMessage(
|
|
||||||
const uint32_t sample_rate)
|
|
||||||
: Message{ID::SamplerateConfig},
|
|
||||||
sample_rate(sample_rate) {
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t sample_rate = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Controls decimation handling in proc_capture. */
|
|
||||||
enum class OversampleRate : uint8_t {
|
enum class OversampleRate : uint8_t {
|
||||||
Rate8x = 8,
|
/* Use either to indicate there's no oversampling needed. */
|
||||||
Rate16x = 16,
|
None = 1,
|
||||||
|
x1 = None,
|
||||||
|
|
||||||
|
// 4x would make sense to have, but need to ensure it doesn't
|
||||||
|
// overrun the IQ read buffer in proc_replay.
|
||||||
|
|
||||||
|
/* Oversample rate of 8 times the sample rate. */
|
||||||
|
x8 = 8,
|
||||||
|
|
||||||
|
/* Oversample rate of 16 times the sample rate. */
|
||||||
|
x16 = 16,
|
||||||
|
|
||||||
|
/* Oversample rate of 32 times the sample rate. */
|
||||||
|
x32 = 32,
|
||||||
};
|
};
|
||||||
|
|
||||||
class OversampleRateConfigMessage : public Message {
|
class SampleRateConfigMessage : public Message {
|
||||||
public:
|
public:
|
||||||
constexpr OversampleRateConfigMessage(
|
constexpr SampleRateConfigMessage(
|
||||||
|
uint32_t sample_rate,
|
||||||
OversampleRate oversample_rate)
|
OversampleRate oversample_rate)
|
||||||
: Message{ID::OversampleRateConfig},
|
: Message{ID::SampleRateConfig},
|
||||||
|
sample_rate(sample_rate),
|
||||||
oversample_rate(oversample_rate) {
|
oversample_rate(oversample_rate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const OversampleRate oversample_rate{OversampleRate::Rate8x};
|
const uint32_t sample_rate = 0;
|
||||||
|
const OversampleRate oversample_rate = OversampleRate::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioLevelReportMessage : public Message {
|
class AudioLevelReportMessage : public Message {
|
||||||
|
61
firmware/common/oversample.hpp
Normal file
61
firmware/common/oversample.hpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Helpers for handling oversampling and interpolation. */
|
||||||
|
|
||||||
|
#ifndef __OVERSAMPLE_H__
|
||||||
|
#define __OVERSAMPLE_H__
|
||||||
|
|
||||||
|
#include "message.hpp"
|
||||||
|
#include "utility.hpp"
|
||||||
|
|
||||||
|
/* TODO:
|
||||||
|
* The decision to oversample/interpolate should only be a baseband concern.
|
||||||
|
* However, the baseband can't set up the radio (M0 code), so the apps also
|
||||||
|
* need to know about the "actual" sample rate so the radio settings can be
|
||||||
|
* applied correctly. Ideally the baseband would tell the apps what the
|
||||||
|
* actual sample rate is. Currently the apps are telling the baseband and
|
||||||
|
* that feels like a separation of concerns problem. */
|
||||||
|
|
||||||
|
/* HackRF suggests a minimum sample rate of 2M so a oversample rate is applied
|
||||||
|
* to the sample rate (pre-scale) to get the sample rate closer to that target.
|
||||||
|
* The baseband needs to know how to correctly decimate (or interpolate) so
|
||||||
|
* the set of allowed scalars is fixed (See OversampleRate enum).
|
||||||
|
* In testing, a minimum rate of 400kHz seems to the functional minimum.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Gets the oversample rate for a given sample rate.
|
||||||
|
* The oversample rate is used to increase the sample rate to improve SNR and quality.
|
||||||
|
* This is also used as the interpolation rate when replaying captures. */
|
||||||
|
inline OversampleRate get_oversample_rate(uint32_t sample_rate) {
|
||||||
|
if (sample_rate < 25'000) return OversampleRate::x32;
|
||||||
|
if (sample_rate < 50'000) return OversampleRate::x16;
|
||||||
|
|
||||||
|
return OversampleRate::x8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gets the actual sample rate for a given sample rate.
|
||||||
|
* This is the rate with the correct oversampling rate applied. */
|
||||||
|
inline uint32_t get_actual_sample_rate(uint32_t sample_rate) {
|
||||||
|
return sample_rate * toUType(get_oversample_rate(sample_rate));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*__OVERSAMPLE_H__*/
|
Loading…
Reference in New Issue
Block a user