Compare commits

...

4 Commits

Author SHA1 Message Date
Brumi-2021 cb0a4854f5 Increase REC BW Option till 1Mhz Capture App (#1352)
* Increase REC BW Option till 1Mhz Capture App

* Format issues

* miss type correction

* format issues
2023-08-06 21:54:24 +02:00
Mark Thompson 1188157a3e Reject bmp images that aren't 240 pixels wide (#1351) 2023-08-05 17:14:18 +02:00
gullradriel 2ae639412d fix/simplify internal timing (#1348)
* fix/simplify internal timing
2023-08-04 07:56:39 +02:00
Kyle Reed 37386c29cb 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
2023-08-02 21:59:26 +02:00
23 changed files with 310 additions and 194 deletions
+19 -16
View File
@@ -60,25 +60,28 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
};
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
/* 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. 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);
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
waterfall.stop();
record_view.set_sampling_rate(sampling_rate, oversample_rate); // NB: Actually updates the baseband.
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
// record_view determines the correct oversampling to apply and returns the actual sample rate.
// 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();
};
+2 -2
View File
@@ -124,7 +124,7 @@ void ReplayAppView::start() {
if (reader) {
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>(
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.enable();
+10 -11
View File
@@ -24,18 +24,18 @@
#include "ui_playlist.hpp"
#include "baseband_api.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "io_file.hpp"
#include "io_convert.hpp"
#include "oversample.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "utility.hpp"
#include "baseband_api.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include <unistd.h>
#include <fstream>
@@ -266,17 +266,16 @@ void PlaylistView::send_current_track() {
return;
}
// ReplayThread starts immediately on construction so
// these need to be set before creating the ReplayThread.
// Update the sample rate in proc_replay baseband.
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_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.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.
progressbar_transmit.set_value(0);
+10 -16
View File
@@ -859,18 +859,12 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
uint32_t local_recon_lock_duration = recon_lock_duration;
chrono_end = chTimeNow();
if (field_mode.selected_index_value() == SPEC_MODULATION) {
time_interval = chrono_end - chrono_start;
// capping here to avoid double check a frequency because time_interval is more than exactly 100 by a few units
// this is because we are making a measurement and not using a fixed value
if (time_interval > 100)
time_interval = 100;
if (field_lock_wait.value() == 0) {
if (time_interval <= 1) // capping here to avoid freeze because too quick
local_recon_lock_duration = 2; // minimum working tested value
else
local_recon_lock_duration = time_interval;
}
time_interval = chrono_end - chrono_start;
if (field_lock_wait.value() == 0) {
if (time_interval <= 1) // capping here to avoid freeze because too quick
local_recon_lock_duration = 2; // minimum working tested value
else
local_recon_lock_duration = time_interval;
}
chrono_start = chrono_end;
@@ -907,7 +901,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (db > squelch) // MATCHING LEVEL
{
freq_lock++;
timer += time_interval; // give some more time for next lock
timer += local_recon_lock_duration; // give some more time for next lock
} else {
// continuous, direct cut it if not consecutive match after 1 first match
if (recon_match_mode == RECON_MATCH_CONTINUOUS) {
@@ -961,7 +955,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
}
if (timer != 0) {
timer -= time_interval;
timer -= local_recon_lock_duration;
if (timer < 0) {
timer = 0;
}
@@ -1234,10 +1228,10 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
break;
case SPEC_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 200k (0) default
// bw 12k5 (0) default
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_capture);
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
field_bw.set_by_value(0);
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
record_view->set_sampling_rate(sampling_rate);
+2 -7
View File
@@ -347,13 +347,8 @@ void spectrum_streaming_stop() {
send_message(&message);
}
void set_sample_rate(const uint32_t sample_rate) {
SamplerateConfigMessage message{sample_rate};
send_message(&message);
}
void set_oversample_rate(OversampleRate oversample_rate) {
OversampleRateConfigMessage message{oversample_rate};
void set_sample_rate(uint32_t sample_rate, OversampleRate oversample_rate) {
SampleRateConfigMessage message{sample_rate, oversample_rate};
send_message(&message);
}
+2 -2
View File
@@ -94,8 +94,8 @@ void shutdown();
void spectrum_streaming_start();
void spectrum_streaming_stop();
void set_sample_rate(const uint32_t sample_rate);
void set_oversample_rate(OversampleRate oversample_rate);
/* NB: sample_rate should be desired rate. Don't pre-scale. */
void set_sample_rate(uint32_t sample_rate, OversampleRate oversample_rate = OversampleRate::None);
void capture_start(CaptureConfig* const config);
void capture_stop();
void replay_start(ReplayConfig* const config);
+4 -3
View File
@@ -82,10 +82,11 @@ options_t freqman_bandwidths[4] = {
{"100k", 100000},
{"250k", 250000},
{"500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/
{"600k", 600000}, /* That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) */
{"600k", 600000}, /* We doubled x2 previous REC BW limit , now extended BW from 600k to 1M with fast enough SD card in C16 or C8 format .*/
{"650k", 650000},
{"750k", 750000}, /* From this BW onwards, the LCD is ok, but the recorded file is decimated, (not real file size) */
{"1100k", 1100000},
{"750k", 750000},
{"1000k", 1000000}, /* New limit bandwith option for recording in C16 (in fast SD card) or in C8 */
{"1500k", 1500000}, /* From this BW onwards, the LCD is ok, but M4 CPU is having periodical sample rec dropps, (not real file size, accelerated replay) */
{"1750k", 1750000},
{"2000k", 2000000},
{"2500k", 2500000},
+4 -2
View File
@@ -80,6 +80,8 @@ uint32_t ReplayThread::run() {
chThdSleep(100);
};
constexpr size_t block_size = 512;
// While empty buffers fifo is not empty...
while (!buffers.empty()) {
prefill_buffer = buffers.get_prefill();
@@ -87,10 +89,10 @@ uint32_t ReplayThread::run() {
if (prefill_buffer == nullptr) {
buffers.put_app(prefill_buffer);
} else {
size_t blocks = config.read_size / 512;
size_t blocks = config.read_size / block_size;
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()) {
return READ_ERROR;
}
+23 -18
View File
@@ -380,33 +380,38 @@ void WaterfallView::on_audio_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) {
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.
return 1750000; // Minimum BPF MAX2837 for all those lower BW options.
switch (sampling_rate) { // Use the var fs (sampling_rate) to set up BPF aprox < fs_max / 2 by Nyquist theorem.
case 0 ... 2'000'000: // BW Captured range (0 <= 250kHz max) fs = 8 x 250 kHz.
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
// 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.
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).
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).
return 3500000;
case 8'000'000: // BW capture 1Mhz fs = 8 x 1Mhz = 8Mhz. (1Mhz showed slightly higher noise background).
return 3'500'000; // some low SD cards, if not showing avg. writting speed >4MB/sec, they will produce sammples drop at REC with 1MB and C16 format.
case 14000000: // 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.
return 5000000;
case 12'000'000: // BW capture 1,5Mhz, fs = 8 x 1,5Mhz = 12Mhz
// Good BPF, good matching, we have some periodical M4 % samples drop.
return 5'000'000;
case 16000000: // BW capture 2Mhz, fs = 8 x 2Mhz = 16Mhz
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
return 6000000;
case 14'000'000: // BW capture 1,75Mhz, fs = 8 x 1,75Mhz = 14Mhz
// Good BPF, good matching, we have some periodical M4 % samples drop.
return 5'000'000;
case 20000000: // 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.
return 7000000;
case 16'000'000: // BW capture 2Mhz, fs = 8 x 2Mhz = 16Mhz
// Good BPF, good matching, we have some periodical M4 % samples drop.
return 6'000'000;
case 20'000'000: // BW capture 2,5Mhz, fs = 8 x 2,5 Mhz = 20Mhz
// Good BPF, good matching, we have some periodical M4 % samples drop.
return 7'000'000;
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.
return 8000000;
return 8'000'000;
}
}
+34 -20
View File
@@ -30,6 +30,7 @@ using namespace portapack;
#include "baseband_api.hpp"
#include "metadata_file.hpp"
#include "oversample.hpp"
#include "rtc_time.hpp"
#include "string_format.hpp"
#include "utility.hpp"
@@ -101,27 +102,28 @@ void RecordView::focus() {
button_record.focus();
}
void RecordView::set_sampling_rate(size_t new_sampling_rate, OversampleRate new_oversample_rate) {
/* We are changing "REC" icon background to yellow in BW rec Options >600kHz
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.
uint32_t RecordView::set_sampling_rate(uint32_t new_sampling_rate) {
// Determine the oversampling needed (if any) and the actual sampling rate.
auto oversample_rate = get_oversample_rate(new_sampling_rate);
auto actual_sampling_rate = new_sampling_rate * toUType(oversample_rate);
We keep original black background in all the correct IQ .C16 files BW's Options */
if (new_sampling_rate > 4'800'000) { // > BW >600kHz (fs=8*BW), (750kHz...2750kHz)
/* We are changing "REC" icon background to yellow in BW rec Options >1Mhz
* > 1Mhz BW options , we are NOT recording full IQ .C16 files (those files has some periodical missing dropped samples).
* Those recorded files, has not the full IQ samples information, looks like decimated in file size.
* They are ok as recorded spectrum indication, but they should not be used by Replay app. (the voice speed will be accelerated)
* We keep original black background in all the correct IQ .C16 files BW's Options. */
if (actual_sampling_rate > 8'000'000) { // Till fw 1.7.4 BW limit yellow REC button was >500khz, now BW >1Mhz (fs=8*BW), (>1Mhz ..2M75)
button_record.set_background(ui::Color::yellow());
} else {
button_record.set_background(ui::Color::black());
}
if (new_sampling_rate != sampling_rate ||
new_oversample_rate != oversample_rate) {
if (sampling_rate != new_sampling_rate) {
stop();
sampling_rate = new_sampling_rate;
oversample_rate = new_oversample_rate;
baseband::set_sample_rate(sampling_rate);
baseband::set_oversample_rate(oversample_rate);
baseband::set_sample_rate(sampling_rate, oversample_rate);
button_record.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();
}
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
@@ -202,8 +222,7 @@ void RecordView::start() {
case FileType::RawS8:
case FileType::RawS16: {
const auto metadata_file_error = write_metadata_file(
get_metadata_path(base_path),
{receiver_model.target_frequency(), sampling_rate / toUType(oversample_rate)});
get_metadata_path(base_path), {receiver_model.target_frequency(), sampling_rate});
if (metadata_file_error.is_valid()) {
handle_error(metadata_file_error.value());
return;
@@ -278,12 +297,7 @@ void RecordView::update_status_display() {
// - 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.
const auto bytes_per_sample = file_type == FileType::RawS16 ? 4 : 2;
// WAV files are not oversampled, but C8 and C16 are. Divide by the
// 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 bytes_per_second = sampling_rate * bytes_per_sample;
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;
+8 -12
View File
@@ -56,16 +56,11 @@ class RecordView : public View {
void focus() override;
/* Sets the sampling rate and the oversampling "decimation" rate.
* These values are passed down to the baseband proc_capture. For
* Audio (WAV) recording, the OversampleRate should not be
* specified and the default will be used. */
/* TODO: Currently callers are expected to have already multiplied the
* 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);
/* Sets the sampling rate for the baseband.
* NB: Do not pre-apply any oversampling. This function will determine
* the correct amount of oversampling and return the actual sample rate
* that can be used to configure the radio or other UI element. */
uint32_t set_sampling_rate(uint32_t new_sampling_rate);
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_error(const File::Error error);
OversampleRate get_oversample_rate(uint32_t sample_rate);
// bool pitch_rssi_enabled = false;
// Time Stamp
@@ -98,8 +95,7 @@ class RecordView : public View {
FileType file_type;
const size_t write_size;
const size_t buffer_count;
size_t sampling_rate{0};
OversampleRate oversample_rate{OversampleRate::Rate8x};
uint32_t sampling_rate{0};
SignalToken signal_token_tick_second{};
Rectangle rect_background{
+3 -3
View File
@@ -77,8 +77,8 @@ void AudioTXProcessor::on_message(const Message* const message) {
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
break;
case Message::ID::SamplerateConfig:
samplerate_config(*reinterpret_cast<const SamplerateConfigMessage*>(message));
case Message::ID::SampleRateConfig:
sample_rate_config(*reinterpret_cast<const SampleRateConfigMessage*>(message));
break;
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
}
+1 -1
View File
@@ -53,7 +53,7 @@ class AudioTXProcessor : public BasebandProcessor {
bool configured{false};
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 replay_config(const ReplayConfigMessage& message);
+24 -22
View File
@@ -27,8 +27,10 @@
#include "utility.hpp"
CaptureProcessor::CaptureProcessor() {
decim_0_4.configure(taps_200k_decim_0.taps, 33554432);
decim_0_8.configure(taps_200k_decim_0.taps, 33554432);
decim_0_4.configure(taps_200k_decim_0.taps, 33554432); // to be used with decim1 (/2), then total two stages decim (/8)
decim_0_8.configure(taps_200k_decim_0.taps, 33554432); // to be used with decim1 (/2), then total two stages decim (/16)
decim_0_8_180k.configure(taps_180k_wfm_decim_0.taps, 33554432); // to be used alone - no additional decim1 (/2), then total single stage decim (/8)
decim_1.configure(taps_200k_decim_1.taps, 131072);
channel_spectrum.set_decimation_factor(1);
@@ -37,8 +39,12 @@ CaptureProcessor::CaptureProcessor() {
void CaptureProcessor::execute(const buffer_c8_t& buffer) {
/* 2.4576MHz, 2048 samples */
const auto decim_0_out = decim_0_execute(buffer, dst_buffer);
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
const auto decim_0_out = decim_0_execute(buffer, dst_buffer); // selectable 3 possible decim_0, (/4. /8 200k soft transition filter , /8 180k sharp )
const auto decim_1_out = baseband_fs < 4800'000
? decim_1.execute(decim_0_out, dst_buffer) // < 500khz double decim. stage
: decim_0_out; // > 500khz single decim. stage
const auto& decimator_out = decim_1_out;
const auto& channel = decimator_out;
@@ -66,19 +72,9 @@ void CaptureProcessor::on_message(const Message* const message) {
channel_spectrum.on_message(message);
break;
case Message::ID::SamplerateConfig: {
auto config = reinterpret_cast<const SamplerateConfigMessage*>(message);
baseband_fs = config->sample_rate;
update_for_rate_change();
case Message::ID::SampleRateConfig:
sample_rate_config(*reinterpret_cast<const SampleRateConfigMessage*>(message));
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:
capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
@@ -89,13 +85,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);
auto decim_0_factor = oversample_rate == OversampleRate::Rate8x
auto decim_0_factor = oversample_rate == OversampleRate::x8
? decim_0_4.decimation_factor
: decim_0_8.decimation_factor;
size_t decim_0_output_fs = baseband_fs / decim_0_factor;
size_t decim_1_input_fs = decim_0_output_fs;
@@ -119,10 +117,14 @@ void CaptureProcessor::capture_config(const CaptureConfigMessage& message) {
buffer_c16_t CaptureProcessor::decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst) {
switch (oversample_rate) {
case OversampleRate::Rate8x:
return decim_0_4.execute(src, dst);
case OversampleRate::x8: // we can get /8 by two means , decim0 (:4) + decim1 (:2) . or just decim0 (;8)
if (baseband_fs < 4800'000) { // 600khz (600k x 8)
return decim_0_4.execute(src, dst); // decim_0 /4 with double decim stage
} else {
return decim_0_8_180k.execute(src, dst); // decim_0 /8 with single decim stage
}
case OversampleRate::Rate16x:
case OversampleRate::x16:
return decim_0_8.execute(src, dst);
default:
+5 -5
View File
@@ -42,7 +42,7 @@ class CaptureProcessor : public BasebandProcessor {
void on_message(const Message* const message) override;
private:
size_t baseband_fs = 3072000;
size_t baseband_fs = 3072000; // aka: sample_rate
static constexpr auto spectrum_rate_hz = 50.0f;
std::array<complex16_t, 512> dst{};
@@ -56,8 +56,9 @@ class CaptureProcessor : public BasebandProcessor {
* use decim_0_8 for an overall decimation factor of 16. */
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0_4{};
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8{};
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8_180k{};
dsp::decimate::FIRC16xR16x16Decim2 decim_1{};
int32_t channel_filter_low_f = 0;
int32_t channel_filter_high_f = 0;
int32_t channel_filter_transition = 0;
@@ -67,15 +68,14 @@ class CaptureProcessor : public BasebandProcessor {
SpectrumCollector channel_spectrum{};
size_t spectrum_interval_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. */
BasebandThread baseband_thread{
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
RSSIThread rssi_thread{};
/* Called to update members when the sample rate or oversample rate is changed. */
void update_for_rate_change();
void sample_rate_config(const SampleRateConfigMessage& message);
void capture_config(const CaptureConfigMessage& message);
/* Dispatch to the correct decim_0 based on oversample rate. */
+3 -3
View File
@@ -83,8 +83,8 @@ void GPSReplayProcessor::on_message(const Message* const message) {
channel_spectrum.on_message(message);
break;
case Message::ID::SamplerateConfig:
samplerate_config(*reinterpret_cast<const SamplerateConfigMessage*>(message));
case Message::ID::SampleRateConfig:
sample_rate_config(*reinterpret_cast<const SampleRateConfigMessage*>(message));
break;
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_thread.set_sampling_rate(baseband_fs);
spectrum_interval_samples = baseband_fs / spectrum_rate_hz;
+1 -1
View File
@@ -64,7 +64,7 @@ class GPSReplayProcessor : public BasebandProcessor {
bool configured{false};
uint32_t bytes_read{0};
void samplerate_config(const SamplerateConfigMessage& message);
void sample_rate_config(const SampleRateConfigMessage& message);
void replay_config(const ReplayConfigMessage& message);
TXProgressMessage txprogress_message{};
+63 -29
View File
@@ -41,43 +41,75 @@ ReplayProcessor::ReplayProcessor() {
baseband_thread.start();
}
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
/* 4MHz, 2048 samples */
// Change to 1 to enable buffer assertions in replay.
#define BUFFER_SIZE_ASSERT 0
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
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
// File samplerate is 500kHz, we're at 4MHz
// 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);
// Wrap the IQ data array in a buffer with the correct sample_rate.
buffer_c16_t iq_buffer{iq.data(), iq.size(), baseband_fs / interpolation_factor};
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"
for (size_t i = 0; i < oversamples_this_iteration; i++) {
if (i & 7) {
buffer.p[i] = buffer.p[i - 1];
} else {
auto re_out = iq_buffer.p[i >> 3].real() >> 8;
auto im_out = iq_buffer.p[i >> 3].imag() >> 8;
buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
#if BUFFER_SIZE_ASSERT
// Verify the output buffer size is divisible by the interpolation factor.
if (samples_to_read * interpolation_factor != buffer.count)
chDbgPanic("Output not div.");
// Is the input smaple buffer big enough?
if (samples_to_read > iq_buffer.size())
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) {
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;
shared_memory.application_queue.push(txprogress_message);
}
@@ -90,8 +122,8 @@ void ReplayProcessor::on_message(const Message* const message) {
channel_spectrum.on_message(message);
break;
case Message::ID::SamplerateConfig:
samplerate_config(*reinterpret_cast<const SamplerateConfigMessage*>(message));
case Message::ID::SampleRateConfig:
sample_rate_config(*reinterpret_cast<const SampleRateConfigMessage*>(message));
break;
case Message::ID::ReplayConfig:
@@ -110,9 +142,11 @@ void ReplayProcessor::on_message(const Message* const message) {
}
}
void ReplayProcessor::samplerate_config(const SamplerateConfigMessage& message) {
baseband_fs = message.sample_rate;
void ReplayProcessor::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);
spectrum_interval_samples = baseband_fs / spectrum_rate_hz;
}
+3 -1
View File
@@ -44,6 +44,7 @@ class ReplayProcessor : public BasebandProcessor {
size_t baseband_fs = 3072000;
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{};
int32_t channel_filter_low_f = 0;
@@ -58,8 +59,9 @@ class ReplayProcessor : public BasebandProcessor {
bool configured{false};
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);
TXProgressMessage txprogress_message{};
+1
View File
@@ -49,6 +49,7 @@ using Timestamp = lpc43xx::rtc::RTC;
template <typename T>
struct buffer_t {
using Type = T;
T* const p;
const size_t count;
const uint32_t sampling_rate;
+3
View File
@@ -456,6 +456,9 @@ bool ILI9341::drawBMP2(const ui::Point p, const std::filesystem::path& file) {
width = bmp_header.width;
height = bmp_header.height;
if (width != 240)
return false;
file_pos = bmp_header.image_data;
py = height + 16;
+24 -20
View File
@@ -78,7 +78,7 @@ class Message {
ReplayThreadDone = 21,
AFSKRxConfigure = 22,
StatusRefresh = 23,
SamplerateConfig = 24,
SampleRateConfig = 24,
BTLERxConfigure = 25,
NRFRxConfigure = 26,
TXProgress = 27,
@@ -111,7 +111,6 @@ class Message {
APRSRxConfigure = 54,
SpectrumPainterBufferRequestConfigure = 55,
SpectrumPainterBufferResponseConfigure = 56,
OversampleRateConfig = 57,
MAX
};
@@ -799,32 +798,37 @@ class RetuneMessage : public Message {
uint32_t range = 0;
};
class SamplerateConfigMessage : public Message {
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. */
/* Oversample/Interpolation sample rate multipliers. */
enum class OversampleRate : uint8_t {
Rate8x = 8,
Rate16x = 16,
/* Use either to indicate there's no oversampling needed. */
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:
constexpr OversampleRateConfigMessage(
constexpr SampleRateConfigMessage(
uint32_t sample_rate,
OversampleRate oversample_rate)
: Message{ID::OversampleRateConfig},
: Message{ID::SampleRateConfig},
sample_rate(sample_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 {
+61
View 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__*/