Compare commits
2 Commits
445d9a6076
...
628a25dc24
Author | SHA1 | Date | |
---|---|---|---|
628a25dc24 | |||
172a731ea5 |
@ -51,7 +51,7 @@ public:
|
||||
interleaver(baud_rate, interleave_setting, is_frequency_hopping),
|
||||
input_data(std::move(_data)),
|
||||
mgd_decoder(baud_rate, is_frequency_hopping),
|
||||
modulator(48000) {}
|
||||
modulator(baud_rate, 48000, 0.5, is_frequency_hopping) {}
|
||||
|
||||
/**
|
||||
* @brief Transmits the input data by processing it through different phases like FEC encoding, interleaving, symbol formation, scrambling, and modulation.
|
||||
@ -75,19 +75,12 @@ public:
|
||||
|
||||
std::vector<uint8_t> mgd_decoded_data = mgd_decoder.mgdDecode(processed_data);
|
||||
|
||||
// Step 4: Symbol Formation. This function injects the sync preamble symbols.
|
||||
// Step 4: Symbol Formation. This function injects the sync preamble symbols. Scrambling is built-in.
|
||||
std::vector<uint8_t> symbol_stream = symbol_formation.formSymbols(mgd_decoded_data);
|
||||
|
||||
// Step 5: Scrambling
|
||||
std::vector<uint8_t> scrambled_data = scrambler.scrambleData(symbol_stream);
|
||||
std::vector<int16_t> modulated_signal = modulator.modulate(symbol_stream);
|
||||
|
||||
// Step 6: PSK Modulation. This is the final baseband output of the class.
|
||||
std::vector<int16_t> modulated_signal = modulator.modulate(scrambled_data);
|
||||
|
||||
// Step 7: Apply Sqrt Half Cosine Filter with a rolloff factor of 0.3 (30%)
|
||||
std::vector<int16_t> filtered_signal = SqrtHalfCosine(modulated_signal, 0.3);
|
||||
|
||||
return filtered_signal;
|
||||
return modulated_signal;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -96,6 +89,7 @@ private:
|
||||
bool is_frequency_hopping; ///< Indicates if frequency hopping is used.
|
||||
BitStream input_data; ///< The input data stream.
|
||||
size_t interleave_setting; ///< The interleave setting to be used.
|
||||
size_t sample_rate;
|
||||
|
||||
SymbolFormation symbol_formation; ///< Symbol formation instance to form symbols from data.
|
||||
Scrambler scrambler; ///< Scrambler instance for scrambling the data.
|
||||
@ -132,28 +126,6 @@ private:
|
||||
return eom_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a square-root raised cosine filter to the input signal.
|
||||
* @param input_signal The input modulated signal.
|
||||
* @param rolloff_factor The rolloff factor of the filter.
|
||||
* @return The filtered signal.
|
||||
*/
|
||||
[[nodiscard]] std::vector<int16_t> SqrtHalfCosine(const std::vector<int16_t>& input_signal, double rolloff_factor) const {
|
||||
std::vector<int16_t> filtered_signal(input_signal.size());
|
||||
const double pi = M_PI;
|
||||
const double normalization_factor = 1.0 / (1.0 + rolloff_factor);
|
||||
|
||||
for (size_t i = 0; i < input_signal.size(); ++i) {
|
||||
double t = static_cast<double>(i) / input_signal.size();
|
||||
double cosine_term = std::cos(pi * t * rolloff_factor);
|
||||
double filtered_value = input_signal[i] * normalization_factor * (0.5 + 0.5 * cosine_term);
|
||||
|
||||
filtered_signal[i] = clamp(static_cast<int16_t>(filtered_value));
|
||||
}
|
||||
|
||||
return filtered_signal;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> splitTribitSymbols(const BitStream& input_data) {
|
||||
std::vector<uint8_t> return_vector;
|
||||
size_t max_index = input_data.getMaxBitIndex();
|
||||
|
@ -5,52 +5,130 @@
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <complex>
|
||||
#include <algorithm>
|
||||
|
||||
class PSKModulator {
|
||||
public:
|
||||
PSKModulator(double sample_rate) : sample_rate(sample_rate), carrier_freq(1800), phase(0.0) {
|
||||
PSKModulator(double baud_rate, double sample_rate, double energy_per_bit, bool is_frequency_hopping)
|
||||
: sample_rate(sample_rate), carrier_freq(1800), phase(0.0) {
|
||||
initializeSymbolMap();
|
||||
symbol_rate = 2400; // Fixed symbol rate as per specification (2400 symbols per second)
|
||||
samples_per_symbol = static_cast<size_t>(sample_rate / symbol_rate);
|
||||
}
|
||||
|
||||
std::vector<int16_t> modulate(const std::vector<uint8_t>& symbols) {
|
||||
std::vector<int16_t> modulated_signal;
|
||||
std::vector<std::complex<double>> modulated_signal;
|
||||
|
||||
const double phase_increment = 2 * M_PI * carrier_freq / sample_rate;
|
||||
for (auto symbol : symbols) {
|
||||
if (symbol >= symbolMap.size()) {
|
||||
throw std::out_of_range("Invalid symbol value for 8-PSK modulation");
|
||||
}
|
||||
double target_phase = symbolMap[symbol];
|
||||
std::complex<double> target_symbol = symbolMap[symbol];
|
||||
|
||||
for (size_t i = 0; i < samples_per_symbol; ++i) {
|
||||
double value = std::sin(phase + target_phase);
|
||||
modulated_signal.push_back(static_cast<int16_t>(value * std::numeric_limits<int16_t>::max()));
|
||||
phase += phase_increment;
|
||||
if (phase >= 2 * M_PI) {
|
||||
phase -= 2 * M_PI;
|
||||
}
|
||||
double in_phase = std::cos(phase + target_symbol.real());
|
||||
double quadrature = std::sin(phase + target_symbol.imag());
|
||||
modulated_signal.emplace_back(in_phase, quadrature);
|
||||
phase = std::fmod(phase + phase_increment, 2 * M_PI);
|
||||
}
|
||||
}
|
||||
return modulated_signal;
|
||||
|
||||
// Apply raised-cosine filter
|
||||
auto filter_taps = sqrtRaisedCosineFilter(201, symbol_rate); // Adjusted number of filter taps to 201 for balance
|
||||
auto filtered_signal = applyFilter(modulated_signal, filter_taps);
|
||||
|
||||
// Normalize the filtered signal
|
||||
double max_value = 0.0;
|
||||
for (const auto& sample : filtered_signal) {
|
||||
max_value = std::max(max_value, std::abs(sample.real()));
|
||||
max_value = std::max(max_value, std::abs(sample.imag()));
|
||||
}
|
||||
double gain = (max_value > 0) ? (32767.0 / max_value) : 1.0;
|
||||
|
||||
// Combine the I and Q components and apply gain for audio output
|
||||
std::vector<int16_t> combined_signal;
|
||||
for (auto& sample : filtered_signal) {
|
||||
int16_t combined_sample = static_cast<int16_t>(std::clamp(gain * (sample.real() + sample.imag()), -32768.0, 32767.0));
|
||||
combined_signal.push_back(combined_sample);
|
||||
}
|
||||
|
||||
return combined_signal;
|
||||
}
|
||||
|
||||
std::vector<double> sqrtRaisedCosineFilter(size_t num_taps, double symbol_rate) {
|
||||
double rolloff = 0.35; // Fixed rolloff factor as per specification
|
||||
std::vector<double> filter_taps(num_taps);
|
||||
double norm_factor = 0.0;
|
||||
double sampling_interval = 1.0 / sample_rate;
|
||||
double symbol_duration = 1.0 / symbol_rate;
|
||||
double half_num_taps = static_cast<double>(num_taps - 1) / 2.0;
|
||||
|
||||
for (size_t i = 0; i < num_taps; ++i) {
|
||||
double t = (i - half_num_taps) * sampling_interval;
|
||||
if (std::abs(t) < 1e-10) {
|
||||
filter_taps[i] = 1.0;
|
||||
} else {
|
||||
double numerator = std::sin(M_PI * t / symbol_duration * (1.0 - rolloff)) +
|
||||
4.0 * rolloff * t / symbol_duration * std::cos(M_PI * t / symbol_duration * (1.0 + rolloff));
|
||||
double denominator = M_PI * t * (1.0 - std::pow(4.0 * rolloff * t / symbol_duration, 2));
|
||||
filter_taps[i] = numerator / denominator;
|
||||
}
|
||||
norm_factor += filter_taps[i] * filter_taps[i];
|
||||
}
|
||||
|
||||
norm_factor = std::sqrt(norm_factor);
|
||||
std::for_each(filter_taps.begin(), filter_taps.end(), [&norm_factor](double &tap) { tap /= norm_factor; });
|
||||
return filter_taps;
|
||||
}
|
||||
|
||||
std::vector<std::complex<double>> applyFilter(const std::vector<std::complex<double>>& signal, const std::vector<double>& filter_taps) {
|
||||
std::vector<std::complex<double>> filtered_signal(signal.size());
|
||||
|
||||
size_t filter_length = filter_taps.size();
|
||||
size_t half_filter_length = filter_length / 2;
|
||||
|
||||
// Convolve the signal with the filter taps
|
||||
for (size_t i = 0; i < signal.size(); ++i) {
|
||||
double filtered_i = 0.0;
|
||||
double filtered_q = 0.0;
|
||||
|
||||
for (size_t j = 0; j < filter_length; ++j) {
|
||||
if (i >= j) {
|
||||
filtered_i += filter_taps[j] * signal[i - j].real();
|
||||
filtered_q += filter_taps[j] * signal[i - j].imag();
|
||||
} else {
|
||||
// Handle edge case by zero-padding
|
||||
filtered_i += filter_taps[j] * 0.0;
|
||||
filtered_q += filter_taps[j] * 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
filtered_signal[i] = std::complex<double>(filtered_i, filtered_q);
|
||||
}
|
||||
|
||||
return filtered_signal;
|
||||
}
|
||||
|
||||
private:
|
||||
double sample_rate; ///< The sample rate of the system.
|
||||
double carrier_freq; ///< The frequency of the carrier, set to 1800 Hz as per standard.
|
||||
double phase; ///< Current phase of the carrier waveform.
|
||||
size_t samples_per_symbol = 40; ///< Number of samples per symbol, calculated to match symbol duration with cycle.
|
||||
std::vector<double> symbolMap; ///< The mapping of tribit symbols to phase shifts.
|
||||
size_t samples_per_symbol; ///< Number of samples per symbol, calculated to match symbol duration with cycle.
|
||||
size_t symbol_rate;
|
||||
std::vector<std::complex<double>> symbolMap; ///< The mapping of tribit symbols to I/Q components.
|
||||
|
||||
void initializeSymbolMap() {
|
||||
symbolMap = {
|
||||
0.0, // 0 (000) corresponds to 0 degrees
|
||||
M_PI / 4, // 1 (001) corresponds to 45 degrees
|
||||
M_PI / 2, // 2 (010) corresponds to 90 degrees
|
||||
3 * M_PI / 4, // 3 (011) corresponds to 135 degrees
|
||||
M_PI, // 4 (100) corresponds to 180 degrees
|
||||
5 * M_PI / 4, // 5 (101) corresponds to 225 degrees
|
||||
3 * M_PI / 2, // 6 (110) corresponds to 270 degrees
|
||||
7 * M_PI / 4 // 7 (111) corresponds to 315 degrees
|
||||
{1.0, 0.0}, // 0 (000) corresponds to I = 1.0, Q = 0.0
|
||||
{std::sqrt(2.0) / 2.0, std::sqrt(2.0) / 2.0}, // 1 (001) corresponds to I = cos(45), Q = sin(45)
|
||||
{0.0, 1.0}, // 2 (010) corresponds to I = 0.0, Q = 1.0
|
||||
{-std::sqrt(2.0) / 2.0, std::sqrt(2.0) / 2.0}, // 3 (011) corresponds to I = cos(135), Q = sin(135)
|
||||
{-1.0, 0.0}, // 4 (100) corresponds to I = -1.0, Q = 0.0
|
||||
{-std::sqrt(2.0) / 2.0, -std::sqrt(2.0) / 2.0}, // 5 (101) corresponds to I = cos(225), Q = sin(225)
|
||||
{0.0, -1.0}, // 6 (110) corresponds to I = 0.0, Q = -1.0
|
||||
{std::sqrt(2.0) / 2.0, -std::sqrt(2.0) / 2.0} // 7 (111) corresponds to I = cos(315), Q = sin(315)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user