Changed project structure

This commit is contained in:
AlexandreRouma
2021-10-03 16:50:36 +02:00
parent c36034dbb8
commit 73393e36c6
252 changed files with 52 additions and 52 deletions

View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.13)
project(falcon9_decoder)
if (MSVC)
add_compile_options(/O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
add_compile_options(-O3 -std=c++17)
endif ()
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include_directories("src/")
include_directories("src/libcorrect/")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native")
add_library(falcon9_decoder SHARED ${SRC})
target_link_libraries(falcon9_decoder PRIVATE sdrpp_core)
set_target_properties(falcon9_decoder PROPERTIES PREFIX "")
# Install directives
install(TARGETS falcon9_decoder DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,127 @@
#pragma once
#include <dsp/block.h>
#include <inttypes.h>
// WTF???
extern "C"
{
#include <correct.h>
}
const uint8_t toDB[] = {
0x00, 0x7b, 0xaf, 0xd4, 0x99, 0xe2, 0x36, 0x4d, 0xfa, 0x81, 0x55, 0x2e, 0x63, 0x18, 0xcc, 0xb7, 0x86, 0xfd, 0x29, 0x52, 0x1f,
0x64, 0xb0, 0xcb, 0x7c, 0x07, 0xd3, 0xa8, 0xe5, 0x9e, 0x4a, 0x31, 0xec, 0x97, 0x43, 0x38, 0x75, 0x0e, 0xda, 0xa1, 0x16, 0x6d, 0xb9, 0xc2, 0x8f, 0xf4,
0x20, 0x5b, 0x6a, 0x11, 0xc5, 0xbe, 0xf3, 0x88, 0x5c, 0x27, 0x90, 0xeb, 0x3f, 0x44, 0x09, 0x72, 0xa6, 0xdd, 0xef, 0x94, 0x40, 0x3b, 0x76, 0x0d, 0xd9,
0xa2, 0x15, 0x6e, 0xba, 0xc1, 0x8c, 0xf7, 0x23, 0x58, 0x69, 0x12, 0xc6, 0xbd, 0xf0, 0x8b, 0x5f, 0x24, 0x93, 0xe8, 0x3c, 0x47, 0x0a, 0x71, 0xa5, 0xde,
0x03, 0x78, 0xac, 0xd7, 0x9a, 0xe1, 0x35, 0x4e, 0xf9, 0x82, 0x56, 0x2d, 0x60, 0x1b, 0xcf, 0xb4, 0x85, 0xfe, 0x2a, 0x51, 0x1c, 0x67, 0xb3, 0xc8, 0x7f,
0x04, 0xd0, 0xab, 0xe6, 0x9d, 0x49, 0x32, 0x8d, 0xf6, 0x22, 0x59, 0x14, 0x6f, 0xbb, 0xc0, 0x77, 0x0c, 0xd8, 0xa3, 0xee, 0x95, 0x41, 0x3a, 0x0b, 0x70,
0xa4, 0xdf, 0x92, 0xe9, 0x3d, 0x46, 0xf1, 0x8a, 0x5e, 0x25, 0x68, 0x13, 0xc7, 0xbc, 0x61, 0x1a, 0xce, 0xb5, 0xf8, 0x83, 0x57, 0x2c, 0x9b, 0xe0, 0x34,
0x4f, 0x02, 0x79, 0xad, 0xd6, 0xe7, 0x9c, 0x48, 0x33, 0x7e, 0x05, 0xd1, 0xaa, 0x1d, 0x66, 0xb2, 0xc9, 0x84, 0xff, 0x2b, 0x50, 0x62, 0x19, 0xcd, 0xb6,
0xfb, 0x80, 0x54, 0x2f, 0x98, 0xe3, 0x37, 0x4c, 0x01, 0x7a, 0xae, 0xd5, 0xe4, 0x9f, 0x4b, 0x30, 0x7d, 0x06, 0xd2, 0xa9, 0x1e, 0x65, 0xb1, 0xca, 0x87,
0xfc, 0x28, 0x53, 0x8e, 0xf5, 0x21, 0x5a, 0x17, 0x6c, 0xb8, 0xc3, 0x74, 0x0f, 0xdb, 0xa0, 0xed, 0x96, 0x42, 0x39, 0x08, 0x73, 0xa7, 0xdc, 0x91, 0xea,
0x3e, 0x45, 0xf2, 0x89, 0x5d, 0x26, 0x6b, 0x10, 0xc4, 0xbf
};
const uint8_t fromDB[] = {
0x00, 0xcc, 0xac, 0x60, 0x79, 0xb5, 0xd5, 0x19, 0xf0, 0x3c, 0x5c, 0x90, 0x89, 0x45, 0x25, 0xe9, 0xfd, 0x31, 0x51, 0x9d,
0x84, 0x48, 0x28, 0xe4, 0x0d, 0xc1, 0xa1, 0x6d, 0x74, 0xb8, 0xd8, 0x14, 0x2e, 0xe2, 0x82, 0x4e, 0x57, 0x9b, 0xfb, 0x37, 0xde, 0x12, 0x72, 0xbe, 0xa7,
0x6b, 0x0b, 0xc7, 0xd3, 0x1f, 0x7f, 0xb3, 0xaa, 0x66, 0x06, 0xca, 0x23, 0xef, 0x8f, 0x43, 0x5a, 0x96, 0xf6, 0x3a, 0x42, 0x8e, 0xee, 0x22, 0x3b, 0xf7,
0x97, 0x5b, 0xb2, 0x7e, 0x1e, 0xd2, 0xcb, 0x07, 0x67, 0xab, 0xbf, 0x73, 0x13, 0xdf, 0xc6, 0x0a, 0x6a, 0xa6, 0x4f, 0x83, 0xe3, 0x2f, 0x36, 0xfa, 0x9a,
0x56, 0x6c, 0xa0, 0xc0, 0x0c, 0x15, 0xd9, 0xb9, 0x75, 0x9c, 0x50, 0x30, 0xfc, 0xe5, 0x29, 0x49, 0x85, 0x91, 0x5d, 0x3d, 0xf1, 0xe8, 0x24, 0x44, 0x88,
0x61, 0xad, 0xcd, 0x01, 0x18, 0xd4, 0xb4, 0x78, 0xc5, 0x09, 0x69, 0xa5, 0xbc, 0x70, 0x10, 0xdc, 0x35, 0xf9, 0x99, 0x55, 0x4c, 0x80, 0xe0, 0x2c, 0x38,
0xf4, 0x94, 0x58, 0x41, 0x8d, 0xed, 0x21, 0xc8, 0x04, 0x64, 0xa8, 0xb1, 0x7d, 0x1d, 0xd1, 0xeb, 0x27, 0x47, 0x8b, 0x92, 0x5e, 0x3e, 0xf2, 0x1b, 0xd7,
0xb7, 0x7b, 0x62, 0xae, 0xce, 0x02, 0x16, 0xda, 0xba, 0x76, 0x6f, 0xa3, 0xc3, 0x0f, 0xe6, 0x2a, 0x4a, 0x86, 0x9f, 0x53, 0x33, 0xff, 0x87, 0x4b, 0x2b,
0xe7, 0xfe, 0x32, 0x52, 0x9e, 0x77, 0xbb, 0xdb, 0x17, 0x0e, 0xc2, 0xa2, 0x6e, 0x7a, 0xb6, 0xd6, 0x1a, 0x03, 0xcf, 0xaf, 0x63, 0x8a, 0x46, 0x26, 0xea,
0xf3, 0x3f, 0x5f, 0x93, 0xa9, 0x65, 0x05, 0xc9, 0xd0, 0x1c, 0x7c, 0xb0, 0x59, 0x95, 0xf5, 0x39, 0x20, 0xec, 0x8c, 0x40, 0x54, 0x98, 0xf8, 0x34, 0x2d,
0xe1, 0x81, 0x4d, 0xa4, 0x68, 0x08, 0xc4, 0xdd, 0x11, 0x71, 0xbd
};
const uint8_t randVals[] = {
0xFF, 0x48, 0x0E, 0xC0, 0x9A, 0x0D, 0x70, 0xBC, 0x8E, 0x2C, 0x93, 0xAD, 0xA7, 0xB7, 0x46, 0xCE,
0x5A, 0x97, 0x7D, 0xCC, 0x32, 0xA2, 0xBF, 0x3E, 0x0A, 0x10, 0xF1, 0x88, 0x94, 0xCD, 0xEA, 0xB1,
0xFE, 0x90, 0x1D, 0x81, 0x34, 0x1A, 0xE1, 0x79, 0x1C, 0x59, 0x27, 0x5B, 0x4F, 0x6E, 0x8D, 0x9C,
0xB5, 0x2E, 0xFB, 0x98, 0x65, 0x45, 0x7E, 0x7C, 0x14, 0x21, 0xE3, 0x11, 0x29, 0x9B, 0xD5, 0x63,
0xFD, 0x20, 0x3B, 0x02, 0x68, 0x35, 0xC2, 0xF2, 0x38, 0xB2, 0x4E, 0xB6, 0x9E, 0xDD, 0x1B, 0x39,
0x6A, 0x5D, 0xF7, 0x30, 0xCA, 0x8A, 0xFC, 0xF8, 0x28, 0x43, 0xC6, 0x22, 0x53, 0x37, 0xAA, 0xC7,
0xFA, 0x40, 0x76, 0x04, 0xD0, 0x6B, 0x85, 0xE4, 0x71, 0x64, 0x9D, 0x6D, 0x3D, 0xBA, 0x36, 0x72,
0xD4, 0xBB, 0xEE, 0x61, 0x95, 0x15, 0xF9, 0xF0, 0x50, 0x87, 0x8C, 0x44, 0xA6, 0x6F, 0x55, 0x8F,
0xF4, 0x80, 0xEC, 0x09, 0xA0, 0xD7, 0x0B, 0xC8, 0xE2, 0xC9, 0x3A, 0xDA, 0x7B, 0x74, 0x6C, 0xE5,
0xA9, 0x77, 0xDC, 0xC3, 0x2A, 0x2B, 0xF3, 0xE0, 0xA1, 0x0F, 0x18, 0x89, 0x4C, 0xDE, 0xAB, 0x1F,
0xE9, 0x01, 0xD8, 0x13, 0x41, 0xAE, 0x17, 0x91, 0xC5, 0x92, 0x75, 0xB4, 0xF6, 0xE8, 0xD9, 0xCB,
0x52, 0xEF, 0xB9, 0x86, 0x54, 0x57, 0xE7, 0xC1, 0x42, 0x1E, 0x31, 0x12, 0x99, 0xBD, 0x56, 0x3F,
0xD2, 0x03, 0xB0, 0x26, 0x83, 0x5C, 0x2F, 0x23, 0x8B, 0x24, 0xEB, 0x69, 0xED, 0xD1, 0xB3, 0x96,
0xA5, 0xDF, 0x73, 0x0C, 0xA8, 0xAF, 0xCF, 0x82, 0x84, 0x3C, 0x62, 0x25, 0x33, 0x7A, 0xAC, 0x7F,
0xA4, 0x07, 0x60, 0x4D, 0x06, 0xB8, 0x5E, 0x47, 0x16, 0x49, 0xD6, 0xD3, 0xDB, 0xA3, 0x67, 0x2D,
0x4B, 0xBE, 0xE6, 0x19, 0x51, 0x5F, 0x9F, 0x05, 0x08, 0x78, 0xC4, 0x4A, 0x66, 0xF5, 0x58
};
namespace dsp {
class FalconRS : public generic_block<FalconRS> {
public:
FalconRS() {}
FalconRS(stream<uint8_t>* in) { init(in); }
~FalconRS() {
generic_block<FalconRS>::stop();
}
void init(stream<uint8_t>* in) {
_in = in;
for (int i = 0; i < 5; i++) { memset(buffers[i], 0, 255); }
for (int i = 0; i < 5; i++) { memset(outBuffers[i], 0, 255); }
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 120, 11, 16);
if (rs == NULL) { printf("Error creating the reed solomon decoder\n"); }
generic_block<FalconRS>::registerInput(_in);
generic_block<FalconRS>::registerOutput(&out);
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
uint8_t* data = _in->readBuf + 4;
// Deinterleave
for (int i = 0; i < 255*5; i++) {
buffers[i%5][i/5] = fromDB[data[i]];
}
// Reed the solomon :weary:
int result = 0;
result = correct_reed_solomon_decode(rs, buffers[0], 255, outBuffers[0]);
if (result == -1) { _in->flush(); return count; }
result = correct_reed_solomon_decode(rs, buffers[1], 255, outBuffers[1]);
if (result == -1) { _in->flush(); return count; }
result = correct_reed_solomon_decode(rs, buffers[2], 255, outBuffers[2]);
if (result == -1) { _in->flush(); return count; }
result = correct_reed_solomon_decode(rs, buffers[3], 255, outBuffers[3]);
if (result == -1) { _in->flush(); return count; }
result = correct_reed_solomon_decode(rs, buffers[4], 255, outBuffers[4]);
if (result == -1) { _in->flush(); return count; }
// Reinterleave
for (int i = 0; i < 255*5; i++) {
out.writeBuf[i] = toDB[outBuffers[i%5][i/5]] ^ randVals[i % 255];
}
out.swap(255*5);
_in->flush();
return count;
}
stream<uint8_t> out;
private:
int count;
uint8_t buffers[5][255];
uint8_t outBuffers[5][255];
correct_reed_solomon* rs;
stream<uint8_t>* _in;
};
}

View File

@ -0,0 +1,121 @@
#pragma once
#include <dsp/block.h>
#include <inttypes.h>
namespace dsp {
struct FalconFrameHeader {
uint32_t counter;
uint16_t packet;
};
class FalconPacketSync : public generic_block<FalconPacketSync> {
public:
FalconPacketSync() {}
FalconPacketSync(stream<uint8_t>* in) { init(in); }
~FalconPacketSync() {
generic_block<FalconPacketSync>::stop();
}
void init(stream<uint8_t>* in) {
_in = in;
generic_block<FalconPacketSync>::registerInput(_in);
generic_block<FalconPacketSync>::registerOutput(&out);
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
// Parse frame header
FalconFrameHeader header;
header.packet = (_in->readBuf[3] | ((_in->readBuf[2] & 0b111) << 8));
header.counter = ((_in->readBuf[2] >> 3) | (_in->readBuf[1] << 5) | ((_in->readBuf[0] & 0b111111) << 13));
// Pointer to the data aera of the frame
uint8_t* data = _in->readBuf + 4;
int dataLen = 1191;
// If a frame was missed, cancel reading the current packet
if (lastCounter + 1 != header.counter) {
packetRead = -1;
}
lastCounter = header.counter;
// If frame is just a continuation of a single packet, save it
// If we're not currently reading a packet
if (header.packet == 2047 && packetRead >= 0) {
memcpy(packet + packetRead, data, dataLen);
packetRead += dataLen;
_in->flush();
printf("Wow, all data\n");
return count;
}
else if (header.packet == 2047) {
printf("Wow, all data\n");
_in->flush();
return count;
}
// Finish reading the last package and send it
if (packetRead >= 0) {
memcpy(packet + packetRead, data, header.packet);
memcpy(out.writeBuf, packet, packetRead + header.packet);
out.swap(packetRead + header.packet);
packetRead = -1;
}
// Iterate through every packet of the frame
for (int i = header.packet; i < dataLen;) {
// First, check if we can read the header. If not, save and wait for next frame
if (dataLen - i < 4) {
packetRead = dataLen - i;
memcpy(packet, &data[i], packetRead);
break;
}
// Extract packet length
uint16_t length = (((data[i] & 0b1111) << 8) | data[i + 1]) + 2;
// Check if it's not an invalid zero length packet
if (length <= 2) {
packetRead = -1;
break;
}
uint64_t pktId = ((uint64_t)data[i + 2] << 56) | ((uint64_t)data[i + 3] << 48) | ((uint64_t)data[i + 4] << 40) | ((uint64_t)data[i + 5] << 32)
| ((uint64_t)data[i + 6] << 24) | ((uint64_t)data[i + 7] << 16) | ((uint64_t)data[i + 8] << 8) | data[i + 9];
// If the packet doesn't fit the frame, save and go to next frame
if (dataLen - i < length) {
packetRead = dataLen - i;
memcpy(packet, &data[i], packetRead);
break;
}
// Here, the package fits fully, read it and jump to the next
memcpy(out.writeBuf, &data[i], length);
out.swap(length);
i += length;
}
_in->flush();
return count;
}
stream<uint8_t> out;
private:
int count;
uint32_t lastCounter = 0;
int packetRead = -1;
uint8_t packet[0x4008];
stream<uint8_t>* _in;
};
}

View File

@ -0,0 +1,5 @@
set(SRCFILES bit.c metric.c history_buffer.c error_buffer.c lookup.c convolutional.c encode.c decode.c)
add_library(correct-convolutional OBJECT ${SRCFILES})
if(HAVE_SSE)
add_subdirectory(sse)
endif()

View File

@ -0,0 +1,232 @@
#include "correct/convolutional/bit.h"
bit_writer_t *bit_writer_create(uint8_t *bytes, size_t len) {
bit_writer_t *w = calloc(1, sizeof(bit_writer_t));
if (bytes) {
bit_writer_reconfigure(w, bytes, len);
}
return w;
}
void bit_writer_reconfigure(bit_writer_t *w, uint8_t *bytes, size_t len) {
w->bytes = bytes;
w->len = len;
w->current_byte = 0;
w->current_byte_len = 0;
w->byte_index = 0;
}
void bit_writer_destroy(bit_writer_t *w) {
free(w);
}
void bit_writer_write(bit_writer_t *w, uint8_t val, unsigned int n) {
for (size_t j = 0; j < n; j++) {
bit_writer_write_1(w, val);
val >>= 1;
}
}
void bit_writer_write_1(bit_writer_t *w, uint8_t val) {
w->current_byte |= val & 1;
w->current_byte_len++;
if (w->current_byte_len == 8) {
// 8 bits in a byte -- move to the next byte
w->bytes[w->byte_index] = w->current_byte;
w->byte_index++;
w->current_byte_len = 0;
w->current_byte = 0;
} else {
w->current_byte <<= 1;
}
}
void bit_writer_write_bitlist(bit_writer_t *w, uint8_t *l, size_t len) {
// first close the current byte
// we might have been given too few elements to do that. be careful.
size_t close_len = 8 - w->current_byte_len;
close_len = (close_len < len) ? close_len : len;
uint16_t b = w->current_byte;
for (ptrdiff_t i = 0; i < close_len; i++) {
b |= l[i];
b <<= 1;
}
l += close_len;
len -= close_len;
uint8_t *bytes = w->bytes;
size_t byte_index = w->byte_index;
if (w->current_byte_len + close_len == 8) {
b >>= 1;
bytes[byte_index] = b;
byte_index++;
} else {
w->current_byte = b;
w->current_byte_len += close_len;
return;
}
size_t full_bytes = len/8;
for (size_t i = 0; i < full_bytes; i++) {
bytes[byte_index] = l[0] << 7 | l[1] << 6 | l[2] << 5 |
l[3] << 4 | l[4] << 3 | l[5] << 2 |
l[6] << 1 | l[7];
byte_index += 1;
l += 8;
}
len -= 8*full_bytes;
b = 0;
for (ptrdiff_t i = 0; i < len; i++) {
b |= l[i];
b <<= 1;
}
w->current_byte = b;
w->byte_index = byte_index;
w->current_byte_len = len;
}
void bit_writer_write_bitlist_reversed(bit_writer_t *w, uint8_t *l, size_t len) {
l = l + len - 1;
uint8_t *bytes = w->bytes;
size_t byte_index = w->byte_index;
uint16_t b;
if (w->current_byte_len != 0) {
size_t close_len = 8 - w->current_byte_len;
close_len = (close_len < len) ? close_len : len;
b = w->current_byte;
for (ptrdiff_t i = 0; i < close_len; i++) {
b |= *l;
b <<= 1;
l--;
}
len -= close_len;
if (w->current_byte_len + close_len == 8) {
b >>= 1;
bytes[byte_index] = b;
byte_index++;
} else {
w->current_byte = b;
w->current_byte_len += close_len;
return;
}
}
size_t full_bytes = len/8;
for (size_t i = 0; i < full_bytes; i++) {
bytes[byte_index] = l[0] << 7 | l[-1] << 6 | l[-2] << 5 |
l[-3] << 4 | l[-4] << 3 | l[-5] << 2 |
l[-6] << 1 | l[-7];
byte_index += 1;
l -= 8;
}
len -= 8*full_bytes;
b = 0;
for (ptrdiff_t i = 0; i < len; i++) {
b |= *l;
b <<= 1;
l--;
}
w->current_byte = (uint8_t)b;
w->byte_index = byte_index;
w->current_byte_len = len;
}
void bit_writer_flush_byte(bit_writer_t *w) {
if (w->current_byte_len != 0) {
w->current_byte <<= (8 - w->current_byte_len);
w->bytes[w->byte_index] = w->current_byte;
w->byte_index++;
w->current_byte_len = 0;
}
}
size_t bit_writer_length(bit_writer_t *w) {
return w->byte_index;
}
uint8_t reverse_byte(uint8_t b) {
return (b & 0x80) >> 7 | (b & 0x40) >> 5 | (b & 0x20) >> 3 |
(b & 0x10) >> 1 | (b & 0x08) << 1 | (b & 0x04) << 3 |
(b & 0x02) << 5 | (b & 0x01) << 7;
}
static uint8_t reverse_table[256];
void create_reverse_table() {
for (uint16_t i = 0; i < 256; i++) {
reverse_table[i] = reverse_byte(i);
}
}
bit_reader_t *bit_reader_create(const uint8_t *bytes, size_t len) {
bit_reader_t *r = calloc(1, sizeof(bit_reader_t));
static bool reverse_table_created = false;
if (!reverse_table_created) {
create_reverse_table();
reverse_table_created = true;
}
if (bytes) {
bit_reader_reconfigure(r, bytes, len);
}
return r;
}
void bit_reader_reconfigure(bit_reader_t *r, const uint8_t *bytes, size_t len) {
r->bytes = bytes;
r->len = len;
r->current_byte_len = 8;
r->current_byte = bytes[0];
r->byte_index = 0;
}
void bit_reader_destroy(bit_reader_t *r) {
free(r);
}
uint8_t bit_reader_read(bit_reader_t *r, unsigned int n) {
unsigned int read = 0;
unsigned int n_copy = n;
if (r->current_byte_len < n) {
read = r->current_byte & ((1 << r->current_byte_len) - 1);
r->byte_index++;
r->current_byte = r->bytes[r->byte_index];
n -= r->current_byte_len;
r->current_byte_len = 8;
read <<= n;
}
uint8_t copy_mask = (1 << n) - 1;
copy_mask <<= (r->current_byte_len - n);
read |= (r->current_byte & copy_mask) >> (r->current_byte_len - n);
r->current_byte_len -= n;
return reverse_table[read] >> (8 - n_copy);
}

View File

@ -0,0 +1,59 @@
#include "correct/convolutional/convolutional.h"
// https://www.youtube.com/watch?v=b3_lVSrPB6w
correct_convolutional *_correct_convolutional_init(correct_convolutional *conv,
size_t rate, size_t order,
const polynomial_t *poly) {
if (order > 8 * sizeof(shift_register_t)) {
// XXX turn this into an error code
// printf("order must be smaller than 8 * sizeof(shift_register_t)\n");
return NULL;
}
if (rate < 2) {
// XXX turn this into an error code
// printf("rate must be 2 or greater\n");
return NULL;
}
conv->order = order;
conv->rate = rate;
conv->numstates = 1 << order;
unsigned int *table = malloc(sizeof(unsigned int) * (1 << order));
fill_table(conv->rate, conv->order, poly, table);
*(unsigned int **)&conv->table = table;
conv->bit_writer = bit_writer_create(NULL, 0);
conv->bit_reader = bit_reader_create(NULL, 0);
conv->has_init_decode = false;
return conv;
}
correct_convolutional *correct_convolutional_create(size_t rate, size_t order,
const polynomial_t *poly) {
correct_convolutional *conv = malloc(sizeof(correct_convolutional));
correct_convolutional *init_conv = _correct_convolutional_init(conv, rate, order, poly);
if (!init_conv) {
free(conv);
}
return init_conv;
}
void _correct_convolutional_teardown(correct_convolutional *conv) {
free(*(unsigned int **)&conv->table);
bit_writer_destroy(conv->bit_writer);
bit_reader_destroy(conv->bit_reader);
if (conv->has_init_decode) {
pair_lookup_destroy(conv->pair_lookup);
history_buffer_destroy(conv->history_buffer);
error_buffer_destroy(conv->errors);
free(conv->distances);
}
}
void correct_convolutional_destroy(correct_convolutional *conv) {
_correct_convolutional_teardown(conv);
free(conv);
}

View File

@ -0,0 +1,321 @@
#include "correct/convolutional/convolutional.h"
void conv_decode_print_iter(correct_convolutional *conv, unsigned int iter,
unsigned int winner_index) {
if (iter < 2220) {
return;
}
printf("iteration: %d\n", iter);
distance_t *errors = conv->errors->write_errors;
printf("errors:\n");
for (shift_register_t i = 0; i < conv->numstates / 2; i++) {
printf("%2d: %d\n", i, errors[i]);
}
printf("\n");
printf("history:\n");
for (shift_register_t i = 0; i < conv->numstates / 2; i++) {
printf("%2d: ", i);
for (unsigned int j = 0; j <= winner_index; j++) {
printf("%d", conv->history_buffer->history[j][i] ? 1 : 0);
}
printf("\n");
}
printf("\n");
}
void convolutional_decode_warmup(correct_convolutional *conv, unsigned int sets,
const uint8_t *soft) {
// first phase: load shiftregister up from 0 (order goes from 1 to conv->order)
// we are building up error metrics for the first order bits
for (unsigned int i = 0; i < conv->order - 1 && i < sets; i++) {
// peel off rate bits from encoded to recover the same `out` as in the encoding process
// the difference being that this `out` will have the channel noise/errors applied
unsigned int out;
if (!soft) {
out = bit_reader_read(conv->bit_reader, conv->rate);
}
const distance_t *read_errors = conv->errors->read_errors;
distance_t *write_errors = conv->errors->write_errors;
// walk all of the state we have so far
for (size_t j = 0; j < (1 << (i + 1)); j += 1) {
unsigned int last = j >> 1;
distance_t dist;
if (soft) {
if (conv->soft_measurement == CORRECT_SOFT_LINEAR) {
dist = metric_soft_distance_linear(conv->table[j], soft + i * conv->rate,
conv->rate);
} else {
dist = metric_soft_distance_quadratic(conv->table[j], soft + i * conv->rate,
conv->rate);
}
} else {
dist = metric_distance(conv->table[j], out);
}
write_errors[j] = dist + read_errors[last];
}
error_buffer_swap(conv->errors);
}
}
void convolutional_decode_inner(correct_convolutional *conv, unsigned int sets,
const uint8_t *soft) {
shift_register_t highbit = 1 << (conv->order - 1);
for (unsigned int i = conv->order - 1; i < (sets - conv->order + 1); i++) {
distance_t *distances = conv->distances;
// lasterrors are the aggregate bit errors for the states of shiftregister for the previous
// time slice
if (soft) {
if (conv->soft_measurement == CORRECT_SOFT_LINEAR) {
for (unsigned int j = 0; j < 1 << (conv->rate); j++) {
distances[j] =
metric_soft_distance_linear(j, soft + i * conv->rate, conv->rate);
}
} else {
for (unsigned int j = 0; j < 1 << (conv->rate); j++) {
distances[j] =
metric_soft_distance_quadratic(j, soft + i * conv->rate, conv->rate);
}
}
} else {
unsigned int out = bit_reader_read(conv->bit_reader, conv->rate);
for (unsigned int i = 0; i < 1 << (conv->rate); i++) {
distances[i] = metric_distance(i, out);
}
}
pair_lookup_t pair_lookup = conv->pair_lookup;
pair_lookup_fill_distance(pair_lookup, distances);
// a mask to get the high order bit from the shift register
unsigned int num_iter = highbit << 1;
const distance_t *read_errors = conv->errors->read_errors;
// aggregate bit errors for this time slice
distance_t *write_errors = conv->errors->write_errors;
uint8_t *history = history_buffer_get_slice(conv->history_buffer);
// walk through all states, ignoring oldest bit
// we will track a best register state (path) and the number of bit errors at that path at
// this time slice
// this loop considers two paths per iteration (high order bit set, clear)
// so, it only runs numstates/2 iterations
// we'll update the history for every state and find the path with the least aggregated bit
// errors
// now run the main loop
// we calculate 2 sets of 2 register states here (4 states per iter)
// this creates 2 sets which share a predecessor, and 2 sets which share a successor
//
// the first set definition is the two states that are the same except for the least order
// bit
// these two share a predecessor because their high n - 1 bits are the same (differ only by
// newest bit)
//
// the second set definition is the two states that are the same except for the high order
// bit
// these two share a successor because the oldest high order bit will be shifted out, and
// the other bits will be present in the successor
//
shift_register_t highbase = highbit >> 1;
for (shift_register_t low = 0, high = highbit, base = 0; high < num_iter;
low += 8, high += 8, base += 4) {
// shifted-right ancestors
// low and low_plus_one share low_past_error
// note that they are the same when shifted right by 1
// same goes for high and high_plus_one
for (shift_register_t offset = 0, base_offset = 0; base_offset < 4;
offset += 2, base_offset += 1) {
distance_pair_key_t low_key = pair_lookup.keys[base + base_offset];
distance_pair_key_t high_key = pair_lookup.keys[highbase + base + base_offset];
distance_pair_t low_concat_dist = pair_lookup.distances[low_key];
distance_pair_t high_concat_dist = pair_lookup.distances[high_key];
distance_t low_past_error = read_errors[base + base_offset];
distance_t high_past_error = read_errors[highbase + base + base_offset];
distance_t low_error = (low_concat_dist & 0xffff) + low_past_error;
distance_t high_error = (high_concat_dist & 0xffff) + high_past_error;
shift_register_t successor = low + offset;
distance_t error;
uint8_t history_mask;
if (low_error <= high_error) {
error = low_error;
history_mask = 0;
} else {
error = high_error;
history_mask = 1;
}
write_errors[successor] = error;
history[successor] = history_mask;
shift_register_t low_plus_one = low + offset + 1;
distance_t low_plus_one_error = (low_concat_dist >> 16) + low_past_error;
distance_t high_plus_one_error = (high_concat_dist >> 16) + high_past_error;
shift_register_t plus_one_successor = low_plus_one;
distance_t plus_one_error;
uint8_t plus_one_history_mask;
if (low_plus_one_error <= high_plus_one_error) {
plus_one_error = low_plus_one_error;
plus_one_history_mask = 0;
} else {
plus_one_error = high_plus_one_error;
plus_one_history_mask = 1;
}
write_errors[plus_one_successor] = plus_one_error;
history[plus_one_successor] = plus_one_history_mask;
}
}
history_buffer_process(conv->history_buffer, write_errors, conv->bit_writer);
error_buffer_swap(conv->errors);
}
}
void convolutional_decode_tail(correct_convolutional *conv, unsigned int sets,
const uint8_t *soft) {
// flush state registers
// now we only shift in 0s, skipping 1-successors
shift_register_t highbit = 1 << (conv->order - 1);
for (unsigned int i = sets - conv->order + 1; i < sets; i++) {
// lasterrors are the aggregate bit errors for the states of shiftregister for the previous
// time slice
const distance_t *read_errors = conv->errors->read_errors;
// aggregate bit errors for this time slice
distance_t *write_errors = conv->errors->write_errors;
uint8_t *history = history_buffer_get_slice(conv->history_buffer);
// calculate the distance from all output states to our sliced bits
distance_t *distances = conv->distances;
if (soft) {
if (conv->soft_measurement == CORRECT_SOFT_LINEAR) {
for (unsigned int j = 0; j < 1 << (conv->rate); j++) {
distances[j] =
metric_soft_distance_linear(j, soft + i * conv->rate, conv->rate);
}
} else {
for (unsigned int j = 0; j < 1 << (conv->rate); j++) {
distances[j] =
metric_soft_distance_quadratic(j, soft + i * conv->rate, conv->rate);
}
}
} else {
unsigned int out = bit_reader_read(conv->bit_reader, conv->rate);
for (unsigned int i = 0; i < 1 << (conv->rate); i++) {
distances[i] = metric_distance(i, out);
}
}
const unsigned int *table = conv->table;
// a mask to get the high order bit from the shift register
unsigned int num_iter = highbit << 1;
unsigned int skip = 1 << (conv->order - (sets - i));
unsigned int base_skip = skip >> 1;
shift_register_t highbase = highbit >> 1;
for (shift_register_t low = 0, high = highbit, base = 0; high < num_iter;
low += skip, high += skip, base += base_skip) {
unsigned int low_output = table[low];
unsigned int high_output = table[high];
distance_t low_dist = distances[low_output];
distance_t high_dist = distances[high_output];
distance_t low_past_error = read_errors[base];
distance_t high_past_error = read_errors[highbase + base];
distance_t low_error = low_dist + low_past_error;
distance_t high_error = high_dist + high_past_error;
shift_register_t successor = low;
distance_t error;
uint8_t history_mask;
if (low_error < high_error) {
error = low_error;
history_mask = 0;
} else {
error = high_error;
history_mask = 1;
}
write_errors[successor] = error;
history[successor] = history_mask;
}
history_buffer_process_skip(conv->history_buffer, write_errors, conv->bit_writer, skip);
error_buffer_swap(conv->errors);
}
}
void _convolutional_decode_init(correct_convolutional *conv, unsigned int min_traceback,
unsigned int traceback_length, unsigned int renormalize_interval) {
conv->has_init_decode = true;
conv->distances = calloc(1 << (conv->rate), sizeof(distance_t));
conv->pair_lookup = pair_lookup_create(conv->rate, conv->order, conv->table);
conv->soft_measurement = CORRECT_SOFT_LINEAR;
// we limit history to go back as far as 5 * the order of our polynomial
conv->history_buffer = history_buffer_create(min_traceback, traceback_length, renormalize_interval,
conv->numstates / 2, 1 << (conv->order - 1));
conv->errors = error_buffer_create(conv->numstates);
}
static ssize_t _convolutional_decode(correct_convolutional *conv, size_t num_encoded_bits,
size_t num_encoded_bytes, uint8_t *msg,
const soft_t *soft_encoded) {
if (!conv->has_init_decode) {
uint64_t max_error_per_input = conv->rate * soft_max;
unsigned int renormalize_interval = distance_max / max_error_per_input;
_convolutional_decode_init(conv, 5 * conv->order, 15 * conv->order, renormalize_interval);
}
size_t sets = num_encoded_bits / conv->rate;
// XXX fix this vvvvvv
size_t decoded_len_bytes = num_encoded_bytes;
bit_writer_reconfigure(conv->bit_writer, msg, decoded_len_bytes);
error_buffer_reset(conv->errors);
history_buffer_reset(conv->history_buffer);
// no outputs are generated during warmup
convolutional_decode_warmup(conv, sets, soft_encoded);
convolutional_decode_inner(conv, sets, soft_encoded);
convolutional_decode_tail(conv, sets, soft_encoded);
history_buffer_flush(conv->history_buffer, conv->bit_writer);
return bit_writer_length(conv->bit_writer);
}
// perform viterbi decoding
// hard decoder
ssize_t correct_convolutional_decode(correct_convolutional *conv, const uint8_t *encoded,
size_t num_encoded_bits, uint8_t *msg) {
if (num_encoded_bits % conv->rate) {
// XXX turn this into an error code
// printf("encoded length of message must be a multiple of rate\n");
return -1;
}
size_t num_encoded_bytes =
(num_encoded_bits % 8) ? (num_encoded_bits / 8 + 1) : (num_encoded_bits / 8);
bit_reader_reconfigure(conv->bit_reader, encoded, num_encoded_bytes);
return _convolutional_decode(conv, num_encoded_bits, num_encoded_bytes, msg, NULL);
}
ssize_t correct_convolutional_decode_soft(correct_convolutional *conv, const soft_t *encoded,
size_t num_encoded_bits, uint8_t *msg) {
if (num_encoded_bits % conv->rate) {
// XXX turn this into an error code
// printf("encoded length of message must be a multiple of rate\n");
return -1;
}
size_t num_encoded_bytes =
(num_encoded_bits % 8) ? (num_encoded_bits / 8 + 1) : (num_encoded_bits / 8);
return _convolutional_decode(conv, num_encoded_bits, num_encoded_bytes, msg, encoded);
}

View File

@ -0,0 +1,61 @@
#include "correct/convolutional/convolutional.h"
size_t correct_convolutional_encode_len(correct_convolutional *conv, size_t msg_len) {
size_t msgbits = 8 * msg_len;
size_t encodedbits = conv->rate * (msgbits + conv->order + 1);
return encodedbits;
}
// shift in most significant bit every time, one byte at a time
// shift register takes most recent bit on right, shifts left
// poly is written in same order, just & mask message w/ poly
// assume that encoded length is long enough?
size_t correct_convolutional_encode(correct_convolutional *conv,
const uint8_t *msg,
size_t msg_len,
uint8_t *encoded) {
// convolutional code convolves filter coefficients, given by
// the polynomial, with some history from our message.
// the history is stored as single subsequent bits in shiftregister
shift_register_t shiftregister = 0;
// shiftmask is the shiftregister bit mask that removes bits
// that extend beyond order
// e.g. if order is 7, then remove the 8th bit and beyond
unsigned int shiftmask = (1 << conv->order) - 1;
size_t encoded_len_bits = correct_convolutional_encode_len(conv, msg_len);
size_t encoded_len = (encoded_len_bits % 8) ? (encoded_len_bits / 8 + 1) : (encoded_len_bits / 8);
bit_writer_reconfigure(conv->bit_writer, encoded, encoded_len);
bit_reader_reconfigure(conv->bit_reader, msg, msg_len);
for (size_t i = 0; i < 8 * msg_len; i++) {
// shiftregister has oldest bits on left, newest on right
shiftregister <<= 1;
shiftregister |= bit_reader_read(conv->bit_reader, 1);
shiftregister &= shiftmask;
// shift most significant bit from byte and move down one bit at a time
// we do direct lookup of our convolutional output here
// all of the bits from this convolution are stored in this row
unsigned int out = conv->table[shiftregister];
bit_writer_write(conv->bit_writer, out, conv->rate);
}
// now flush the shiftregister
// this is simply running the loop as above but without any new inputs
// or rather, the new input string is all 0s
for (size_t i = 0; i < conv->order + 1; i++) {
shiftregister <<= 1;
shiftregister &= shiftmask;
unsigned int out = conv->table[shiftregister];
bit_writer_write(conv->bit_writer, out, conv->rate);
}
// 0-fill any remaining bits on our final byte
bit_writer_flush_byte(conv->bit_writer);
return encoded_len_bits;
}

View File

@ -0,0 +1,43 @@
#include "correct/convolutional/error_buffer.h"
error_buffer_t *error_buffer_create(unsigned int num_states) {
error_buffer_t *buf = calloc(1, sizeof(error_buffer_t));
// how large are the error buffers?
buf->num_states = num_states;
// save two error metrics, one for last round and one for this
// (double buffer)
// the error metric is the aggregated number of bit errors found
// at a given path which terminates at a particular shift register state
buf->errors[0] = calloc(sizeof(distance_t), num_states);
buf->errors[1] = calloc(sizeof(distance_t), num_states);
// which buffer are we using, 0 or 1?
buf->index = 0;
buf->read_errors = buf->errors[0];
buf->write_errors = buf->errors[1];
return buf;
}
void error_buffer_destroy(error_buffer_t *buf) {
free(buf->errors[0]);
free(buf->errors[1]);
free(buf);
}
void error_buffer_reset(error_buffer_t *buf) {
memset(buf->errors[0], 0, buf->num_states * sizeof(distance_t));
memset(buf->errors[1], 0, buf->num_states * sizeof(distance_t));
buf->index = 0;
buf->read_errors = buf->errors[0];
buf->write_errors = buf->errors[1];
}
void error_buffer_swap(error_buffer_t *buf) {
buf->read_errors = buf->errors[buf->index];
buf->index = (buf->index + 1) % 2;
buf->write_errors = buf->errors[buf->index];
}

View File

@ -0,0 +1,158 @@
#include "correct/convolutional/history_buffer.h"
history_buffer *history_buffer_create(unsigned int min_traceback_length,
unsigned int traceback_group_length,
unsigned int renormalize_interval, unsigned int num_states,
shift_register_t highbit) {
history_buffer *buf = calloc(1, sizeof(history_buffer));
*(unsigned int *)&buf->min_traceback_length = min_traceback_length;
*(unsigned int *)&buf->traceback_group_length = traceback_group_length;
*(unsigned int *)&buf->cap = min_traceback_length + traceback_group_length;
*(unsigned int *)&buf->num_states = num_states;
*(shift_register_t *)&buf->highbit = highbit;
buf->history = malloc(buf->cap * sizeof(uint8_t *));
for (unsigned int i = 0; i < buf->cap; i++) {
buf->history[i] = calloc(num_states, sizeof(uint8_t));
}
buf->fetched = malloc(buf->cap * sizeof(uint8_t));
buf->index = 0;
buf->len = 0;
buf->renormalize_counter = 0;
buf->renormalize_interval = renormalize_interval;
return buf;
}
void history_buffer_destroy(history_buffer *buf) {
for (unsigned int i = 0; i < buf->cap; i++) {
free(buf->history[i]);
}
free(buf->history);
free(buf->fetched);
free(buf);
}
void history_buffer_reset(history_buffer *buf) {
buf->len = 0;
buf->index = 0;
}
uint8_t *history_buffer_get_slice(history_buffer *buf) { return buf->history[buf->index]; }
shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
unsigned int search_every) {
shift_register_t bestpath;
distance_t leasterror = USHRT_MAX;
// search for a state with the least error
for (shift_register_t state = 0; state < buf->num_states; state += search_every) {
if (distances[state] < leasterror) {
leasterror = distances[state];
bestpath = state;
}
}
return bestpath;
}
void history_buffer_renormalize(history_buffer *buf, distance_t *distances,
shift_register_t min_register) {
distance_t min_distance = distances[min_register];
for (shift_register_t i = 0; i < buf->num_states; i++) {
distances[i] -= min_distance;
}
}
void history_buffer_traceback(history_buffer *buf, shift_register_t bestpath,
unsigned int min_traceback_length, bit_writer_t *output) {
unsigned int fetched_index = 0;
shift_register_t highbit = buf->highbit;
unsigned int index = buf->index;
unsigned int cap = buf->cap;
for (unsigned int j = 0; j < min_traceback_length; j++) {
if (index == 0) {
index = cap - 1;
} else {
index--;
}
// we're walking backwards from what the work we did before
// so, we'll shift high order bits in
// the path will cross multiple different shift register states, and we determine
// which state by going backwards one time slice at a time
uint8_t history = buf->history[index][bestpath];
shift_register_t pathbit = history ? highbit : 0;
bestpath |= pathbit;
bestpath >>= 1;
}
unsigned int prefetch_index = index;
if (prefetch_index == 0) {
prefetch_index = cap - 1;
} else {
prefetch_index--;
}
unsigned int len = buf->len;
for (unsigned int j = min_traceback_length; j < len; j++) {
index = prefetch_index;
if (prefetch_index == 0) {
prefetch_index = cap - 1;
} else {
prefetch_index--;
}
prefetch(buf->history[prefetch_index]);
// we're walking backwards from what the work we did before
// so, we'll shift high order bits in
// the path will cross multiple different shift register states, and we determine
// which state by going backwards one time slice at a time
uint8_t history = buf->history[index][bestpath];
shift_register_t pathbit = history ? highbit : 0;
bestpath |= pathbit;
bestpath >>= 1;
buf->fetched[fetched_index] = (pathbit ? 1 : 0);
fetched_index++;
}
bit_writer_write_bitlist_reversed(output, buf->fetched, fetched_index);
buf->len -= fetched_index;
}
void history_buffer_process_skip(history_buffer *buf, distance_t *distances, bit_writer_t *output,
unsigned int skip) {
buf->index++;
if (buf->index == buf->cap) {
buf->index = 0;
}
buf->renormalize_counter++;
buf->len++;
// there are four ways these branches can resolve
// a) we are neither renormalizing nor doing a traceback
// b) we are renormalizing but not doing a traceback
// c) we are renormalizing and doing a traceback
// d) we are not renormalizing but we are doing a traceback
// in case c, we want to save the effort of finding the bestpath
// since that's expensive
// so we have to check for that case after we renormalize
if (buf->renormalize_counter == buf->renormalize_interval) {
buf->renormalize_counter = 0;
shift_register_t bestpath = history_buffer_search(buf, distances, skip);
history_buffer_renormalize(buf, distances, bestpath);
if (buf->len == buf->cap) {
// reuse the bestpath found for renormalizing
history_buffer_traceback(buf, bestpath, buf->min_traceback_length, output);
}
} else if (buf->len == buf->cap) {
// not renormalizing, find the bestpath here
shift_register_t bestpath = history_buffer_search(buf, distances, skip);
history_buffer_traceback(buf, bestpath, buf->min_traceback_length, output);
}
}
void history_buffer_process(history_buffer *buf, distance_t *distances, bit_writer_t *output) {
history_buffer_process_skip(buf, distances, output, 1);
}
void history_buffer_flush(history_buffer *buf, bit_writer_t *output) {
history_buffer_traceback(buf, 0, 0, output);
}

View File

@ -0,0 +1,74 @@
#include "correct/convolutional/lookup.h"
// table has numstates rows
// each row contains all of the polynomial output bits concatenated together
// e.g. for rate 2, we have 2 bits in each row
// the first poly gets the LEAST significant bit, last poly gets most significant
void fill_table(unsigned int rate,
unsigned int order,
const polynomial_t *poly,
unsigned int *table) {
for (shift_register_t i = 0; i < 1 << order; i++) {
unsigned int out = 0;
unsigned int mask = 1;
for (size_t j = 0; j < rate; j++) {
out |= (popcount(i & poly[j]) % 2) ? mask : 0;
mask <<= 1;
}
table[i] = out;
}
}
pair_lookup_t pair_lookup_create(unsigned int rate,
unsigned int order,
const unsigned int *table) {
pair_lookup_t pairs;
pairs.keys = malloc(sizeof(unsigned int) * (1 << (order - 1)));
pairs.outputs = calloc((1 << (rate * 2)), sizeof(unsigned int));
unsigned int *inv_outputs = calloc((1 << (rate * 2)), sizeof(unsigned int));
unsigned int output_counter = 1;
// for every (even-numbered) shift register state, find the concatenated output of the state
// and the subsequent state that follows it (low bit set). then, check to see if this
// concatenated output has a unique key assigned to it already. if not, give it a key.
// if it does, retrieve the key. assign this key to the shift register state.
for (unsigned int i = 0; i < (1 << (order - 1)); i++) {
// first get the concatenated pair of outputs
unsigned int out = table[i * 2 + 1];
out <<= rate;
out |= table[i * 2];
// does this concatenated output exist in the outputs table yet?
if (!inv_outputs[out]) {
// doesn't exist, allocate a new key
inv_outputs[out] = output_counter;
pairs.outputs[output_counter] = out;
output_counter++;
}
// set the opaque key for the ith shift register state to the concatenated output entry
pairs.keys[i] = inv_outputs[out];
}
pairs.outputs_len = output_counter;
pairs.output_mask = (1 << (rate)) - 1;
pairs.output_width = rate;
pairs.distances = calloc(pairs.outputs_len, sizeof(distance_pair_t));
free(inv_outputs);
return pairs;
}
void pair_lookup_destroy(pair_lookup_t pairs) {
free(pairs.keys);
free(pairs.outputs);
free(pairs.distances);
}
void pair_lookup_fill_distance(pair_lookup_t pairs, distance_t *distances) {
for (unsigned int i = 1; i < pairs.outputs_len; i += 1) {
output_pair_t concat_out = pairs.outputs[i];
unsigned int i_0 = concat_out & pairs.output_mask;
concat_out >>= pairs.output_width;
unsigned int i_1 = concat_out;
pairs.distances[i] = (distances[i_1] << 16) | distances[i_0];
}
}

View File

@ -0,0 +1,17 @@
#include "correct/convolutional/metric.h"
// measure the square of the euclidean distance between x and y
// since euclidean dist is sqrt(a^2 + b^2 + ... + n^2), the square is just
// a^2 + b^2 + ... + n^2
distance_t metric_soft_distance_quadratic(unsigned int hard_x, const uint8_t *soft_y, size_t len) {
distance_t dist = 0;
for (unsigned int i = 0; i < len; i++) {
// first, convert hard_x to a soft measurement (0 -> 0, 1 - > 255)
unsigned int soft_x = (hard_x & 1) ? 255 : 0;
hard_x >>= 1;
int d = soft_y[i] - soft_x;
dist += d*d;
}
return dist >> 3;
}

View File

@ -0,0 +1,2 @@
set(SRCFILES lookup.c convolutional.c encode.c decode.c)
add_library(correct-convolutional-sse OBJECT ${SRCFILES})

View File

@ -0,0 +1,21 @@
#include "correct/convolutional/sse/convolutional.h"
correct_convolutional_sse *correct_convolutional_sse_create(size_t rate,
size_t order,
const polynomial_t *poly) {
correct_convolutional_sse *conv = malloc(sizeof(correct_convolutional_sse));
correct_convolutional *init_conv = _correct_convolutional_init(&conv->base_conv, rate, order, poly);
if (!init_conv) {
free(conv);
conv = NULL;
}
return conv;
}
void correct_convolutional_sse_destroy(correct_convolutional_sse *conv) {
if (conv->base_conv.has_init_decode) {
oct_lookup_destroy(conv->oct_lookup);
}
_correct_convolutional_teardown(&conv->base_conv);
free(conv);
}

View File

@ -0,0 +1,319 @@
#include "correct/convolutional/sse/convolutional.h"
static void convolutional_sse_decode_inner(correct_convolutional_sse *sse_conv, unsigned int sets,
const uint8_t *soft) {
correct_convolutional *conv = &sse_conv->base_conv;
shift_register_t highbit = 1 << (conv->order - 1);
unsigned int hist_buf_index = conv->history_buffer->index;
unsigned int hist_buf_cap = conv->history_buffer->cap;
unsigned int hist_buf_len = conv->history_buffer->len;
unsigned int hist_buf_rn_int = conv->history_buffer->renormalize_interval;
unsigned int hist_buf_rn_cnt = conv->history_buffer->renormalize_counter;
for (unsigned int i = conv->order - 1; i < (sets - conv->order + 1); i++) {
distance_t *distances = conv->distances;
// lasterrors are the aggregate bit errors for the states of
// shiftregister for the previous time slice
if (soft) {
if (conv->soft_measurement == CORRECT_SOFT_LINEAR) {
for (unsigned int j = 0; j < 1 << (conv->rate); j++) {
distances[j] =
metric_soft_distance_linear(j, soft + i * conv->rate, conv->rate);
}
} else {
for (unsigned int j = 0; j < 1 << (conv->rate); j++) {
distances[j] =
metric_soft_distance_quadratic(j, soft + i * conv->rate, conv->rate);
}
}
} else {
unsigned int out = bit_reader_read(conv->bit_reader, conv->rate);
for (unsigned int i = 0; i < 1 << (conv->rate); i++) {
distances[i] = metric_distance(i, out);
}
}
oct_lookup_t oct_lookup = sse_conv->oct_lookup;
oct_lookup_fill_distance(oct_lookup, distances);
// a mask to get the high order bit from the shift register
unsigned int num_iter = highbit << 1;
const distance_t *read_errors = conv->errors->read_errors;
// aggregate bit errors for this time slice
distance_t *write_errors = conv->errors->write_errors;
uint8_t *history = conv->history_buffer->history[hist_buf_index];
;
// walk through all states, ignoring oldest bit
// we will track a best register state (path) and the number of bit
// errors at that path at this time slice
// this loop considers two paths per iteration (high order bit set,
// clear)
// so, it only runs numstates/2 iterations
// we'll update the history for every state and find the path with the
// least aggregated bit errors
// now run the main loop
// we calculate 2 sets of 2 register states here (4 states per iter)
// this creates 2 sets which share a predecessor, and 2 sets which share
// a successor
//
// the first set definition is the two states that are the same except
// for the least order bit
// these two share a predecessor because their high n - 1 bits are the
// same (differ only by newest bit)
//
// the second set definition is the two states that are the same except
// for the high order bit
// these two share a successor because the oldest high order bit will be
// shifted out, and the other bits will be present in the successor
//
shift_register_t highbase = highbit >> 1;
shift_register_t oct_highbase = highbase >> 2;
for (shift_register_t low = 0, high = highbit, base = 0, oct = 0; high < num_iter;
low += 32, high += 32, base += 16, oct += 4) {
// shifted-right ancestors
// low and low_plus_one share low_past_error
// note that they are the same when shifted right by 1
// same goes for high and high_plus_one
__m128i past_shuffle_mask =
_mm_set_epi32(0x07060706, 0x05040504, 0x03020302, 0x01000100);
__m128i hist_mask =
_mm_set_epi32(0x80808080, 0x80808080, 0x0e0c0a09, 0x07050301);
// the loop below calculates 64 register states per loop iteration
// it does this by packing the 128-bit xmm registers with 8, 16-bit
// distances
// 4 of these registers hold distances for convolutional shift
// register states with the high bit cleared
// and 4 hold distances for the corresponding shift register
// states with the high bit set
// since each xmm register holds 8 distances, this adds up to a
// total of 8 * 8 = 64 shift register states
for (shift_register_t offset = 0, base_offset = 0; base_offset < 16;
offset += 32, base_offset += 16) {
// load the past error for the register states with the high
// order bit cleared
__m128i low_past_error =
_mm_loadl_epi64((const __m128i *)(read_errors + base + base_offset));
__m128i low_past_error0 =
_mm_loadl_epi64((const __m128i *)(read_errors + base + base_offset + 4));
__m128i low_past_error1 =
_mm_loadl_epi64((const __m128i *)(read_errors + base + base_offset + 8));
__m128i low_past_error2 =
_mm_loadl_epi64((const __m128i *)(read_errors + base + base_offset + 12));
// shuffle the low past error
// register states that differ only by their low order bit share
// a past error
low_past_error = _mm_shuffle_epi8(low_past_error, past_shuffle_mask);
low_past_error0 = _mm_shuffle_epi8(low_past_error0, past_shuffle_mask);
low_past_error1 = _mm_shuffle_epi8(low_past_error1, past_shuffle_mask);
low_past_error2 = _mm_shuffle_epi8(low_past_error2, past_shuffle_mask);
// repeat past error lookup for register states with high order
// bit set
__m128i high_past_error =
_mm_loadl_epi64((const __m128i *)(read_errors + highbase + base + base_offset));
__m128i high_past_error0 = _mm_loadl_epi64(
(const __m128i *)(read_errors + highbase + base + base_offset + 4));
__m128i high_past_error1 = _mm_loadl_epi64(
(const __m128i *)(read_errors + highbase + base + base_offset + 8));
__m128i high_past_error2 = _mm_loadl_epi64(
(const __m128i *)(read_errors + highbase + base + base_offset + 12));
high_past_error = _mm_shuffle_epi8(high_past_error, past_shuffle_mask);
high_past_error0 = _mm_shuffle_epi8(high_past_error0, past_shuffle_mask);
high_past_error1 = _mm_shuffle_epi8(high_past_error1, past_shuffle_mask);
high_past_error2 = _mm_shuffle_epi8(high_past_error2, past_shuffle_mask);
// __m128i this_shuffle_mask = (__m128i){0x80800100, 0x80800302,
// 0x80800504, 0x80800706};
// load the opaque oct distance table keys from out loop index
distance_oct_key_t low_key = oct_lookup.keys[oct + (base_offset / 4)];
distance_oct_key_t low_key0 = oct_lookup.keys[oct + (base_offset / 4) + 1];
distance_oct_key_t low_key1 = oct_lookup.keys[oct + (base_offset / 4) + 2];
distance_oct_key_t low_key2 = oct_lookup.keys[oct + (base_offset / 4) + 3];
// load the distances for the register states with high order
// bit cleared
__m128i low_this_error =
_mm_load_si128((const __m128i *)(oct_lookup.distances + low_key));
__m128i low_this_error0 =
_mm_load_si128((const __m128i *)(oct_lookup.distances + low_key0));
__m128i low_this_error1 =
_mm_load_si128((const __m128i *)(oct_lookup.distances + low_key1));
__m128i low_this_error2 =
_mm_load_si128((const __m128i *)(oct_lookup.distances + low_key2));
// add the distance for this time slice to the past distances
__m128i low_error = _mm_add_epi16(low_past_error, low_this_error);
__m128i low_error0 = _mm_add_epi16(low_past_error0, low_this_error0);
__m128i low_error1 = _mm_add_epi16(low_past_error1, low_this_error1);
__m128i low_error2 = _mm_add_epi16(low_past_error2, low_this_error2);
// repeat oct distance table lookup for registers with high
// order bit set
distance_oct_key_t high_key =
oct_lookup.keys[oct_highbase + oct + (base_offset / 4)];
distance_oct_key_t high_key0 =
oct_lookup.keys[oct_highbase + oct + (base_offset / 4) + 1];
distance_oct_key_t high_key1 =
oct_lookup.keys[oct_highbase + oct + (base_offset / 4) + 2];
distance_oct_key_t high_key2 =
oct_lookup.keys[oct_highbase + oct + (base_offset / 4) + 3];
__m128i high_this_error =
_mm_load_si128((const __m128i *)(oct_lookup.distances + high_key));
__m128i high_this_error0 =
_mm_load_si128((const __m128i *)(oct_lookup.distances + high_key0));
__m128i high_this_error1 =
_mm_load_si128((const __m128i *)(oct_lookup.distances + high_key1));
__m128i high_this_error2 =
_mm_load_si128((const __m128i *)(oct_lookup.distances + high_key2));
__m128i high_error = _mm_add_epi16(high_past_error, high_this_error);
__m128i high_error0 = _mm_add_epi16(high_past_error0, high_this_error0);
__m128i high_error1 = _mm_add_epi16(high_past_error1, high_this_error1);
__m128i high_error2 = _mm_add_epi16(high_past_error2, high_this_error2);
// distances for this time slice calculated
// find the least error between registers who differ only in
// their high order bit
__m128i min_error = _mm_min_epu16(low_error, high_error);
__m128i min_error0 = _mm_min_epu16(low_error0, high_error0);
__m128i min_error1 = _mm_min_epu16(low_error1, high_error1);
__m128i min_error2 = _mm_min_epu16(low_error2, high_error2);
_mm_store_si128((__m128i *)(write_errors + low + offset), min_error);
_mm_store_si128((__m128i *)(write_errors + low + offset + 8), min_error0);
_mm_store_si128((__m128i *)(write_errors + low + offset + 16), min_error1);
_mm_store_si128((__m128i *)(write_errors + low + offset + 24), min_error2);
// generate history bits as (low_error > least_error)
// this operation fills each element with all 1s if true and 0s
// if false
// in other words, we set the history bit to 1 if
// the register state with high order bit set was the least
// error
__m128i hist = _mm_cmpgt_epi16(low_error, min_error);
// pack the bits down from 16-bit wide to 8-bit wide to
// accomodate history table
hist = _mm_shuffle_epi8(hist, hist_mask);
__m128i hist0 = _mm_cmpgt_epi16(low_error0, min_error0);
hist0 = _mm_shuffle_epi8(hist0, hist_mask);
__m128i hist1 = _mm_cmpgt_epi16(low_error1, min_error1);
hist1 = _mm_shuffle_epi8(hist1, hist_mask);
__m128i hist2 = _mm_cmpgt_epi16(low_error2, min_error2);
hist2 = _mm_shuffle_epi8(hist2, hist_mask);
// write the least error so that the next time slice sees it as
// the past error
// store the history bits set by cmp and shuffle operations
_mm_storel_epi64((__m128i *)(history + low + offset), hist);
_mm_storel_epi64((__m128i *)(history + low + offset + 8), hist0);
_mm_storel_epi64((__m128i *)(history + low + offset + 16), hist1);
_mm_storel_epi64((__m128i *)(history + low + offset + 24), hist2);
}
}
// bypass the call to history buffer
// we should really make that function inline and remove this below
if (hist_buf_len == hist_buf_cap - 1 || hist_buf_rn_cnt == hist_buf_rn_int - 1) {
// restore hist buffer state and invoke it
conv->history_buffer->len = hist_buf_len;
conv->history_buffer->index = hist_buf_index;
conv->history_buffer->renormalize_counter = hist_buf_rn_cnt;
history_buffer_process(conv->history_buffer, write_errors, conv->bit_writer);
// restore our local values
hist_buf_len = conv->history_buffer->len;
hist_buf_index = conv->history_buffer->index;
hist_buf_cap = conv->history_buffer->cap;
hist_buf_rn_cnt = conv->history_buffer->renormalize_counter;
} else {
hist_buf_len++;
hist_buf_index++;
if (hist_buf_index == hist_buf_cap) {
hist_buf_index = 0;
}
hist_buf_rn_cnt++;
}
error_buffer_swap(conv->errors);
}
conv->history_buffer->len = hist_buf_len;
conv->history_buffer->index = hist_buf_index;
conv->history_buffer->renormalize_counter = hist_buf_rn_cnt;
}
static void _convolutional_sse_decode_init(correct_convolutional_sse *conv,
unsigned int min_traceback,
unsigned int traceback_length,
unsigned int renormalize_interval) {
_convolutional_decode_init(&conv->base_conv, min_traceback, traceback_length,
renormalize_interval);
conv->oct_lookup =
oct_lookup_create(conv->base_conv.rate, conv->base_conv.order, conv->base_conv.table);
}
static ssize_t _convolutional_sse_decode(correct_convolutional_sse *sse_conv,
size_t num_encoded_bits, size_t num_encoded_bytes,
uint8_t *msg, const soft_t *soft_encoded) {
correct_convolutional *conv = &sse_conv->base_conv;
if (!conv->has_init_decode) {
uint64_t max_error_per_input = conv->rate * soft_max;
// sse implementation unfortunately uses signed math on our unsigned values
// reduces usable distance by /2
unsigned int renormalize_interval = (distance_max / 2) / max_error_per_input;
_convolutional_sse_decode_init(sse_conv, 5 * conv->order, 100 * conv->order,
renormalize_interval);
}
size_t sets = num_encoded_bits / conv->rate;
// XXX fix this vvvvvv
size_t decoded_len_bytes = num_encoded_bytes;
bit_writer_reconfigure(conv->bit_writer, msg, decoded_len_bytes);
error_buffer_reset(conv->errors);
history_buffer_reset(conv->history_buffer);
// no outputs are generated during warmup
convolutional_decode_warmup(conv, sets, soft_encoded);
convolutional_sse_decode_inner(sse_conv, sets, soft_encoded);
convolutional_decode_tail(conv, sets, soft_encoded);
history_buffer_flush(conv->history_buffer, conv->bit_writer);
return bit_writer_length(conv->bit_writer);
}
ssize_t correct_convolutional_sse_decode(correct_convolutional_sse *conv, const uint8_t *encoded,
size_t num_encoded_bits, uint8_t *msg) {
if (num_encoded_bits % conv->base_conv.rate) {
// XXX turn this into an error code
// printf("encoded length of message must be a multiple of rate\n");
return -1;
}
size_t num_encoded_bytes =
(num_encoded_bits % 8) ? (num_encoded_bits / 8 + 1) : (num_encoded_bits / 8);
bit_reader_reconfigure(conv->base_conv.bit_reader, encoded, num_encoded_bytes);
return _convolutional_sse_decode(conv, num_encoded_bits, num_encoded_bytes, msg, NULL);
}
ssize_t correct_convolutional_sse_decode_soft(correct_convolutional_sse *conv, const soft_t *encoded,
size_t num_encoded_bits, uint8_t *msg) {
if (num_encoded_bits % conv->base_conv.rate) {
// XXX turn this into an error code
// printf("encoded length of message must be a multiple of rate\n");
return -1;
}
size_t num_encoded_bytes =
(num_encoded_bits % 8) ? (num_encoded_bits / 8 + 1) : (num_encoded_bits / 8);
return _convolutional_sse_decode(conv, num_encoded_bits, num_encoded_bytes, msg, encoded);
}

View File

@ -0,0 +1,9 @@
#include "correct/convolutional/sse/convolutional.h"
size_t correct_convolutional_sse_encode_len(correct_convolutional_sse *conv, size_t msg_len) {
return correct_convolutional_encode_len(&conv->base_conv, msg_len);
}
size_t correct_convolutional_sse_encode(correct_convolutional_sse *conv, const uint8_t *msg, size_t msg_len, uint8_t *encoded) {
return correct_convolutional_encode(&conv->base_conv, msg, msg_len, encoded);
}

View File

@ -0,0 +1,183 @@
#include "correct/convolutional/sse/lookup.h"
quad_lookup_t quad_lookup_create(unsigned int rate,
unsigned int order,
const unsigned int *table) {
quad_lookup_t quads;
quads.keys = malloc(sizeof(unsigned int) * (1 << (order - 2)));
quads.outputs = calloc((1 << (rate * 4)), sizeof(unsigned int));
unsigned int *inv_outputs = calloc((1 << (rate * 4)), sizeof(unsigned int));
unsigned int output_counter = 1;
// for every (even-numbered) shift register state, find the concatenated output of the state
// and the subsequent state that follows it (low bit set). then, check to see if this
// concatenated output has a unique key assigned to it already. if not, give it a key.
// if it does, retrieve the key. assign this key to the shift register state.
for (unsigned int i = 0; i < (1 << (order - 2)); i++) {
// first get the concatenated quad of outputs
unsigned int out = table[i * 4 + 3];
out <<= rate;
out |= table[i * 4 + 2];
out <<= rate;
out |= table[i * 4 + 1];
out <<= rate;
out |= table[i * 4];
// does this concatenated output exist in the outputs table yet?
if (!inv_outputs[out]) {
// doesn't exist, allocate a new key
inv_outputs[out] = output_counter;
quads.outputs[output_counter] = out;
output_counter++;
}
// set the opaque key for the ith shift register state to the concatenated output entry
quads.keys[i] = inv_outputs[out];
}
quads.outputs_len = output_counter;
quads.output_mask = (1 << (rate)) - 1;
quads.output_width = rate;
quads.distances = calloc(quads.outputs_len, sizeof(distance_quad_t));
free(inv_outputs);
return quads;
}
void quad_lookup_destroy(quad_lookup_t quads) {
free(quads.keys);
free(quads.outputs);
free(quads.distances);
}
void quad_lookup_fill_distance(quad_lookup_t quads, distance_t *distances) {
for (unsigned int i = 1; i < quads.outputs_len; i += 1) {
output_quad_t concat_out = quads.outputs[i];
unsigned int i_0 = concat_out & quads.output_mask;
concat_out >>= quads.output_width;
unsigned int i_1 = concat_out & quads.output_mask;
concat_out >>= quads.output_width;
unsigned int i_2 = concat_out & quads.output_mask;
concat_out >>= quads.output_width;
unsigned int i_3 = concat_out;
quads.distances[i] = ((uint64_t)distances[i_3] << 48) | ((uint64_t)distances[i_2] << 32) | (distances[i_1] << 16) | distances[i_0];
}
}
distance_oct_key_t oct_lookup_find_key(output_oct_t *outputs, output_oct_t out, size_t num_keys) {
for (size_t i = 1; i < num_keys; i++) {
if (outputs[i] == out) {
return i;
}
}
return 0;
}
oct_lookup_t oct_lookup_create(unsigned int rate,
unsigned int order,
const unsigned int *table) {
oct_lookup_t octs;
octs.keys = malloc((1 << (order - 3)) * sizeof(distance_oct_key_t));
octs.outputs = malloc(((output_oct_t)2 << rate) * sizeof(uint64_t));
output_oct_t *short_outs = calloc(((output_oct_t)2 << rate), sizeof(output_oct_t));
size_t outputs_len = 2 << rate;
unsigned int output_counter = 1;
// for every (even-numbered) shift register state, find the concatenated output of the state
// and the subsequent state that follows it (low bit set). then, check to see if this
// concatenated output has a unique key assigned to it already. if not, give it a key.
// if it does, retrieve the key. assign this key to the shift register state.
for (shift_register_t i = 0; i < (1 << (order - 3)); i++) {
// first get the concatenated oct of outputs
output_oct_t out = table[i * 8 + 7];
out <<= rate;
out |= table[i * 8 + 6];
out <<= rate;
out |= table[i * 8 + 5];
out <<= rate;
out |= table[i * 8 + 4];
out <<= rate;
out |= table[i * 8 + 3];
out <<= rate;
out |= table[i * 8 + 2];
out <<= rate;
out |= table[i * 8 + 1];
out <<= rate;
out |= table[i * 8];
distance_oct_key_t key = oct_lookup_find_key(short_outs, out, output_counter);
// does this concatenated output exist in the outputs table yet?
if (!key) {
// doesn't exist, allocate a new key
// now build it in expanded form
output_oct_t expanded_out = table[i * 8 + 7];
expanded_out <<= 8;
expanded_out |= table[i * 8 + 6];
expanded_out <<= 8;
expanded_out |= table[i * 8 + 5];
expanded_out <<= 8;
expanded_out |= table[i * 8 + 4];
expanded_out <<= 8;
expanded_out |= table[i * 8 + 3];
expanded_out <<= 8;
expanded_out |= table[i * 8 + 2];
expanded_out <<= 8;
expanded_out |= table[i * 8 + 1];
expanded_out <<= 8;
expanded_out |= table[i * 8];
if (output_counter == outputs_len) {
octs.outputs = realloc(octs.outputs, outputs_len * 2 * sizeof(output_oct_t));
short_outs = realloc(short_outs, outputs_len * 2 * sizeof(output_oct_t));
outputs_len *= 2;
}
short_outs[output_counter] = out;
octs.outputs[output_counter] = expanded_out;
key = output_counter;
output_counter++;
}
// set the opaque key for the ith shift register state to the concatenated output entry
// we multiply the key by 2 since the distances are strided by 2
octs.keys[i] = key * 2;
}
free(short_outs);
octs.outputs_len = output_counter;
octs.output_mask = (1 << (rate)) - 1;
octs.output_width = rate;
octs.distances = malloc(octs.outputs_len * 2 * sizeof(uint64_t));
return octs;
}
void oct_lookup_destroy(oct_lookup_t octs) {
free(octs.keys);
free(octs.outputs);
free(octs.distances);
}
// WIP: sse approach to filling the distance table
/*
void oct_lookup_fill_distance_sse(oct_lookup_t octs, distance_t *distances) {
distance_pair_t *distance_pair = (distance_pair_t*)octs.distances;
__v4si index_shuffle_mask = (__v4si){0xffffff00, 0xffffff01, 0xffffff02, 0xffffff03};
__m256i dist_shuffle_mask = (__m256i){0x01000504, 0x09080d0c, 0xffffffff, 0xffffffff,
0x01000504, 0x09080d0c, 0xffffffff, 0xffffffff};
const int dist_permute_mask = 0x0c;
for (unsigned int i = 1; i < octs.outputs_len; i += 2) {
// big heaping todo vvv
// a) we want 16 bit distances GATHERed, not 32 bit
// b) we need to load 8 of those distances, not 4
__v4si short_concat_index = _mm_loadl_epi64(octs.outputs + 2*i);
__v4si short_concat_index0 = _mm_loadl_epi64(octs.outputs + 2*i + 1);
__m256i concat_index = _mm256_cvtepu8_epi32(short_concat_index);
__m256i concat_index0 = _mm256_cvtepu8_epi32(short_concat_index0);
__m256i dist = _mm256_i32gather_epi32(distances, concat_index, sizeof(distance_t));
__m256i dist0 = _mm256_i32gather_epi32(distances, concat_index0, sizeof(distance_t));
dist = _mm256_shuffle_epi8(dist, dist_shuffle_mask);
dist0 = _mm256_shuffle_epi8(dist0, dist_shuffle_mask);
dist = __builtin_shufflevector(dist, dist, 0, 5, 0, 0);
dist0 = __builtin_shufflevector(dist0, dist0, 0, 5, 0, 0);
__v4si packed_dist = _mm256_castsi256_si128(dist);
_mm_store_si128(distance_pair + 8 * i, packed_dist);
__v4si packed_dist0 = _mm256_castsi256_si128(dist0);
_mm_store_si128(distance_pair + 8 * i + 4, packed_dist0);
}
}
*/

View File

@ -0,0 +1,30 @@
#ifndef CORRECT_SSE_H
#define CORRECT_SSE_H
#include <correct.h>
struct correct_convolutional_sse;
typedef struct correct_convolutional_sse correct_convolutional_sse;
/* SSE versions of libcorrect's convolutional encoder/decoder.
* These instances should not be used with the non-sse functions,
* and non-sse instances should not be used with the sse functions.
*/
correct_convolutional_sse *correct_convolutional_sse_create(
size_t rate, size_t order, const correct_convolutional_polynomial_t *poly);
void correct_convolutional_sse_destroy(correct_convolutional_sse *conv);
size_t correct_convolutional_sse_encode_len(correct_convolutional_sse *conv, size_t msg_len);
size_t correct_convolutional_sse_encode(correct_convolutional_sse *conv, const uint8_t *msg,
size_t msg_len, uint8_t *encoded);
ssize_t correct_convolutional_sse_decode(correct_convolutional_sse *conv, const uint8_t *encoded,
size_t num_encoded_bits, uint8_t *msg);
ssize_t correct_convolutional_sse_decode_soft(correct_convolutional_sse *conv,
const correct_convolutional_soft_t *encoded,
size_t num_encoded_bits, uint8_t *msg);
#endif

View File

@ -0,0 +1,277 @@
#ifndef CORRECT_H
#define CORRECT_H
#include <stdint.h>
#ifndef _MSC_VER
#include <unistd.h>
#else
#include <stddef.h>
typedef ptrdiff_t ssize_t;
#endif
// Convolutional Codes
// Convolutional polynomials are 16 bits wide
typedef uint16_t correct_convolutional_polynomial_t;
static const correct_convolutional_polynomial_t correct_conv_r12_6_polynomial[] = {073, 061};
static const correct_convolutional_polynomial_t correct_conv_r12_7_polynomial[] = {0161, 0127};
static const correct_convolutional_polynomial_t correct_conv_r12_8_polynomial[] = {0225, 0373};
static const correct_convolutional_polynomial_t correct_conv_r12_9_polynomial[] = {0767, 0545};
static const correct_convolutional_polynomial_t correct_conv_r13_6_polynomial[] = {053, 075, 047};
static const correct_convolutional_polynomial_t correct_conv_r13_7_polynomial[] = {0137, 0153,
0121};
static const correct_convolutional_polynomial_t correct_conv_r13_8_polynomial[] = {0333, 0257,
0351};
static const correct_convolutional_polynomial_t correct_conv_r13_9_polynomial[] = {0417, 0627,
0675};
typedef uint8_t correct_convolutional_soft_t;
struct correct_convolutional;
typedef struct correct_convolutional correct_convolutional;
/* correct_convolutional_create allocates and initializes an encoder/decoder for
* a convolutional code with the given parameters. This function expects that
* poly will contain inv_rate elements. E.g., to create a conv. code instance
* with rate 1/2, order 7 and polynomials 0161, 0127, call
* correct_convolutional_create(2, 7, []correct_convolutional_polynomial_t{0161, 0127});
*
* If this call is successful, it returns a non-NULL pointer.
*/
correct_convolutional *correct_convolutional_create(size_t inv_rate, size_t order,
const correct_convolutional_polynomial_t *poly);
/* correct_convolutional_destroy releases all resources associated
* with conv. This pointer should not be used for further calls
* after calling destroy.
*/
void correct_convolutional_destroy(correct_convolutional *conv);
/* correct_convolutional_encode_len returns the number of *bits*
* in a msg_len of given size, in *bytes*. In order to convert
* this returned length to bytes, save the result of the length
* modulo 8. If it's nonzero, then the length in bytes is
* length/8 + 1. If it is zero, then the length is just
* length/8.
*/
size_t correct_convolutional_encode_len(correct_convolutional *conv, size_t msg_len);
/* correct_convolutional_encode uses the given conv instance to
* encode a block of data and write it to encoded. The length of
* encoded must be long enough to hold the resulting encoded length,
* which can be calculated by calling correct_convolutional_encode_len.
* However, this length should first be converted to bytes, as that
* function returns the length in bits.
*
* This function returns the number of bits written to encoded. If
* this is not an exact multiple of 8, then it occupies an additional
* byte.
*/
size_t correct_convolutional_encode(correct_convolutional *conv, const uint8_t *msg, size_t msg_len,
uint8_t *encoded);
/* correct_convolutional_decode uses the given conv instance to
* decode a block encoded by correct_convolutional_encode. This
* call can cope with some bits being corrupted. This function
* cannot detect if there are too many bits corrupted, however,
* and will still write a message even if it is not recovered
* correctly. It is up to the user to perform checksums or CRC
* in order to guarantee that the decoded message is intact.
*
* num_encoded_bits should contain the length of encoded in *bits*.
* This value need not be an exact multiple of 8. However,
* it must be a multiple of the inv_rate used to create
* the conv instance.
*
* This function writes the result to msg, which must be large
* enough to hold the decoded message. A good conservative size
* for this buffer is the number of encoded bits multiplied by the
* rate of the code, e.g. for a rate 1/2 code, divide by 2. This
* value should then be converted to bytes to find the correct
* length for msg.
*
* This function returns the number of bytes written to msg. If
* it fails, it returns -1.
*/
ssize_t correct_convolutional_decode(correct_convolutional *conv, const uint8_t *encoded,
size_t num_encoded_bits, uint8_t *msg);
/* correct_convolutional_decode_soft uses the given conv instance
* to decode a block encoded by correct_convolutional_encode and
* then modulated/demodulated to 8-bit symbols. This function expects
* that 1 is mapped to 255 and 0 to 0. An erased symbol should be
* set to 128. The decoded message may contain errors.
*
* num_encoded_bits should contain the length of encoded in *bits*.
* This value need not be an exact multiple of 8. However,
* it must be a multiple of the inv_rate used to create
* the conv instance.
*
* This function writes the result to msg, which must be large
* enough to hold the decoded message. A good conservative size
* for this buffer is the number of encoded bits multiplied by the
* rate of the code, e.g. for a rate 1/2 code, divide by 2. This
* value should then be converted to bytes to find the correct
* length for msg.
*
* This function returns the number of bytes written to msg. If
* it fails, it returns -1.
*/
ssize_t correct_convolutional_decode_soft(correct_convolutional *conv,
const correct_convolutional_soft_t *encoded,
size_t num_encoded_bits, uint8_t *msg);
// Reed-Solomon
struct correct_reed_solomon;
typedef struct correct_reed_solomon correct_reed_solomon;
static const uint16_t correct_rs_primitive_polynomial_8_4_3_2_0 =
0x11d; // x^8 + x^4 + x^3 + x^2 + 1
static const uint16_t correct_rs_primitive_polynomial_8_5_3_1_0 =
0x12b; // x^8 + x^5 + x^3 + x + 1
static const uint16_t correct_rs_primitive_polynomial_8_5_3_2_0 =
0x12d; // x^8 + x^5 + x^3 + x^2 + 1
static const uint16_t correct_rs_primitive_polynomial_8_6_3_2_0 =
0x14d; // x^8 + x^6 + x^3 + x^2 + 1
static const uint16_t correct_rs_primitive_polynomial_8_6_4_3_2_1_0 =
0x15f; // x^8 + x^6 + x^4 + x^3 + x^2 + x + 1;
static const uint16_t correct_rs_primitive_polynomial_8_6_5_1_0 =
0x163; // x^8 + x^6 + x^5 + x + 1
static const uint16_t correct_rs_primitive_polynomial_8_6_5_2_0 =
0x165; // x^8 + x^6 + x^5 + x^2 + 1
static const uint16_t correct_rs_primitive_polynomial_8_6_5_3_0 =
0x169; // x^8 + x^6 + x^5 + x^3 + 1
static const uint16_t correct_rs_primitive_polynomial_8_6_5_4_0 =
0x171; // x^8 + x^6 + x^5 + x^4 + 1
static const uint16_t correct_rs_primitive_polynomial_8_7_2_1_0 =
0x187; // x^8 + x^7 + x^2 + x + 1
static const uint16_t correct_rs_primitive_polynomial_8_7_3_2_0 =
0x18d; // x^8 + x^7 + x^3 + x^2 + 1
static const uint16_t correct_rs_primitive_polynomial_8_7_5_3_0 =
0x1a9; // x^8 + x^7 + x^5 + x^3 + 1
static const uint16_t correct_rs_primitive_polynomial_8_7_6_1_0 =
0x1c3; // x^8 + x^7 + x^6 + x + 1
static const uint16_t correct_rs_primitive_polynomial_8_7_6_3_2_1_0 =
0x1cf; // x^8 + x^7 + x^6 + x^3 + x^2 + x + 1
static const uint16_t correct_rs_primitive_polynomial_8_7_6_5_2_1_0 =
0x1e7; // x^8 + x^7 + x^6 + x^5 + x^2 + x + 1
static const uint16_t correct_rs_primitive_polynomial_8_7_6_5_4_2_0 =
0x1f5; // x^8 + x^7 + x^6 + x^5 + x^4 + x^2 + 1
static const uint16_t correct_rs_primitive_polynomial_ccsds =
0x187; // x^8 + x^7 + x^2 + x + 1
/* correct_reed_solomon_create allocates and initializes an
* encoder/decoder for a given reed solomon error correction
* code. The block size must be 255 bytes with 8-bit symbols.
*
* This block can repair corrupted bytes. It can handle as
* many as num_roots/2 bytes having corruption and still recover
* the encoded payload. However, using more num_roots
* adds more parity overhead and substantially increases
* the computational time for decoding.
*
* primitive_polynomial should be one of the given values in this
* file. Sane values for first_consecutive_root and
* generator_root_gap are 1 and 1. Not all combinations of
* values produce valid codes.
*/
correct_reed_solomon *correct_reed_solomon_create(uint16_t primitive_polynomial,
uint8_t first_consecutive_root,
uint8_t generator_root_gap,
size_t num_roots);
/* correct_reed_solomon_encode uses the rs instance to encode
* parity information onto a block of data. msg_length should be
* no more than the payload size for one block e.g. no more
* than 223 for a (255, 223) code. Shorter blocks will be encoded
* with virtual padding where the padding is not emitted.
*
* encoded should be at least msg_length + parity length bytes long
*
* It is allowable for msg and encoded to be the same pointer. In
* that case, the parity bytes will be written after the msg bytes
* end.
*
* This function returns the number of bytes written to encoded.
*/
ssize_t correct_reed_solomon_encode(correct_reed_solomon *rs, const uint8_t *msg, size_t msg_length,
uint8_t *encoded);
/* correct_reed_solomon_decode uses the rs instance to decode
* a payload from a block containing payload and parity bytes.
* This function can recover in spite of some bytes being corrupted.
*
* In most cases, if the block is too corrupted, this function
* will return -1 and not perform decoding. It is possible but
* unlikely that the payload written to msg will contain
* errors when this function returns a positive value.
*
* msg should be long enough to contain a decoded payload for
* this encoded block.
*
* This function returns a positive number of bytes written to msg
* if it has decoded or -1 if it has encountered an error.
*/
ssize_t correct_reed_solomon_decode(correct_reed_solomon *rs, const uint8_t *encoded,
size_t encoded_length, uint8_t *msg);
/* correct_reed_solomon_decode_with_erasures uses the rs
* instance to decode a payload from a block containing payload
* and parity bytes. Additionally, the user can provide the
* indices of bytes which have been suspected to be corrupted.
* This erasure information is typically provided by a demodulating
* or receiving device. This function can recover with
* some additional errors on top of the erasures.
*
* In order to successfully decode, the quantity
* (num_erasures + 2*num_errors) must be less than
* num_roots.
*
* erasure_locations shold contain erasure_length items.
* erasure_length should not exceed the number of parity
* bytes encoded into this block.
*
* In most cases, if the block is too corrupted, this function
* will return -1 and not perform decoding. It is possible but
* unlikely that the payload written to msg will contain
* errors when this function returns a positive value.
*
* msg should be long enough to contain a decoded payload for
* this encoded block.
*
* This function returns a positive number of bytes written to msg
* if it has decoded or -1 if it has encountered an error.
*/
ssize_t correct_reed_solomon_decode_with_erasures(correct_reed_solomon *rs, const uint8_t *encoded,
size_t encoded_length,
const uint8_t *erasure_locations,
size_t erasure_length, uint8_t *msg);
/* correct_reed_solomon_destroy releases the resources
* associated with rs. This pointer should not be
* used for any functions after this call.
*/
void correct_reed_solomon_destroy(correct_reed_solomon *rs);
#endif

View File

@ -0,0 +1,28 @@
#ifndef CORRECT_CONVOLUTIONAL
#define CORRECT_CONVOLUTIONAL
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#include "correct.h"
#include "correct/portable.h"
typedef unsigned int shift_register_t;
typedef uint16_t polynomial_t;
typedef uint64_t path_t;
typedef uint8_t soft_t;
static const soft_t soft_max = UINT8_MAX;
typedef uint16_t distance_t;
static const distance_t distance_max = UINT16_MAX;
typedef enum {
CORRECT_SOFT_LINEAR,
CORRECT_SOFT_QUADRATIC,
} soft_measurement_t;
#endif

View File

@ -0,0 +1,44 @@
#ifndef CORRECT_CONVOLUTIONAL_BIT
#define CORRECT_CONVOLUTIONAL_BIT
#include "correct/convolutional.h"
typedef struct {
uint8_t current_byte;
unsigned int current_byte_len;
uint8_t *bytes;
size_t byte_index;
size_t len;
} bit_writer_t;
bit_writer_t *bit_writer_create(uint8_t *bytes, size_t len);
void bit_writer_reconfigure(bit_writer_t *w, uint8_t *bytes, size_t len);
void bit_writer_destroy(bit_writer_t *w);
void bit_writer_write(bit_writer_t *w, uint8_t val, unsigned int n);
void bit_writer_write_1(bit_writer_t *w, uint8_t val);
void bit_writer_write_bitlist_reversed(bit_writer_t *w, uint8_t *l, size_t len);
void bit_writer_flush_byte(bit_writer_t *w);
size_t bit_writer_length(bit_writer_t *w);
typedef struct {
uint8_t current_byte;
size_t byte_index;
size_t len;
size_t current_byte_len;
const uint8_t *bytes;
} bit_reader_t;
bit_reader_t *bit_reader_create(const uint8_t *bytes, size_t len);
void bit_reader_reconfigure(bit_reader_t *r, const uint8_t *bytes, size_t len);
void bit_reader_destroy(bit_reader_t *r);
uint8_t bit_reader_read(bit_reader_t *r, unsigned int n);
#endif

View File

@ -0,0 +1,40 @@
#ifndef CORRECT_CONVOLUTIONAL_H
#define CORRECT_CONVOLUTIONAL_H
#include "correct/convolutional.h"
#include "correct/convolutional/bit.h"
#include "correct/convolutional/metric.h"
#include "correct/convolutional/lookup.h"
#include "correct/convolutional/history_buffer.h"
#include "correct/convolutional/error_buffer.h"
struct correct_convolutional {
const unsigned int *table; // size 2**order
size_t rate; // e.g. 2, 3...
size_t order; // e.g. 7, 9...
unsigned int numstates; // 2**order
bit_writer_t *bit_writer;
bit_reader_t *bit_reader;
bool has_init_decode;
distance_t *distances;
pair_lookup_t pair_lookup;
soft_measurement_t soft_measurement;
history_buffer *history_buffer;
error_buffer_t *errors;
};
correct_convolutional *_correct_convolutional_init(correct_convolutional *conv,
size_t rate, size_t order,
const polynomial_t *poly);
void _correct_convolutional_teardown(correct_convolutional *conv);
// portable versions
void _convolutional_decode_init(correct_convolutional *conv, unsigned int min_traceback, unsigned int traceback_length, unsigned int renormalize_interval);
void convolutional_decode_warmup(correct_convolutional *conv, unsigned int sets,
const uint8_t *soft);
void convolutional_decode_inner(correct_convolutional *conv, unsigned int sets,
const uint8_t *soft);
void convolutional_decode_tail(correct_convolutional *conv, unsigned int sets,
const uint8_t *soft);
#endif

View File

@ -0,0 +1,15 @@
#include "correct/convolutional.h"
typedef struct {
unsigned int index;
distance_t *errors[2];
unsigned int num_states;
const distance_t *read_errors;
distance_t *write_errors;
} error_buffer_t;
error_buffer_t *error_buffer_create(unsigned int num_states);
void error_buffer_destroy(error_buffer_t *buf);
void error_buffer_reset(error_buffer_t *buf);
void error_buffer_swap(error_buffer_t *buf);

View File

@ -0,0 +1,59 @@
#include "correct/convolutional.h"
#include "correct/convolutional/bit.h"
// ring buffer of path histories
// generates output bits after accumulating sufficient history
typedef struct {
// history entries must be at least this old to be decoded
const unsigned int min_traceback_length;
// we'll decode entries in bursts. this tells us the length of the burst
const unsigned int traceback_group_length;
// we will store a total of cap entries. equal to min_traceback_length +
// traceback_group_length
const unsigned int cap;
// how many states in the shift register? this is one of the dimensions of
// history table
const unsigned int num_states;
// what's the high order bit of the shift register?
const shift_register_t highbit;
// history is a compact history representation for every shift register
// state,
// one bit per time slice
uint8_t **history;
// which slice are we writing next?
unsigned int index;
// how many valid entries are there?
unsigned int len;
// temporary store of fetched bits
uint8_t *fetched;
// how often should we renormalize?
unsigned int renormalize_interval;
unsigned int renormalize_counter;
} history_buffer;
history_buffer *history_buffer_create(unsigned int min_traceback_length,
unsigned int traceback_group_length,
unsigned int renormalize_interval,
unsigned int num_states,
shift_register_t highbit);
void history_buffer_destroy(history_buffer *buf);
void history_buffer_reset(history_buffer *buf);
void history_buffer_step(history_buffer *buf);
uint8_t *history_buffer_get_slice(history_buffer *buf);
shift_register_t history_buffer_search(history_buffer *buf,
const distance_t *distances,
unsigned int search_every);
void history_buffer_traceback(history_buffer *buf, shift_register_t bestpath,
unsigned int min_traceback_length,
bit_writer_t *output);
void history_buffer_process_skip(history_buffer *buf, distance_t *distances,
bit_writer_t *output, unsigned int skip);
void history_buffer_process(history_buffer *buf, distance_t *distances,
bit_writer_t *output);
void history_buffer_flush(history_buffer *buf, bit_writer_t *output);

View File

@ -0,0 +1,27 @@
#ifndef CORRECT_CONVOLUTIONAL_LOOKUP
#define CORRECT_CONVOLUTIONAL_LOOKUP
#include "correct/convolutional.h"
typedef unsigned int distance_pair_key_t;
typedef uint32_t output_pair_t;
typedef uint32_t distance_pair_t;
typedef struct {
distance_pair_key_t *keys;
output_pair_t *outputs;
output_pair_t output_mask;
unsigned int output_width;
size_t outputs_len;
distance_pair_t *distances;
} pair_lookup_t;
void fill_table(unsigned int order,
unsigned int rate,
const polynomial_t *poly,
unsigned int *table);
pair_lookup_t pair_lookup_create(unsigned int rate,
unsigned int order,
const unsigned int *table);
void pair_lookup_destroy(pair_lookup_t pairs);
void pair_lookup_fill_distance(pair_lookup_t pairs, distance_t *distances);
#endif

View File

@ -0,0 +1,20 @@
#include "correct/convolutional.h"
// measure the hamming distance of two bit strings
// implemented as population count of x XOR y
static inline distance_t metric_distance(unsigned int x, unsigned int y) {
return popcount(x ^ y);
}
static inline distance_t metric_soft_distance_linear(unsigned int hard_x, const uint8_t *soft_y, size_t len) {
distance_t dist = 0;
for (unsigned int i = 0; i < len; i++) {
unsigned int soft_x = ((int8_t)(0) - (hard_x & 1)) & 0xff;
hard_x >>= 1;
int d = soft_y[i] - soft_x;
dist += (d < 0) ? -d : d;
}
return dist;
}
distance_t metric_soft_distance_quadratic(unsigned int hard_x, const uint8_t *soft_y, size_t len);

View File

@ -0,0 +1,15 @@
#include "correct/convolutional/convolutional.h"
#include "correct/convolutional/sse/lookup.h"
// BIG HEAPING TODO sort out the include mess
#include "correct-sse.h"
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
struct correct_convolutional_sse {
correct_convolutional base_conv;
oct_lookup_t oct_lookup;
};

View File

@ -0,0 +1,65 @@
#include "correct/convolutional/lookup.h"
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
typedef unsigned int distance_quad_key_t;
typedef unsigned int output_quad_t;
typedef uint64_t distance_quad_t;
typedef struct {
distance_quad_key_t *keys;
output_quad_t *outputs;
output_quad_t output_mask;
unsigned int output_width;
size_t outputs_len;
distance_quad_t *distances;
} quad_lookup_t;
typedef uint16_t distance_oct_key_t;
typedef uint64_t output_oct_t;
typedef uint64_t distance_oct_t;
typedef struct {
distance_oct_key_t *keys;
output_oct_t *outputs;
output_oct_t output_mask;
unsigned int output_width;
size_t outputs_len;
distance_oct_t *distances;
} oct_lookup_t;
quad_lookup_t quad_lookup_create(unsigned int rate,
unsigned int order,
const unsigned int *table);
void quad_lookup_destroy(quad_lookup_t quads);
void quad_lookup_fill_distance(quad_lookup_t quads, distance_t *distances);
distance_oct_key_t oct_lookup_find_key(output_oct_t *outputs, output_oct_t out, size_t num_keys);
oct_lookup_t oct_lookup_create(unsigned int rate,
unsigned int order,
const unsigned int *table);
void oct_lookup_destroy(oct_lookup_t octs);
static inline void oct_lookup_fill_distance(oct_lookup_t octs, distance_t *distances) {
distance_pair_t *pairs = (distance_pair_t*)octs.distances;
for (unsigned int i = 1; i < octs.outputs_len; i += 1) {
output_oct_t concat_out = octs.outputs[i];
unsigned int i_0 = concat_out & 0xff;
unsigned int i_1 = (concat_out >> 8) & 0xff;
unsigned int i_2 = (concat_out >> 16) & 0xff;
unsigned int i_3 = (concat_out >> 24) & 0xff;
pairs[i*4 + 1] = distances[i_3] << 16 | distances[i_2];
pairs[i*4 + 0] = distances[i_1] << 16 | distances[i_0];
concat_out >>= 32;
unsigned int i_4 = concat_out & 0xff;
unsigned int i_5 = (concat_out >> 8) & 0xff;
unsigned int i_6 = (concat_out >> 16) & 0xff;
unsigned int i_7 = (concat_out >> 24) & 0xff;
pairs[i*4 + 3] = distances[i_7] << 16 | distances[i_6];
pairs[i*4 + 2] = distances[i_5] << 16 | distances[i_4];
}
}

View File

@ -0,0 +1,20 @@
#ifdef __GNUC__
#define HAVE_BUILTINS
#endif
#ifdef HAVE_BUILTINS
#define popcount __builtin_popcount
#define prefetch __builtin_prefetch
#else
static inline int popcount(int x) {
/* taken from the helpful http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
x = x - ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
return ((x + (x >> 4) & 0x0f0f0f0f) * 0x01010101) >> 24;
}
static inline void prefetch(void *x) {}
#endif

View File

@ -0,0 +1,76 @@
#ifndef CORRECT_REED_SOLOMON
#define CORRECT_REED_SOLOMON
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <stdint.h>
#include "correct.h"
#include "correct/portable.h"
// an element in GF(2^8)
typedef uint8_t field_element_t;
// a power of the primitive element alpha
typedef uint8_t field_logarithm_t;
// give us some bits of headroom to do arithmetic
// variables of this type aren't really in any proper space
typedef uint16_t field_operation_t;
// generated by find_poly
typedef struct {
const field_element_t *exp;
const field_logarithm_t *log;
} field_t;
typedef struct {
field_element_t *coeff;
unsigned int order;
} polynomial_t;
struct correct_reed_solomon {
size_t block_length;
size_t message_length;
size_t min_distance;
field_logarithm_t first_consecutive_root;
field_logarithm_t generator_root_gap;
field_t field;
polynomial_t generator;
field_element_t *generator_roots;
field_logarithm_t **generator_root_exp;
polynomial_t encoded_polynomial;
polynomial_t encoded_remainder;
field_element_t *syndromes;
field_element_t *modified_syndromes;
polynomial_t received_polynomial;
polynomial_t error_locator;
polynomial_t error_locator_log;
polynomial_t erasure_locator;
field_element_t *error_roots;
field_element_t *error_vals;
field_logarithm_t *error_locations;
field_logarithm_t **element_exp;
// scratch
// (do no allocations at steady state)
// used during find_error_locator
polynomial_t last_error_locator;
// used during error value search
polynomial_t error_evaluator;
polynomial_t error_locator_derivative;
polynomial_t init_from_roots_scratch[2];
bool has_init_decode;
};
#endif

View File

@ -0,0 +1,3 @@
#include "correct/reed-solomon.h"
#include "correct/reed-solomon/field.h"
#include "correct/reed-solomon/polynomial.h"

View File

@ -0,0 +1,3 @@
#include "correct/reed-solomon.h"
#include "correct/reed-solomon/field.h"
#include "correct/reed-solomon/polynomial.h"

View File

@ -0,0 +1,167 @@
#ifndef CORRECT_REED_SOLOMON_FIELD
#define CORRECT_REED_SOLOMON_FIELD
#include "correct/reed-solomon.h"
/*
field_t field_create(field_operation_t primitive_poly);
void field_destroy(field_t field);
field_element_t field_add(field_t field, field_element_t l, field_element_t r);
field_element_t field_sub(field_t field, field_element_t l, field_element_t r);
field_element_t field_sum(field_t field, field_element_t elem, unsigned int n);
field_element_t field_mul(field_t field, field_element_t l, field_element_t r);
field_element_t field_div(field_t field, field_element_t l, field_element_t r);
field_logarithm_t field_mul_log(field_t field, field_logarithm_t l, field_logarithm_t r);
field_logarithm_t field_div_log(field_t field, field_logarithm_t l, field_logarithm_t r);
field_element_t field_mul_log_element(field_t field, field_logarithm_t l, field_logarithm_t r);
field_element_t field_pow(field_t field, field_element_t elem, int pow);
*/
static inline field_element_t field_mul_log_element(field_t field, field_logarithm_t l, field_logarithm_t r) {
// like field_mul_log, but returns a field_element_t
// because we are doing lookup here, we can safely skip the wrapover check
field_operation_t res = (field_operation_t)l + (field_operation_t)r;
return field.exp[res];
}
static inline field_t field_create(field_operation_t primitive_poly) {
// in GF(2^8)
// log and exp
// bits are in GF(2), compute alpha^val in GF(2^8)
// exp should be of size 512 so that it can hold a "wraparound" which prevents some modulo ops
// log should be of size 256. no wraparound here, the indices into this table are field elements
field_element_t *exp = malloc(512 * sizeof(field_element_t));
field_logarithm_t *log = malloc(256 * sizeof(field_logarithm_t));
// assume alpha is a primitive element, p(x) (primitive_poly) irreducible in GF(2^8)
// addition is xor
// subtraction is addition (also xor)
// e.g. x^5 + x^4 + x^4 + x^2 + 1 = x^5 + x^2 + 1
// each row of exp contains the field element found by exponentiating
// alpha by the row index
// each row of log contains the coefficients of
// alpha^7 + alpha^6 + alpha^5 + alpha^4 + alpha^3 + alpha^2 + alpha + 1
// as 8 bits packed into one byte
field_operation_t element = 1;
exp[0] = (field_element_t)element;
log[0] = (field_logarithm_t)0; // really, it's undefined. we shouldn't ever access this
for (field_operation_t i = 1; i < 512; i++) {
element = element * 2;
element = (element > 255) ? (element ^ primitive_poly) : element;
exp[i] = (field_element_t)element;
if (i < 256) {
log[element] = (field_logarithm_t)i;
}
}
field_t field;
*(field_element_t **)&field.exp = exp;
*(field_logarithm_t **)&field.log = log;
return field;
}
static inline void field_destroy(field_t field) {
free(*(field_element_t **)&field.exp);
free(*(field_element_t **)&field.log);
}
static inline field_element_t field_add(field_t field, field_element_t l, field_element_t r) {
return l ^ r;
}
static inline field_element_t field_sub(field_t field, field_element_t l, field_element_t r) {
return l ^ r;
}
static inline field_element_t field_sum(field_t field, field_element_t elem, unsigned int n) {
// we'll do a closed-form expression of the sum, although we could also
// choose to call field_add n times
// since the sum is actually the bytewise XOR operator, this suggests two
// kinds of values: n odd, and n even
// if you sum once, you have coeff
// if you sum twice, you have coeff XOR coeff = 0
// if you sum thrice, you are back at coeff
// an even number of XORs puts you at 0
// an odd number of XORs puts you back at your value
// so, just throw away all the even n
return (n % 2) ? elem : 0;
}
static inline field_element_t field_mul(field_t field, field_element_t l, field_element_t r) {
if (l == 0 || r == 0) {
return 0;
}
// multiply two field elements by adding their logarithms.
// yep, get your slide rules out
field_operation_t res = (field_operation_t)field.log[l] + (field_operation_t)field.log[r];
// if coeff exceeds 255, we would normally have to wrap it back around
// alpha^255 = 1; alpha^256 = alpha^255 * alpha^1 = alpha^1
// however, we've constructed exponentiation table so that
// we can just directly lookup this result
// the result must be clamped to [0, 511]
// the greatest we can see at this step is alpha^255 * alpha^255
// = alpha^510
return field.exp[res];
}
static inline field_element_t field_div(field_t field, field_element_t l, field_element_t r) {
if (l == 0) {
return 0;
}
if (r == 0) {
// XXX ???
return 0;
}
// division as subtraction of logarithms
// if rcoeff is larger, then log[l] - log[r] wraps under
// so, instead, always add 255. in some cases, we'll wrap over, but
// that's ok because the exp table runs up to 511.
field_operation_t res = (field_operation_t)255 + (field_operation_t)field.log[l] - (field_operation_t)field.log[r];
return field.exp[res];
}
static inline field_logarithm_t field_mul_log(field_t field, field_logarithm_t l, field_logarithm_t r) {
// this function performs the equivalent of field_mul on two logarithms
// we save a little time by skipping the lookup step at the beginning
field_operation_t res = (field_operation_t)l + (field_operation_t)r;
// because we arent using the table, the value we return must be a valid logarithm
// which we have decided must live in [0, 255] (they are 8-bit values)
// ensuring this makes it so that multiple muls will not reach past the end of the
// exp table whenever we finally convert back to an element
if (res > 255) {
return (field_logarithm_t)(res - 255);
}
return (field_logarithm_t)res;
}
static inline field_logarithm_t field_div_log(field_t field, field_logarithm_t l, field_logarithm_t r) {
// like field_mul_log, this performs field_div without going through a field_element_t
field_operation_t res = (field_operation_t)255 + (field_operation_t)l - (field_operation_t)r;
if (res > 255) {
return (field_logarithm_t)(res - 255);
}
return (field_logarithm_t)res;
}
static inline field_element_t field_pow(field_t field, field_element_t elem, int pow) {
// take the logarithm, multiply, and then "exponentiate"
// n.b. the exp table only considers powers of alpha, the primitive element
// but here we have an arbitrary coeff
field_logarithm_t log = field.log[elem];
int res_log = log * pow;
int mod = res_log % 255;
if (mod < 0) {
mod += 255;
}
return field.exp[mod];
}
#endif

View File

@ -0,0 +1,14 @@
#include "correct/reed-solomon.h"
#include "correct/reed-solomon/field.h"
polynomial_t polynomial_create(unsigned int order);
void polynomial_destroy(polynomial_t polynomial);
void polynomial_mul(field_t field, polynomial_t l, polynomial_t r, polynomial_t res);
void polynomial_mod(field_t field, polynomial_t dividend, polynomial_t divisor, polynomial_t mod);
void polynomial_formal_derivative(field_t field, polynomial_t poly, polynomial_t der);
field_element_t polynomial_eval(field_t field, polynomial_t poly, field_element_t val);
field_element_t polynomial_eval_lut(field_t field, polynomial_t poly, const field_logarithm_t *val_exp);
field_element_t polynomial_eval_log_lut(field_t field, polynomial_t poly_log, const field_logarithm_t *val_exp);
void polynomial_build_exp_lut(field_t field, field_element_t val, unsigned int order, field_logarithm_t *val_exp);
polynomial_t polynomial_init_from_roots(field_t field, unsigned int nroots, field_element_t *roots, polynomial_t poly, polynomial_t *scratch);
polynomial_t polynomial_create_from_roots(field_t field, unsigned int nroots, field_element_t *roots);

View File

@ -0,0 +1,3 @@
#include "correct/reed-solomon.h"
#include "correct/reed-solomon/field.h"
#include "correct/reed-solomon/polynomial.h"

View File

@ -0,0 +1,8 @@
#include "correct/util/error-sim.h"
#include <fec.h>
void conv_fec27_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);
void conv_fec29_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);
void conv_fec39_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);
void conv_fec615_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);

View File

@ -0,0 +1,7 @@
#include "correct/util/error-sim.h"
#include "fec_shim.h"
ssize_t conv_shim27_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);
ssize_t conv_shim29_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);
ssize_t conv_shim39_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);
ssize_t conv_shim615_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);

View File

@ -0,0 +1,7 @@
#include "correct/util/error-sim.h"
#include "correct-sse.h"
size_t conv_correct_sse_enclen(void *conv_v, size_t msg_len);
void conv_correct_sse_encode(void *conv_v, uint8_t *msg, size_t msg_len, uint8_t *encoded);
ssize_t conv_correct_sse_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);

View File

@ -0,0 +1,47 @@
#include <stdbool.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <float.h>
#include <stdio.h>
#include "correct.h"
#include "correct/portable.h"
size_t distance(uint8_t *a, uint8_t *b, size_t len);
void gaussian(double *res, size_t n_res, double sigma);
void encode_bpsk(uint8_t *msg, double *voltages, size_t n_syms, double bpsk_voltage);
void byte2bit(uint8_t *bytes, uint8_t *bits, size_t n_bits);
void decode_bpsk(uint8_t *soft, uint8_t *msg, size_t n_syms);
void decode_bpsk_soft(double *voltages, uint8_t *soft, size_t n_syms, double bpsk_voltage);
double log2amp(double l);
double amp2log(double a);
double sigma_for_eb_n0(double eb_n0, double bpsk_bit_energy);
void build_white_noise(double *noise, size_t n_syms, double eb_n0, double bpsk_bit_energy);
void add_white_noise(double *signal, double *noise, size_t n_syms);
typedef struct {
uint8_t *msg_out;
size_t msg_len;
uint8_t *encoded;
double *v;
double *corrupted;
uint8_t *soft;
double *noise;
size_t enclen;
size_t enclen_bytes;
void (*encode)(void *, uint8_t *msg, size_t msg_len, uint8_t *encoded);
void *encoder;
ssize_t (*decode)(void *, uint8_t *soft, size_t soft_len, uint8_t *msg);
void *decoder;
} conv_testbench;
conv_testbench *resize_conv_testbench(conv_testbench *scratch, size_t (*enclen)(void *, size_t), void *enc, size_t msg_len);
void free_scratch(conv_testbench *scratch);
int test_conv_noise(conv_testbench *scratch, uint8_t *msg, size_t n_bytes,
double bpsk_voltage);
size_t conv_correct_enclen(void *conv_v, size_t msg_len);
void conv_correct_encode(void *conv_v, uint8_t *msg, size_t msg_len, uint8_t *encoded);
ssize_t conv_correct_decode(void *conv_v, uint8_t *soft, size_t soft_len, uint8_t *msg);

View File

@ -0,0 +1,255 @@
#include <stdlib.h>
#include <string.h>
#include "fec_shim.h"
typedef struct {
correct_reed_solomon *rs;
unsigned int msg_length;
unsigned int block_length;
unsigned int num_roots;
uint8_t *msg_out;
unsigned int pad;
uint8_t *erasures;
} reed_solomon_shim;
void *init_rs_char(int symbol_size, int primitive_polynomial,
int first_consecutive_root, int root_gap, int number_roots,
unsigned int pad) {
if (symbol_size != 8) {
return NULL;
}
reed_solomon_shim *shim = malloc(sizeof(reed_solomon_shim));
shim->pad = pad;
shim->block_length = 255 - pad;
shim->num_roots = number_roots;
shim->msg_length = shim->block_length - number_roots;
shim->rs = correct_reed_solomon_create(primitive_polynomial,
first_consecutive_root, root_gap, number_roots);
shim->msg_out = malloc(shim->block_length);
shim->erasures = malloc(number_roots);
return shim;
}
void free_rs_char(void *rs) {
reed_solomon_shim *shim = (reed_solomon_shim *)rs;
correct_reed_solomon_destroy(shim->rs);
free(shim->msg_out);
free(shim->erasures);
free(shim);
}
void encode_rs_char(void *rs, const unsigned char *msg, unsigned char *parity) {
reed_solomon_shim *shim = (reed_solomon_shim *)rs;
correct_reed_solomon_encode(shim->rs, msg, shim->msg_length, shim->msg_out);
memcpy(parity, shim->msg_out + shim->msg_length, shim->num_roots);
}
void decode_rs_char(void *rs, unsigned char *block, int *erasure_locations,
int num_erasures) {
reed_solomon_shim *shim = (reed_solomon_shim *)rs;
for (int i = 0; i < num_erasures; i++) {
shim->erasures[i] = (uint8_t)(erasure_locations[i]) - shim->pad;
}
correct_reed_solomon_decode_with_erasures(shim->rs, block, shim->block_length,
shim->erasures, num_erasures,
block);
}
typedef struct {
correct_convolutional *conv;
unsigned int rate;
unsigned int order;
uint8_t *buf;
size_t buf_len;
uint8_t *read_iter;
uint8_t *write_iter;
} convolutional_shim;
static correct_convolutional_polynomial_t r12k7[] = {V27POLYA, V27POLYB};
static correct_convolutional_polynomial_t r12k9[] = {V29POLYA, V29POLYB};
static correct_convolutional_polynomial_t r13k9[] = {V39POLYA, V39POLYB,
V39POLYC};
static correct_convolutional_polynomial_t r16k15[] = {
V615POLYA, V615POLYB, V615POLYC, V615POLYD, V615POLYE, V615POLYF};
/* Common methods */
static void *create_viterbi(unsigned int num_decoded_bits, unsigned int rate,
unsigned int order,
correct_convolutional_polynomial_t *poly) {
convolutional_shim *shim = malloc(sizeof(convolutional_shim));
size_t num_decoded_bytes = (num_decoded_bits % 8)
? (num_decoded_bits / 8 + 1)
: num_decoded_bits / 8;
shim->rate = rate;
shim->order = order;
shim->buf = malloc(num_decoded_bytes);
shim->buf_len = num_decoded_bytes;
shim->conv = correct_convolutional_create(rate, order, poly);
shim->read_iter = shim->buf;
shim->write_iter = shim->buf;
return shim;
}
static void delete_viterbi(void *vit) {
convolutional_shim *shim = (convolutional_shim *)vit;
free(shim->buf);
correct_convolutional_destroy(shim->conv);
free(shim);
}
static void init_viterbi(void *vit) {
convolutional_shim *shim = (convolutional_shim *)vit;
shim->read_iter = shim->buf;
shim->write_iter = shim->buf;
}
static void update_viterbi_blk(void *vit, const unsigned char *encoded_soft,
unsigned int num_encoded_groups) {
convolutional_shim *shim = (convolutional_shim *)vit;
// don't overwrite our buffer
size_t rem = (shim->buf + shim->buf_len) - shim->write_iter;
size_t rem_bits = 8 * rem;
// this math isn't very clear
// here we sort of do the opposite of what liquid-dsp does
size_t n_write_bits = num_encoded_groups - (shim->order - 1);
if (n_write_bits > rem_bits) {
size_t reduction = n_write_bits - rem_bits;
num_encoded_groups -= reduction;
n_write_bits -= reduction;
}
// what if n_write_bits isn't a multiple of 8?
// libcorrect can't start and stop at arbitrary indices...
correct_convolutional_decode_soft(
shim->conv, encoded_soft, num_encoded_groups * shim->rate, shim->write_iter);
shim->write_iter += n_write_bits / 8;
}
static void chainback_viterbi(void *vit, unsigned char *decoded,
unsigned int num_decoded_bits) {
convolutional_shim *shim = (convolutional_shim *)vit;
// num_decoded_bits not a multiple of 8?
// this is a similar problem to update_viterbi_blk
// although here we could actually resolve a non-multiple of 8
size_t rem = shim->write_iter - shim->read_iter;
size_t rem_bits = 8 * rem;
if (num_decoded_bits > rem_bits) {
num_decoded_bits = rem_bits;
}
size_t num_decoded_bytes = (num_decoded_bits % 8)
? (num_decoded_bits / 8 + 1)
: num_decoded_bits / 8;
memcpy(decoded, shim->read_iter, num_decoded_bytes);
shim->read_iter += num_decoded_bytes;
}
/* Rate 1/2, k = 7 */
void *create_viterbi27(int num_decoded_bits) {
return create_viterbi(num_decoded_bits, 2, 7, r12k7);
}
void delete_viterbi27(void *vit) { delete_viterbi(vit); }
int init_viterbi27(void *vit, int _) {
init_viterbi(vit);
return 0;
}
int update_viterbi27_blk(void *vit, unsigned char *encoded_soft,
int num_encoded_groups) {
update_viterbi_blk(vit, encoded_soft, num_encoded_groups);
return 0;
}
int chainback_viterbi27(void *vit, unsigned char *decoded,
unsigned int num_decoded_bits, unsigned int _) {
chainback_viterbi(vit, decoded, num_decoded_bits);
return 0;
}
/* Rate 1/2, k = 9 */
void *create_viterbi29(int num_decoded_bits) {
return create_viterbi(num_decoded_bits, 2, 9, r12k9);
}
void delete_viterbi29(void *vit) { delete_viterbi(vit); }
int init_viterbi29(void *vit, int _) {
init_viterbi(vit);
return 0;
}
int update_viterbi29_blk(void *vit, unsigned char *encoded_soft,
int num_encoded_groups) {
update_viterbi_blk(vit, encoded_soft, num_encoded_groups);
return 0;
}
int chainback_viterbi29(void *vit, unsigned char *decoded,
unsigned int num_decoded_bits, unsigned int _) {
chainback_viterbi(vit, decoded, num_decoded_bits);
return 0;
}
/* Rate 1/3, k = 9 */
void *create_viterbi39(int num_decoded_bits) {
return create_viterbi(num_decoded_bits, 3, 9, r13k9);
}
void delete_viterbi39(void *vit) { delete_viterbi(vit); }
int init_viterbi39(void *vit, int _) {
init_viterbi(vit);
return 0;
}
int update_viterbi39_blk(void *vit, unsigned char *encoded_soft,
int num_encoded_groups) {
update_viterbi_blk(vit, encoded_soft, num_encoded_groups);
return 0;
}
int chainback_viterbi39(void *vit, unsigned char *decoded,
unsigned int num_decoded_bits, unsigned int _) {
chainback_viterbi(vit, decoded, num_decoded_bits);
return 0;
}
/* Rate 1/6, k = 15 */
void *create_viterbi615(int num_decoded_bits) {
return create_viterbi(num_decoded_bits, 6, 15, r16k15);
}
void delete_viterbi615(void *vit) { delete_viterbi(vit); }
int init_viterbi615(void *vit, int _) {
init_viterbi(vit);
return 0;
}
int update_viterbi615_blk(void *vit, unsigned char *encoded_soft,
int num_encoded_groups) {
update_viterbi_blk(vit, encoded_soft, num_encoded_groups);
return 0;
}
int chainback_viterbi615(void *vit, unsigned char *decoded,
unsigned int num_decoded_bits, unsigned int _) {
chainback_viterbi(vit, decoded, num_decoded_bits);
return 0;
}

View File

@ -0,0 +1,74 @@
#ifndef CORRECT_FEC_H
#define CORRECT_FEC_H
// libcorrect's libfec shim header
// this is a partial implementation of libfec
// header signatures derived from found usages of libfec -- some things may be different
#include <correct.h>
// Reed-Solomon
void *init_rs_char(int symbol_size, int primitive_polynomial, int first_consecutive_root,
int root_gap, int number_roots, unsigned int pad);
void free_rs_char(void *rs);
void encode_rs_char(void *rs, const unsigned char *msg, unsigned char *parity);
void decode_rs_char(void *rs, unsigned char *block, int *erasure_locations, int num_erasures);
// Convolutional Codes
// Polynomials
// These have been determined via find_conv_libfec_poly.c
// We could just make up new ones, but we use libfec's here so that
// codes encoded by this library can be decoded by the original libfec
// and vice-versa
#define V27POLYA 0155
#define V27POLYB 0117
#define V29POLYA 0657
#define V29POLYB 0435
#define V39POLYA 0755
#define V39POLYB 0633
#define V39POLYC 0447
#define V615POLYA 042631
#define V615POLYB 047245
#define V615POLYC 056507
#define V615POLYD 073363
#define V615POLYE 077267
#define V615POLYF 064537
// Convolutional Methods
void *create_viterbi27(int num_decoded_bits);
int init_viterbi27(void *vit, int _mystery);
int update_viterbi27_blk(void *vit, unsigned char *encoded_soft, int n_encoded_groups);
int chainback_viterbi27(void *vit, unsigned char *decoded, unsigned int n_decoded_bits, unsigned int _mystery);
void delete_viterbi27(void *vit);
void *create_viterbi29(int num_decoded_bits);
int init_viterbi29(void *vit, int _mystery);
int update_viterbi29_blk(void *vit, unsigned char *encoded_soft, int n_encoded_groups);
int chainback_viterbi29(void *vit, unsigned char *decoded, unsigned int n_decoded_bits, unsigned int _mystery);
void delete_viterbi29(void *vit);
void *create_viterbi39(int num_decoded_bits);
int init_viterbi39(void *vit, int _mystery);
int update_viterbi39_blk(void *vit, unsigned char *encoded_soft, int n_encoded_groups);
int chainback_viterbi39(void *vit, unsigned char *decoded, unsigned int n_decoded_bits, unsigned int _mystery);
void delete_viterbi39(void *vit);
void *create_viterbi615(int num_decoded_bits);
int init_viterbi615(void *vit, int _mystery);
int update_viterbi615_blk(void *vit, unsigned char *encoded_soft, int n_encoded_groups);
int chainback_viterbi615(void *vit, unsigned char *decoded, unsigned int n_decoded_bits, unsigned int _mystery);
void delete_viterbi615(void *vit);
// Misc other
static inline int parity(unsigned int x) {
/* http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel */
x ^= x >> 16;
x ^= x >> 8;
x ^= x >> 4;
x &= 0xf;
return (0x6996 >> x) & 1;
}
#endif

View File

@ -0,0 +1,2 @@
set(SRCFILES polynomial.c reed-solomon.c encode.c decode.c)
add_library(correct-reed-solomon OBJECT ${SRCFILES})

View File

@ -0,0 +1,508 @@
#include "correct/reed-solomon/encode.h"
// calculate all syndromes of the received polynomial at the roots of the generator
// because we're evaluating at the roots of the generator, and because the transmitted
// polynomial was made to be a product of the generator, we know that the transmitted
// polynomial is 0 at these roots
// any nonzero syndromes we find here are the values of the error polynomial evaluated
// at these roots, so these values give us a window into the error polynomial. if
// these syndromes are all zero, then we can conclude the error polynomial is also
// zero. if they're nonzero, then we know our message received an error in transit.
// returns true if syndromes are all zero
static bool reed_solomon_find_syndromes(field_t field, polynomial_t msgpoly, field_logarithm_t **generator_root_exp,
field_element_t *syndromes, size_t min_distance) {
bool all_zero = true;
memset(syndromes, 0, min_distance * sizeof(field_element_t));
for (unsigned int i = 0; i < min_distance; i++) {
// profiling reveals that this function takes about 50% of the cpu time of
// decoding. so, in order to speed it up a little, we precompute and save
// the successive powers of the roots of the generator, which are
// located in generator_root_exp
field_element_t eval = polynomial_eval_lut(field, msgpoly, generator_root_exp[i]);
if (eval) {
all_zero = false;
}
syndromes[i] = eval;
}
return all_zero;
}
// Berlekamp-Massey algorithm to find LFSR that describes syndromes
// returns number of errors and writes the error locator polynomial to rs->error_locator
static unsigned int reed_solomon_find_error_locator(correct_reed_solomon *rs, size_t num_erasures) {
unsigned int numerrors = 0;
memset(rs->error_locator.coeff, 0, (rs->min_distance + 1) * sizeof(field_element_t));
// initialize to f(x) = 1
rs->error_locator.coeff[0] = 1;
rs->error_locator.order = 0;
memcpy(rs->last_error_locator.coeff, rs->error_locator.coeff, (rs->min_distance + 1) * sizeof(field_element_t));
rs->last_error_locator.order = rs->error_locator.order;
field_element_t discrepancy;
field_element_t last_discrepancy = 1;
unsigned int delay_length = 1;
for (unsigned int i = rs->error_locator.order; i < rs->min_distance - num_erasures; i++) {
discrepancy = rs->syndromes[i];
for (unsigned int j = 1; j <= numerrors; j++) {
discrepancy = field_add(rs->field, discrepancy,
field_mul(rs->field, rs->error_locator.coeff[j], rs->syndromes[i - j]));
}
if (!discrepancy) {
// our existing LFSR describes the new syndrome as well
// leave it as-is but update the number of delay elements
// so that if a discrepancy occurs later we can eliminate it
delay_length++;
continue;
}
if (2 * numerrors <= i) {
// there's a discrepancy, but we still have room for more taps
// lengthen LFSR by one tap and set weight to eliminate discrepancy
// shift the last locator by the delay length, multiply by discrepancy,
// and divide by the last discrepancy
// we move down because we're shifting up, and this prevents overwriting
for (int j = rs->last_error_locator.order; j >= 0; j--) {
// the bounds here will be ok since we have a headroom of numerrors
rs->last_error_locator.coeff[j + delay_length] = field_div(
rs->field, field_mul(rs->field, rs->last_error_locator.coeff[j], discrepancy), last_discrepancy);
}
for (int j = delay_length - 1; j >= 0; j--) {
rs->last_error_locator.coeff[j] = 0;
}
// locator = locator - last_locator
// we will also update last_locator to be locator before this loop takes place
field_element_t temp;
for (int j = 0; j <= (rs->last_error_locator.order + delay_length); j++) {
temp = rs->error_locator.coeff[j];
rs->error_locator.coeff[j] =
field_add(rs->field, rs->error_locator.coeff[j], rs->last_error_locator.coeff[j]);
rs->last_error_locator.coeff[j] = temp;
}
unsigned int temp_order = rs->error_locator.order;
rs->error_locator.order = rs->last_error_locator.order + delay_length;
rs->last_error_locator.order = temp_order;
// now last_locator is locator before we started,
// and locator is (locator - (discrepancy/last_discrepancy) * x^(delay_length) * last_locator)
numerrors = i + 1 - numerrors;
last_discrepancy = discrepancy;
delay_length = 1;
continue;
}
// no more taps
// unlike the previous case, we are preserving last locator,
// but we'll update locator as before
// we're basically flattening the two loops from the previous case because
// we no longer need to update last_locator
for (int j = rs->last_error_locator.order; j >= 0; j--) {
rs->error_locator.coeff[j + delay_length] =
field_add(rs->field, rs->error_locator.coeff[j + delay_length],
field_div(rs->field, field_mul(rs->field, rs->last_error_locator.coeff[j], discrepancy),
last_discrepancy));
}
rs->error_locator.order = (rs->last_error_locator.order + delay_length > rs->error_locator.order)
? rs->last_error_locator.order + delay_length
: rs->error_locator.order;
delay_length++;
}
return rs->error_locator.order;
}
// find the roots of the error locator polynomial
// Chien search
bool reed_solomon_factorize_error_locator(field_t field, unsigned int num_skip, polynomial_t locator_log, field_element_t *roots,
field_logarithm_t **element_exp) {
// normally it'd be tricky to find all the roots
// but, the finite field is awfully finite...
// just brute force search across every field element
unsigned int root = num_skip;
memset(roots + num_skip, 0, (locator_log.order) * sizeof(field_element_t));
for (field_operation_t i = 0; i < 256; i++) {
// we make two optimizations here to help this search go faster
// a) we have precomputed the first successive powers of every single element
// in the field. we need at most n powers, where n is the largest possible
// degree of the error locator
// b) we have precomputed the error locator polynomial in log form, which
// helps reduce some lookups that would be done here
if (!polynomial_eval_log_lut(field, locator_log, element_exp[i])) {
roots[root] = (field_element_t)i;
root++;
}
}
// this is where we find out if we are have too many errors to recover from
// berlekamp-massey may have built an error locator that has 0 discrepancy
// on the syndromes but doesn't have enough roots
return root == locator_log.order + num_skip;
}
// use error locator and syndromes to find the error evaluator polynomial
void reed_solomon_find_error_evaluator(field_t field, polynomial_t locator, polynomial_t syndromes,
polynomial_t error_evaluator) {
// the error evaluator, omega(x), is S(x)*Lamba(x) mod x^(2t)
// where S(x) is a polynomial constructed from the syndromes
// S(1) + S(2)*x + ... + S(2t)*x(2t - 1)
// and Lambda(x) is the error locator
// the modulo is implicit here -- we have limited the max length of error_evaluator,
// which polynomial_mul will interpret to mean that it should not compute
// powers larger than that, which is the same as performing mod x^(2t)
polynomial_mul(field, locator, syndromes, error_evaluator);
}
// use error locator, error roots and syndromes to find the error values
// that is, the elements in the finite field which can be added to the received
// polynomial at the locations of the error roots in order to produce the
// transmitted polynomial
// forney algorithm
void reed_solomon_find_error_values(correct_reed_solomon *rs) {
// error value e(j) = -(X(j)^(1-c) * omega(X(j)^-1))/(lambda'(X(j)^-1))
// where X(j)^-1 is a root of the error locator, omega(X) is the error evaluator,
// lambda'(X) is the first formal derivative of the error locator,
// and c is the first consecutive root of the generator used in encoding
// first find omega(X), the error evaluator
// we generate S(x), the polynomial constructed from the roots of the syndromes
// this is *not* the polynomial constructed by expanding the products of roots
// S(x) = S(1) + S(2)*x + ... + S(2t)*x(2t - 1)
polynomial_t syndrome_poly;
syndrome_poly.order = rs->min_distance - 1;
syndrome_poly.coeff = rs->syndromes;
memset(rs->error_evaluator.coeff, 0, (rs->error_evaluator.order + 1) * sizeof(field_element_t));
reed_solomon_find_error_evaluator(rs->field, rs->error_locator, syndrome_poly, rs->error_evaluator);
// now find lambda'(X)
rs->error_locator_derivative.order = rs->error_locator.order - 1;
polynomial_formal_derivative(rs->field, rs->error_locator, rs->error_locator_derivative);
// calculate each e(j)
for (unsigned int i = 0; i < rs->error_locator.order; i++) {
if (rs->error_roots[i] == 0) {
continue;
}
rs->error_vals[i] = field_mul(
rs->field, field_pow(rs->field, rs->error_roots[i], rs->first_consecutive_root - 1),
field_div(
rs->field, polynomial_eval_lut(rs->field, rs->error_evaluator, rs->element_exp[rs->error_roots[i]]),
polynomial_eval_lut(rs->field, rs->error_locator_derivative, rs->element_exp[rs->error_roots[i]])));
}
}
void reed_solomon_find_error_locations(field_t field, field_logarithm_t generator_root_gap,
field_element_t *error_roots, field_logarithm_t *error_locations,
unsigned int num_errors, unsigned int num_skip) {
for (unsigned int i = 0; i < num_errors; i++) {
// the error roots are the reciprocals of the error locations, so div 1 by them
// we do mod 255 here because the log table aliases at index 1
// the log of 1 is both 0 and 255 (alpha^255 = alpha^0 = 1)
// for most uses it makes sense to have log(1) = 255, but in this case
// we're interested in a byte index, and the 255th index is not even valid
// just wrap it back to 0
if (error_roots[i] == 0) {
continue;
}
field_operation_t loc = field_div(field, 1, error_roots[i]);
for (field_operation_t j = 0; j < 256; j++) {
if (field_pow(field, j, generator_root_gap) == loc) {
error_locations[i] = field.log[j];
break;
}
}
}
}
// erasure method -- take given locations and convert to roots
// this is the inverse of reed_solomon_find_error_locations
static void reed_solomon_find_error_roots_from_locations(field_t field, field_logarithm_t generator_root_gap,
const field_logarithm_t *error_locations,
field_element_t *error_roots, unsigned int num_errors) {
for (unsigned int i = 0; i < num_errors; i++) {
field_element_t loc = field_pow(field, field.exp[error_locations[i]], generator_root_gap);
// field_element_t loc = field.exp[error_locations[i]];
error_roots[i] = field_div(field, 1, loc);
// error_roots[i] = loc;
}
}
// erasure method -- given the roots of the error locator, create the polynomial
static polynomial_t reed_solomon_find_error_locator_from_roots(field_t field, unsigned int num_errors,
field_element_t *error_roots,
polynomial_t error_locator,
polynomial_t *scratch) {
// multiply out roots to build the error locator polynomial
return polynomial_init_from_roots(field, num_errors, error_roots, error_locator, scratch);
}
// erasure method
static void reed_solomon_find_modified_syndromes(correct_reed_solomon *rs, field_element_t *syndromes, polynomial_t error_locator, field_element_t *modified_syndromes) {
polynomial_t syndrome_poly;
syndrome_poly.order = rs->min_distance - 1;
syndrome_poly.coeff = syndromes;
polynomial_t modified_syndrome_poly;
modified_syndrome_poly.order = rs->min_distance - 1;
modified_syndrome_poly.coeff = modified_syndromes;
polynomial_mul(rs->field, error_locator, syndrome_poly, modified_syndrome_poly);
}
void correct_reed_solomon_decoder_create(correct_reed_solomon *rs) {
rs->has_init_decode = true;
rs->syndromes = calloc(rs->min_distance, sizeof(field_element_t));
rs->modified_syndromes = calloc(2 * rs->min_distance, sizeof(field_element_t));
rs->received_polynomial = polynomial_create(rs->block_length - 1);
rs->error_locator = polynomial_create(rs->min_distance);
rs->error_locator_log = polynomial_create(rs->min_distance);
rs->erasure_locator = polynomial_create(rs->min_distance);
rs->error_roots = calloc(2 * rs->min_distance, sizeof(field_element_t));
rs->error_vals = malloc(rs->min_distance * sizeof(field_element_t));
rs->error_locations = malloc(rs->min_distance * sizeof(field_logarithm_t));
rs->last_error_locator = polynomial_create(rs->min_distance);
rs->error_evaluator = polynomial_create(rs->min_distance - 1);
rs->error_locator_derivative = polynomial_create(rs->min_distance - 1);
// calculate and store the first block_length powers of every generator root
// we would have to do this work in order to calculate the syndromes
// if we save it, we can prevent the need to recalculate it on subsequent calls
// total memory usage is min_distance * block_length bytes e.g. 32 * 255 ~= 8k
rs->generator_root_exp = malloc(rs->min_distance * sizeof(field_logarithm_t *));
for (unsigned int i = 0; i < rs->min_distance; i++) {
rs->generator_root_exp[i] = malloc(rs->block_length * sizeof(field_logarithm_t));
polynomial_build_exp_lut(rs->field, rs->generator_roots[i], rs->block_length - 1, rs->generator_root_exp[i]);
}
// calculate and store the first min_distance powers of every element in the field
// we would have to do this for chien search anyway, and its size is only 256 * min_distance bytes
// for min_distance = 32 this is 8k of memory, a pittance for the speedup we receive in exchange
// we also get to reuse this work during error value calculation
rs->element_exp = malloc(256 * sizeof(field_logarithm_t *));
for (field_operation_t i = 0; i < 256; i++) {
rs->element_exp[i] = malloc(rs->min_distance * sizeof(field_logarithm_t));
polynomial_build_exp_lut(rs->field, i, rs->min_distance - 1, rs->element_exp[i]);
}
rs->init_from_roots_scratch[0] = polynomial_create(rs->min_distance);
rs->init_from_roots_scratch[1] = polynomial_create(rs->min_distance);
}
ssize_t correct_reed_solomon_decode(correct_reed_solomon *rs, const uint8_t *encoded, size_t encoded_length,
uint8_t *msg) {
if (encoded_length > rs->block_length) {
return -1;
}
// the message is the non-remainder part
size_t msg_length = encoded_length - rs->min_distance;
// if they handed us a nonfull block, we'll write in 0s
size_t pad_length = rs->block_length - encoded_length;
if (!rs->has_init_decode) {
// initialize rs for decoding
correct_reed_solomon_decoder_create(rs);
}
// we need to copy to our local buffer
// the buffer we're given has the coordinates in the wrong direction
// e.g. byte 0 corresponds to the 254th order coefficient
// so we're going to flip and then write padding
// the final copied buffer will look like
// | rem (rs->min_distance) | msg (msg_length) | pad (pad_length) |
for (unsigned int i = 0; i < encoded_length; i++) {
rs->received_polynomial.coeff[i] = encoded[encoded_length - (i + 1)];
}
// fill the pad_length with 0s
for (unsigned int i = 0; i < pad_length; i++) {
rs->received_polynomial.coeff[i + encoded_length] = 0;
}
bool all_zero = reed_solomon_find_syndromes(rs->field, rs->received_polynomial, rs->generator_root_exp,
rs->syndromes, rs->min_distance);
if (all_zero) {
// syndromes were all zero, so there was no error in the message
// copy to msg and we are done
for (unsigned int i = 0; i < msg_length; i++) {
msg[i] = rs->received_polynomial.coeff[encoded_length - (i + 1)];
}
return msg_length;
}
unsigned int order = reed_solomon_find_error_locator(rs, 0);
// XXX fix this vvvv
rs->error_locator.order = order;
for (unsigned int i = 0; i <= rs->error_locator.order; i++) {
// this is a little strange since the coeffs are logs, not elements
// also, we'll be storing log(0) = 0 for any 0 coeffs in the error locator
// that would seem bad but we'll just be using this in chien search, and we'll skip all 0 coeffs
// (you might point out that log(1) also = 0, which would seem to alias. however, that's ok,
// because log(1) = 255 as well, and in fact that's how it's represented in our log table)
rs->error_locator_log.coeff[i] = rs->field.log[rs->error_locator.coeff[i]];
}
rs->error_locator_log.order = rs->error_locator.order;
if (!reed_solomon_factorize_error_locator(rs->field, 0, rs->error_locator_log, rs->error_roots, rs->element_exp)) {
// roots couldn't be found, so there were too many errors to deal with
// RS has failed for this message
return -1;
}
reed_solomon_find_error_locations(rs->field, rs->generator_root_gap, rs->error_roots, rs->error_locations,
rs->error_locator.order, 0);
reed_solomon_find_error_values(rs);
for (unsigned int i = 0; i < rs->error_locator.order; i++) {
rs->received_polynomial.coeff[rs->error_locations[i]] =
field_sub(rs->field, rs->received_polynomial.coeff[rs->error_locations[i]], rs->error_vals[i]);
}
for (unsigned int i = 0; i < msg_length; i++) {
msg[i] = rs->received_polynomial.coeff[encoded_length - (i + 1)];
}
return msg_length;
}
ssize_t correct_reed_solomon_decode_with_erasures(correct_reed_solomon *rs, const uint8_t *encoded,
size_t encoded_length, const uint8_t *erasure_locations,
size_t erasure_length, uint8_t *msg) {
if (!erasure_length) {
return correct_reed_solomon_decode(rs, encoded, encoded_length, msg);
}
if (encoded_length > rs->block_length) {
return -1;
}
if (erasure_length > rs->min_distance) {
return -1;
}
// the message is the non-remainder part
size_t msg_length = encoded_length - rs->min_distance;
// if they handed us a nonfull block, we'll write in 0s
size_t pad_length = rs->block_length - encoded_length;
if (!rs->has_init_decode) {
// initialize rs for decoding
correct_reed_solomon_decoder_create(rs);
}
// we need to copy to our local buffer
// the buffer we're given has the coordinates in the wrong direction
// e.g. byte 0 corresponds to the 254th order coefficient
// so we're going to flip and then write padding
// the final copied buffer will look like
// | rem (rs->min_distance) | msg (msg_length) | pad (pad_length) |
for (unsigned int i = 0; i < encoded_length; i++) {
rs->received_polynomial.coeff[i] = encoded[encoded_length - (i + 1)];
}
// fill the pad_length with 0s
for (unsigned int i = 0; i < pad_length; i++) {
rs->received_polynomial.coeff[i + encoded_length] = 0;
}
for (unsigned int i = 0; i < erasure_length; i++) {
// remap the coordinates of the erasures
rs->error_locations[i] = rs->block_length - (erasure_locations[i] + pad_length + 1);
}
reed_solomon_find_error_roots_from_locations(rs->field, rs->generator_root_gap, rs->error_locations,
rs->error_roots, erasure_length);
rs->erasure_locator =
reed_solomon_find_error_locator_from_roots(rs->field, erasure_length, rs->error_roots, rs->erasure_locator, rs->init_from_roots_scratch);
bool all_zero = reed_solomon_find_syndromes(rs->field, rs->received_polynomial, rs->generator_root_exp,
rs->syndromes, rs->min_distance);
if (all_zero) {
// syndromes were all zero, so there was no error in the message
// copy to msg and we are done
for (unsigned int i = 0; i < msg_length; i++) {
msg[i] = rs->received_polynomial.coeff[encoded_length - (i + 1)];
}
return msg_length;
}
reed_solomon_find_modified_syndromes(rs, rs->syndromes, rs->erasure_locator, rs->modified_syndromes);
field_element_t *syndrome_copy = malloc(rs->min_distance * sizeof(field_element_t));
memcpy(syndrome_copy, rs->syndromes, rs->min_distance * sizeof(field_element_t));
for (unsigned int i = erasure_length; i < rs->min_distance; i++) {
rs->syndromes[i - erasure_length] = rs->modified_syndromes[i];
}
unsigned int order = reed_solomon_find_error_locator(rs, erasure_length);
// XXX fix this vvvv
rs->error_locator.order = order;
for (unsigned int i = 0; i <= rs->error_locator.order; i++) {
// this is a little strange since the coeffs are logs, not elements
// also, we'll be storing log(0) = 0 for any 0 coeffs in the error locator
// that would seem bad but we'll just be using this in chien search, and we'll skip all 0 coeffs
// (you might point out that log(1) also = 0, which would seem to alias. however, that's ok,
// because log(1) = 255 as well, and in fact that's how it's represented in our log table)
rs->error_locator_log.coeff[i] = rs->field.log[rs->error_locator.coeff[i]];
}
rs->error_locator_log.order = rs->error_locator.order;
/*
for (unsigned int i = 0; i < erasure_length; i++) {
rs->error_roots[i] = field_div(rs->field, 1, rs->error_roots[i]);
}
*/
if (!reed_solomon_factorize_error_locator(rs->field, erasure_length, rs->error_locator_log, rs->error_roots, rs->element_exp)) {
// roots couldn't be found, so there were too many errors to deal with
// RS has failed for this message
free(syndrome_copy);
return -1;
}
polynomial_t temp_poly = polynomial_create(rs->error_locator.order + erasure_length);
polynomial_mul(rs->field, rs->erasure_locator, rs->error_locator, temp_poly);
polynomial_t placeholder_poly = rs->error_locator;
rs->error_locator = temp_poly;
reed_solomon_find_error_locations(rs->field, rs->generator_root_gap, rs->error_roots, rs->error_locations,
rs->error_locator.order, erasure_length);
memcpy(rs->syndromes, syndrome_copy, rs->min_distance * sizeof(field_element_t));
reed_solomon_find_error_values(rs);
for (unsigned int i = 0; i < rs->error_locator.order; i++) {
rs->received_polynomial.coeff[rs->error_locations[i]] =
field_sub(rs->field, rs->received_polynomial.coeff[rs->error_locations[i]], rs->error_vals[i]);
}
rs->error_locator = placeholder_poly;
for (unsigned int i = 0; i < msg_length; i++) {
msg[i] = rs->received_polynomial.coeff[encoded_length - (i + 1)];
}
polynomial_destroy(temp_poly);
free(syndrome_copy);
return msg_length;
}

View File

@ -0,0 +1,34 @@
#include "correct/reed-solomon/encode.h"
ssize_t correct_reed_solomon_encode(correct_reed_solomon *rs, const uint8_t *msg, size_t msg_length, uint8_t *encoded) {
if (msg_length > rs->message_length) {
return -1;
}
size_t pad_length = rs->message_length - msg_length;
for (unsigned int i = 0; i < msg_length; i++) {
// message goes from high order to low order but libcorrect polynomials go low to high
// so we reverse on the way in and on the way out
// we'd have to do a copy anyway so this reversal should be free
rs->encoded_polynomial.coeff[rs->encoded_polynomial.order - (i + pad_length)] = msg[i];
}
// 0-fill the rest of the coefficients -- this length will always be > 0
// because the order of this poly is block_length and the msg_length <= message_length
// e.g. 255 and 223
memset(rs->encoded_polynomial.coeff + (rs->encoded_polynomial.order + 1 - pad_length), 0, pad_length);
memset(rs->encoded_polynomial.coeff, 0, (rs->encoded_polynomial.order + 1 - rs->message_length));
polynomial_mod(rs->field, rs->encoded_polynomial, rs->generator, rs->encoded_remainder);
// now return byte order to highest order to lowest order
for (unsigned int i = 0; i < msg_length; i++) {
encoded[i] = rs->encoded_polynomial.coeff[rs->encoded_polynomial.order - (i + pad_length)];
}
for (unsigned int i = 0; i < rs->min_distance; i++) {
encoded[msg_length + i] = rs->encoded_remainder.coeff[rs->min_distance - (i + 1)];
}
return rs->block_length;
}

View File

@ -0,0 +1,255 @@
#include "correct/reed-solomon/polynomial.h"
polynomial_t polynomial_create(unsigned int order) {
polynomial_t polynomial;
polynomial.coeff = malloc(sizeof(field_element_t) * (order + 1));
polynomial.order = order;
return polynomial;
}
void polynomial_destroy(polynomial_t polynomial) {
free(polynomial.coeff);
}
// if you want a full multiplication, then make res.order = l.order + r.order
// but if you just care about a lower order, e.g. mul mod x^i, then you can select
// fewer coefficients
void polynomial_mul(field_t field, polynomial_t l, polynomial_t r, polynomial_t res) {
// perform an element-wise multiplication of two polynomials
memset(res.coeff, 0, sizeof(field_element_t) * (res.order + 1));
for (unsigned int i = 0; i <= l.order; i++) {
if (i > res.order) {
continue;
}
unsigned int j_limit = (r.order > res.order - i) ? res.order - i : r.order;
for (unsigned int j = 0; j <= j_limit; j++) {
// e.g. alpha^5*x * alpha^37*x^2 --> alpha^42*x^3
res.coeff[i + j] = field_add(field, res.coeff[i + j], field_mul(field, l.coeff[i], r.coeff[j]));
}
}
}
void polynomial_mod(field_t field, polynomial_t dividend, polynomial_t divisor, polynomial_t mod) {
// find the polynomial remainder of dividend mod divisor
// do long division and return just the remainder (written to mod)
if (mod.order < dividend.order) {
// mod.order must be >= dividend.order (scratch space needed)
// this is an error -- catch it in debug?
return;
}
// initialize remainder as dividend
memcpy(mod.coeff, dividend.coeff, sizeof(field_element_t) * (dividend.order + 1));
// XXX make sure divisor[divisor_order] is nonzero
field_logarithm_t divisor_leading = field.log[divisor.coeff[divisor.order]];
// long division steps along one order at a time, starting at the highest order
for (unsigned int i = dividend.order; i > 0; i--) {
// look at the leading coefficient of dividend and divisor
// if leading coefficient of dividend / leading coefficient of divisor is q
// then the next row of subtraction will be q * divisor
// if order of q < 0 then what we have is the remainder and we are done
if (i < divisor.order) {
break;
}
if (mod.coeff[i] == 0) {
continue;
}
unsigned int q_order = i - divisor.order;
field_logarithm_t q_coeff = field_div_log(field, field.log[mod.coeff[i]], divisor_leading);
// now that we've chosen q, multiply the divisor by q and subtract from
// our remainder. subtracting in GF(2^8) is XOR, just like addition
for (unsigned int j = 0; j <= divisor.order; j++) {
if (divisor.coeff[j] == 0) {
continue;
}
// all of the multiplication is shifted up by q_order places
mod.coeff[j + q_order] = field_add(field, mod.coeff[j + q_order],
field_mul_log_element(field, field.log[divisor.coeff[j]], q_coeff));
}
}
}
void polynomial_formal_derivative(field_t field, polynomial_t poly, polynomial_t der) {
// if f(x) = a(n)*x^n + ... + a(1)*x + a(0)
// then f'(x) = n*a(n)*x^(n-1) + ... + 2*a(2)*x + a(1)
// where n*a(n) = sum(k=1, n, a(n)) e.g. the nth sum of a(n) in GF(2^8)
// assumes der.order = poly.order - 1
memset(der.coeff, 0, sizeof(field_element_t) * (der.order + 1));
for (unsigned int i = 0; i <= der.order; i++) {
// we're filling in the ith power of der, so we look ahead one power in poly
// f(x) = a(i + 1)*x^(i + 1) -> f'(x) = (i + 1)*a(i + 1)*x^i
// where (i + 1)*a(i + 1) is the sum of a(i + 1) (i + 1) times, not the product
der.coeff[i] = field_sum(field, poly.coeff[i + 1], i + 1);
}
}
field_element_t polynomial_eval(field_t field, polynomial_t poly, field_element_t val) {
// evaluate the polynomial poly at a particular element val
if (val == 0) {
return poly.coeff[0];
}
field_element_t res = 0;
// we're going to start at 0th order and multiply by val each time
field_logarithm_t val_exponentiated = field.log[1];
field_logarithm_t val_log = field.log[val];
for (unsigned int i = 0; i <= poly.order; i++) {
if (poly.coeff[i] != 0) {
// multiply-accumulate by the next coeff times the next power of val
res = field_add(field, res,
field_mul_log_element(field, field.log[poly.coeff[i]], val_exponentiated));
}
// now advance to the next power
val_exponentiated = field_mul_log(field, val_exponentiated, val_log);
}
return res;
}
field_element_t polynomial_eval_lut(field_t field, polynomial_t poly, const field_logarithm_t *val_exp) {
// evaluate the polynomial poly at a particular element val
// in this case, all of the logarithms of the successive powers of val have been precalculated
// this removes the extra work we'd have to do to calculate val_exponentiated each time
// if this function is to be called on the same val multiple times
if (val_exp[0] == 0) {
return poly.coeff[0];
}
field_element_t res = 0;
for (unsigned int i = 0; i <= poly.order; i++) {
if (poly.coeff[i] != 0) {
// multiply-accumulate by the next coeff times the next power of val
res = field_add(field, res,
field_mul_log_element(field, field.log[poly.coeff[i]], val_exp[i]));
}
}
return res;
}
field_element_t polynomial_eval_log_lut(field_t field, polynomial_t poly_log, const field_logarithm_t *val_exp) {
// evaluate the log_polynomial poly at a particular element val
// like polynomial_eval_lut, the logarithms of the successive powers of val have been
// precomputed
if (val_exp[0] == 0) {
if (poly_log.coeff[0] == 0) {
// special case for the non-existant log case
return 0;
}
return field.exp[poly_log.coeff[0]];
}
field_element_t res = 0;
for (unsigned int i = 0; i <= poly_log.order; i++) {
// using 0 as a sentinel value in log -- log(0) is really -inf
if (poly_log.coeff[i] != 0) {
// multiply-accumulate by the next coeff times the next power of val
res = field_add(field, res,
field_mul_log_element(field, poly_log.coeff[i], val_exp[i]));
}
}
return res;
}
void polynomial_build_exp_lut(field_t field, field_element_t val, unsigned int order, field_logarithm_t *val_exp) {
// create the lookup table of successive powers of val used by polynomial_eval_lut
field_logarithm_t val_exponentiated = field.log[1];
field_logarithm_t val_log = field.log[val];
for (unsigned int i = 0; i <= order; i++) {
if (val == 0) {
val_exp[i] = 0;
} else {
val_exp[i] = val_exponentiated;
val_exponentiated = field_mul_log(field, val_exponentiated, val_log);
}
}
}
polynomial_t polynomial_init_from_roots(field_t field, unsigned int nroots, field_element_t *roots, polynomial_t poly, polynomial_t *scratch) {
unsigned int order = nroots;
polynomial_t l;
field_element_t l_coeff[2];
l.order = 1;
l.coeff = l_coeff;
// we'll keep two temporary stores of rightside polynomial
// each time through the loop, we take the previous result and use it as new rightside
// swap back and forth (prevents the need for a copy)
polynomial_t r[2];
r[0] = scratch[0];
r[1] = scratch[1];
unsigned int rcoeffres = 0;
// initialize the result with x + roots[0]
r[rcoeffres].coeff[1] = 1;
r[rcoeffres].coeff[0] = roots[0];
r[rcoeffres].order = 1;
// initialize lcoeff[1] with x
// we'll fill in the 0th order term in each loop iter
l.coeff[1] = 1;
// loop through, using previous run's result as the new right hand side
// this allows us to multiply one group at a time
for (unsigned int i = 1; i < nroots; i++) {
l.coeff[0] = roots[i];
unsigned int nextrcoeff = rcoeffres;
rcoeffres = (rcoeffres + 1) % 2;
r[rcoeffres].order = i + 1;
polynomial_mul(field, l, r[nextrcoeff], r[rcoeffres]);
}
memcpy(poly.coeff, r[rcoeffres].coeff, (order + 1) * sizeof(field_element_t));
poly.order = order;
return poly;
}
polynomial_t polynomial_create_from_roots(field_t field, unsigned int nroots, field_element_t *roots) {
polynomial_t poly = polynomial_create(nroots);
unsigned int order = nroots;
polynomial_t l;
l.order = 1;
l.coeff = calloc(2, sizeof(field_element_t));
polynomial_t r[2];
// we'll keep two temporary stores of rightside polynomial
// each time through the loop, we take the previous result and use it as new rightside
// swap back and forth (prevents the need for a copy)
r[0].coeff = calloc(order + 1, sizeof(field_element_t));
r[1].coeff = calloc(order + 1, sizeof(field_element_t));
unsigned int rcoeffres = 0;
// initialize the result with x + roots[0]
r[rcoeffres].coeff[0] = roots[0];
r[rcoeffres].coeff[1] = 1;
r[rcoeffres].order = 1;
// initialize lcoeff[1] with x
// we'll fill in the 0th order term in each loop iter
l.coeff[1] = 1;
// loop through, using previous run's result as the new right hand side
// this allows us to multiply one group at a time
for (unsigned int i = 1; i < nroots; i++) {
l.coeff[0] = roots[i];
unsigned int nextrcoeff = rcoeffres;
rcoeffres = (rcoeffres + 1) % 2;
r[rcoeffres].order = i + 1;
polynomial_mul(field, l, r[nextrcoeff], r[rcoeffres]);
}
memcpy(poly.coeff, r[rcoeffres].coeff, (order + 1) * sizeof(field_element_t));
poly.order = order;
free(l.coeff);
free(r[0].coeff);
free(r[1].coeff);
return poly;
}

View File

@ -0,0 +1,187 @@
#include "correct/reed-solomon/reed-solomon.h"
// coeff must be of size nroots + 1
// e.g. 2 roots (x + alpha)(x + alpha^2) yields a poly with 3 terms x^2 + g0*x + g1
static polynomial_t reed_solomon_build_generator(field_t field, unsigned int nroots, field_element_t first_consecutive_root, unsigned int root_gap, polynomial_t generator, field_element_t *roots) {
// generator has order 2*t
// of form (x + alpha^1)(x + alpha^2)...(x - alpha^2*t)
for (unsigned int i = 0; i < nroots; i++) {
roots[i] = field.exp[(root_gap * (i + first_consecutive_root)) % 255];
}
return polynomial_create_from_roots(field, nroots, roots);
}
correct_reed_solomon *correct_reed_solomon_create(field_operation_t primitive_polynomial, field_logarithm_t first_consecutive_root, field_logarithm_t generator_root_gap, size_t num_roots) {
correct_reed_solomon *rs = calloc(1, sizeof(correct_reed_solomon));
rs->field = field_create(primitive_polynomial);
rs->block_length = 255;
rs->min_distance = num_roots;
rs->message_length = rs->block_length - rs->min_distance;
rs->first_consecutive_root = first_consecutive_root;
rs->generator_root_gap = generator_root_gap;
rs->generator_roots = malloc(rs->min_distance * sizeof(field_element_t));
rs->generator = reed_solomon_build_generator(rs->field, rs->min_distance, rs->first_consecutive_root, rs->generator_root_gap, rs->generator, rs->generator_roots);
rs->encoded_polynomial = polynomial_create(rs->block_length - 1);
rs->encoded_remainder = polynomial_create(rs->block_length - 1);
rs->has_init_decode = false;
return rs;
}
void correct_reed_solomon_destroy(correct_reed_solomon *rs) {
field_destroy(rs->field);
polynomial_destroy(rs->generator);
free(rs->generator_roots);
polynomial_destroy(rs->encoded_polynomial);
polynomial_destroy(rs->encoded_remainder);
if (rs->has_init_decode) {
free(rs->syndromes);
free(rs->modified_syndromes);
polynomial_destroy(rs->received_polynomial);
polynomial_destroy(rs->error_locator);
polynomial_destroy(rs->error_locator_log);
polynomial_destroy(rs->erasure_locator);
free(rs->error_roots);
free(rs->error_vals);
free(rs->error_locations);
polynomial_destroy(rs->last_error_locator);
polynomial_destroy(rs->error_evaluator);
polynomial_destroy(rs->error_locator_derivative);
for (unsigned int i = 0; i < rs->min_distance; i++) {
free(rs->generator_root_exp[i]);
}
free(rs->generator_root_exp);
for (field_operation_t i = 0; i < 256; i++) {
free(rs->element_exp[i]);
}
free(rs->element_exp);
polynomial_destroy(rs->init_from_roots_scratch[0]);
polynomial_destroy(rs->init_from_roots_scratch[1]);
}
free(rs);
}
void correct_reed_solomon_debug_print(correct_reed_solomon *rs) {
for (unsigned int i = 0; i < 256; i++) {
printf("%3d %3d %3d %3d\n", i, rs->field.exp[i], i, rs->field.log[i]);
}
printf("\n");
printf("roots: ");
for (unsigned int i = 0; i < rs->min_distance; i++) {
printf("%d", rs->generator_roots[i]);
if (i < rs->min_distance - 1) {
printf(", ");
}
}
printf("\n\n");
printf("generator: ");
for (unsigned int i = 0; i < rs->generator.order + 1; i++) {
printf("%d*x^%d", rs->generator.coeff[i], i);
if (i < rs->generator.order) {
printf(" + ");
}
}
printf("\n\n");
printf("generator (alpha format): ");
for (unsigned int i = rs->generator.order + 1; i > 0; i--) {
printf("alpha^%d*x^%d", rs->field.log[rs->generator.coeff[i - 1]], i - 1);
if (i > 1) {
printf(" + ");
}
}
printf("\n\n");
printf("remainder: ");
bool has_printed = false;
for (unsigned int i = 0; i < rs->encoded_remainder.order + 1; i++) {
if (!rs->encoded_remainder.coeff[i]) {
continue;
}
if (has_printed) {
printf(" + ");
}
has_printed = true;
printf("%d*x^%d", rs->encoded_remainder.coeff[i], i);
}
printf("\n\n");
printf("syndromes: ");
for (unsigned int i = 0; i < rs->min_distance; i++) {
printf("%d", rs->syndromes[i]);
if (i < rs->min_distance - 1) {
printf(", ");
}
}
printf("\n\n");
printf("numerrors: %d\n\n", rs->error_locator.order);
printf("error locator: ");
has_printed = false;
for (unsigned int i = 0; i < rs->error_locator.order + 1; i++) {
if (!rs->error_locator.coeff[i]) {
continue;
}
if (has_printed) {
printf(" + ");
}
has_printed = true;
printf("%d*x^%d", rs->error_locator.coeff[i], i);
}
printf("\n\n");
printf("error roots: ");
for (unsigned int i = 0; i < rs->error_locator.order; i++) {
printf("%d@%d", polynomial_eval(rs->field, rs->error_locator, rs->error_roots[i]), rs->error_roots[i]);
if (i < rs->error_locator.order - 1) {
printf(", ");
}
}
printf("\n\n");
printf("error evaluator: ");
has_printed = false;
for (unsigned int i = 0; i < rs->error_evaluator.order; i++) {
if (!rs->error_evaluator.coeff[i]) {
continue;
}
if (has_printed) {
printf(" + ");
}
has_printed = true;
printf("%d*x^%d", rs->error_evaluator.coeff[i], i);
}
printf("\n\n");
printf("error locator derivative: ");
has_printed = false;
for (unsigned int i = 0; i < rs->error_locator_derivative.order; i++) {
if (!rs->error_locator_derivative.coeff[i]) {
continue;
}
if (has_printed) {
printf(" + ");
}
has_printed = true;
printf("%d*x^%d", rs->error_locator_derivative.coeff[i], i);
}
printf("\n\n");
printf("error locator: ");
for (unsigned int i = 0; i < rs->error_locator.order; i++) {
printf("%d@%d", rs->error_vals[i], rs->error_locations[i]);
if (i < rs->error_locator.order - 1) {
printf(", ");
}
}
printf("\n\n");
}

View File

@ -0,0 +1,263 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <gui/gui.h>
#include <dsp/pll.h>
#include <dsp/stream.h>
#include <dsp/demodulator.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <dsp/routing.h>
#include <dsp/deframing.h>
#include <falcon_fec.h>
#include <falcon_packet.h>
#include <dsp/sink.h>
#include <gui/widgets/symbol_diagram.h>
#include <fstream>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "falcon9_decoder",
/* Description: */ "Falcon9 telemetry decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
#define INPUT_SAMPLE_RATE 6000000
std::ofstream file("output.ts");
class Falcon9DecoderModule : public ModuleManager::Instance {
public:
Falcon9DecoderModule(std::string name) {
this->name = name;
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 4000000, INPUT_SAMPLE_RATE, 4000000, 4000000, true);
// dsp::Splitter<float> split;
// dsp::Reshaper<float> reshape;
// dsp::HandlerSink<float> symSink;
// dsp::stream<float> thrInput;
// dsp::Threshold thr;
demod.init(vfo->output, INPUT_SAMPLE_RATE, 2000000.0f);
recov.init(&demod.out, (float)INPUT_SAMPLE_RATE / 3571400.0f, powf(0.01f, 2) / 4.0f, 0.01, 100e-6f); // 0.00765625f, 0.175f, 0.005f
split.init(&recov.out);
split.bindStream(&reshapeInput);
split.bindStream(&thrInput);
reshape.init(&reshapeInput, 1024, 198976);
symSink.init(&reshape.out, symSinkHandler, this);
thr.init(&thrInput);
deframe.init(&thr.out, 10232, syncWord, 32);
falconRS.init(&deframe.out);
pkt.init(&falconRS.out);
sink.init(&pkt.out, sinkHandler, this);
demod.start();
recov.start();
split.start();
reshape.start();
symSink.start();
sink.start();
pkt.start();
falconRS.start();
deframe.start();
thr.start();
#ifdef _WIN32
ffplay = _popen("ffplay -framedrop -infbuf -hide_banner -loglevel panic -window_title \"Falcon 9 Cameras\" -", "wb");
#else
ffplay = popen("ffplay -framedrop -infbuf -hide_banner -loglevel panic -window_title \"Falcon 9 Cameras\" -", "wb");
#endif
gui::menu.registerEntry(name, menuHandler, this, this);
}
~Falcon9DecoderModule() {
}
void postInit() {}
void enable() {
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 4000000, INPUT_SAMPLE_RATE, 4000000, 4000000, true);
demod.setInput(vfo->output);
demod.start();
recov.start();
split.start();
reshape.start();
symSink.start();
sink.start();
pkt.start();
falconRS.start();
deframe.start();
thr.start();
enabled = true;
}
void disable() {
demod.stop();
recov.stop();
split.stop();
reshape.stop();
symSink.stop();
sink.stop();
pkt.stop();
falconRS.stop();
deframe.stop();
thr.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
Falcon9DecoderModule* _this = (Falcon9DecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (!_this->enabled) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
_this->symDiag.draw();
if (_this->logsVisible) {
if (ImGui::Button("Hide logs", ImVec2(menuWidth, 0))) { _this->logsVisible = false; }
}
else {
if (ImGui::Button("Show logs", ImVec2(menuWidth, 0))) { _this->logsVisible = true; }
}
if (_this->logsVisible) {
std::lock_guard<std::mutex> lck(_this->logsMtx);
ImGui::Begin("Falcon9 Telemetry");
ImGui::BeginTabBar("Falcon9Tabs");
// GPS Logs
ImGui::BeginTabItem("GPS");
if (ImGui::Button("Clear logs##GPSClear")) { _this->gpsLogs.clear(); }
ImGui::BeginChild(ImGuiID("GPSChild"));
ImGui::TextUnformatted(_this->gpsLogs.c_str());
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::EndTabItem();
// STMM1A Logs
ImGui::BeginTabItem("STMM1A");
ImGui::EndTabItem();
// STMM1B Logs
ImGui::BeginTabItem("STMM1B");
ImGui::EndTabItem();
// STMM1C Logs
ImGui::BeginTabItem("STMM1C");
ImGui::EndTabItem();
ImGui::EndTabBar();
ImGui::End();
}
if (!_this->enabled) { style::endDisabled(); }
}
static void sinkHandler(uint8_t* data, int count, void* ctx) {
Falcon9DecoderModule* _this = (Falcon9DecoderModule*)ctx;
uint16_t length = (((data[0] & 0b1111) << 8) | data[1]) + 2;
uint64_t pktId = ((uint64_t)data[2] << 56) | ((uint64_t)data[3] << 48) | ((uint64_t)data[4] << 40) | ((uint64_t)data[5] << 32)
| ((uint64_t)data[6] << 24) | ((uint64_t)data[7] << 16) | ((uint64_t)data[8] << 8) | data[9];
if (pktId == 0x0117FE0800320303 || pktId == 0x0112FA0800320303) {
data[length - 2] = 0;
_this->logsMtx.lock();
_this->gpsLogs += (char*)(data + 25);
_this->logsMtx.unlock();
}
else if (pktId == 0x01123201042E1403) {
fwrite(data + 25, 1, 940, _this->ffplay);
file.write((char*)(data + 25), 940);
}
//printf("%016" PRIX64 ": %d bytes, %d full\n", pktId, length, count);
}
static void symSinkHandler(float* data, int count, void* ctx) {
Falcon9DecoderModule* _this = (Falcon9DecoderModule*)ctx;
float* buf = _this->symDiag.acquireBuffer();
memcpy(buf, data, 1024*sizeof(float));
_this->symDiag.releaseBuffer();
}
std::string name;
bool enabled = true;
bool logsVisible = false;
std::mutex logsMtx;
std::string gpsLogs = "";
// DSP Chain
dsp::FloatFMDemod demod;
dsp::MMClockRecovery<float> recov;
dsp::Splitter<float> split;
dsp::stream<float> reshapeInput;
dsp::Reshaper<float> reshape;
dsp::HandlerSink<float> symSink;
dsp::stream<float> thrInput;
dsp::Threshold thr;
uint8_t syncWord[32] = {0,0,0,1,1,0,1,0,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,0,1};
dsp::Deframer deframe;
dsp::FalconRS falconRS;
dsp::FalconPacketSync pkt;
dsp::HandlerSink<uint8_t> sink;
FILE* ffplay;
VFOManager::VFO* vfo;
ImGui::SymbolDiagram symDiag;
};
MOD_EXPORT void _INIT_() {
// Nothing
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new Falcon9DecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (Falcon9DecoderModule*)instance;
}
MOD_EXPORT void _END_() {
// Nothing either
}

View File

@ -0,0 +1,50 @@
cmake_minimum_required(VERSION 3.13)
project(m17_decoder)
if (MSVC)
add_compile_options(/O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
add_compile_options(-O3 -std=c++17)
endif ()
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include_directories("src/")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native")
add_library(m17_decoder SHARED ${SRC})
target_link_libraries(m17_decoder PRIVATE sdrpp_core)
set_target_properties(m17_decoder PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_include_directories(m17_decoder PUBLIC "C:/Program Files/codec2/include/")
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/codec2/lib")
target_link_libraries(m17_decoder PRIVATE libcodec2)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBCODEC2 REQUIRED codec2)
target_include_directories(m17_decoder PUBLIC ${LIBCODEC2_INCLUDE_DIRS})
target_link_directories(m17_decoder PUBLIC ${LIBCODEC2_LIBRARY_DIRS})
target_link_libraries(m17_decoder PUBLIC ${LIBCODEC2_LIBRARIES})
# Include it because for some reason pkgconfig doesn't look here?
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_include_directories(m17_decoder PUBLIC "/usr/local/include")
endif()
endif ()
# Install directives
install(TARGETS m17_decoder DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,16 @@
#include <base40.h>
void decode_callsign_base40(uint64_t encoded, char *callsign) {
if (encoded >= 262144000000000) { // 40^9
*callsign = 0;
return;
}
char *p = callsign;
for (; encoded > 0; p++) {
*p = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."[encoded % 40];
encoded /= 40;
}
*p = 0;
return;
}

View File

@ -0,0 +1,4 @@
#pragma once
#include <stdint.h>
void decode_callsign_base40(uint64_t encoded, char* callsign);

View File

@ -0,0 +1,72 @@
// Copyright 2020 Mobilinkd LLC.
#pragma once
#include <cstdint>
#include <array>
#include <cstddef>
namespace mobilinkd
{
template <uint16_t Poly = 0x5935, uint16_t Init = 0xFFFF>
struct CRC16
{
static constexpr uint16_t MASK = 0xFFFF;
static constexpr uint16_t LSB = 0x0001;
static constexpr uint16_t MSB = 0x8000;
uint16_t reg_ = Init;
void reset()
{
reg_ = Init;
for (size_t i = 0; i != 16; ++i)
{
auto bit = reg_ & LSB;
if (bit) reg_ ^= Poly;
reg_ >>= 1;
if (bit) reg_ |= MSB;
}
reg_ &= MASK;
}
void operator()(uint8_t byte)
{
reg_ = crc(byte, reg_);
}
uint16_t crc(uint8_t byte, uint16_t reg)
{
for (size_t i = 0; i != 8; ++i)
{
auto msb = reg & MSB;
reg = ((reg << 1) & MASK) | ((byte >> (7 - i)) & LSB);
if (msb) reg ^= Poly;
}
return reg & MASK;
}
uint16_t get()
{
auto reg = reg_;
for (size_t i = 0; i != 16; ++i)
{
auto msb = reg & MSB;
reg = ((reg << 1) & MASK);
if (msb) reg ^= Poly;
}
return reg;
}
std::array<uint8_t, 2> get_bytes()
{
auto crc = get();
std::array<uint8_t, 2> result{uint8_t((crc >> 8) & 0xFF), uint8_t(crc & 0xFF)};
return result;
}
};
} // mobilinkd

View File

@ -0,0 +1,233 @@
// Copyright 2020 Rob Riggs <rob@mobilinkd.com>
// All rights reserved.
#pragma once
#include <array>
#include <cstdint>
#include <algorithm>
#include <utility>
namespace mobilinkd {
// Parts are adapted from:
// http://aqdi.com/articles/using-the-golay-error-detection-and-correction-code-3/
namespace Golay24
{
int popcount(uint32_t n) {
int count = 0;
for (int i = 0; i < 32; i++) {
count += ((n >> i) & 1);
}
return count;
}
namespace detail
{
// Need a constexpr sort.
// https://stackoverflow.com/a/40030044/854133
template<class T>
void swap(T& l, T& r)
{
T tmp = std::move(l);
l = std::move(r);
r = std::move(tmp);
}
template <typename T, size_t N>
struct array
{
constexpr T& operator[](size_t i)
{
return arr[i];
}
constexpr const T& operator[](size_t i) const
{
return arr[i];
}
constexpr const T* begin() const
{
return arr;
}
constexpr const T* end() const
{
return arr + N;
}
T arr[N];
};
template <typename T, size_t N>
void sort_impl(array<T, N> &array, size_t left, size_t right)
{
if (left < right)
{
size_t m = left;
for (size_t i = left + 1; i<right; i++)
if (array[i]<array[left])
swap(array[++m], array[i]);
swap(array[left], array[m]);
sort_impl(array, left, m);
sort_impl(array, m + 1, right);
}
}
template <typename T, size_t N>
array<T, N> sort(array<T, N> array)
{
auto sorted = array;
sort_impl(sorted, 0, N);
return sorted;
}
} // detail
// static constexpr uint16_t POLY = 0xAE3;
constexpr uint16_t POLY = 0xC75;
#pragma pack(push, 1)
struct SyndromeMapEntry
{
uint32_t a{0};
uint16_t b{0};
};
#pragma pack(pop)
/**
* Calculate the syndrome of a [23,12] Golay codeword.
*
* @return the 11-bit syndrome of the codeword in bits [22:12].
*/
uint32_t syndrome(uint32_t codeword)
{
codeword &= 0xffffffl;
for (size_t i = 0; i != 12; ++i)
{
if (codeword & 1)
codeword ^= POLY;
codeword >>= 1;
}
return (codeword << 12);
}
bool parity(uint32_t codeword)
{
return popcount(codeword) & 1;
}
SyndromeMapEntry makeSyndromeMapEntry(uint64_t val)
{
return SyndromeMapEntry{uint32_t(val >> 16), uint16_t(val & 0xFFFF)};
}
uint64_t makeSME(uint64_t syndrome, uint32_t bits)
{
return (syndrome << 24) | (bits & 0xFFFFFF);
}
constexpr size_t LUT_SIZE = 2048;
std::array<SyndromeMapEntry, LUT_SIZE> make_lut()
{
constexpr size_t VECLEN=23;
detail::array<uint64_t, LUT_SIZE> result{};
size_t index = 0;
result[index++] = makeSME(syndrome(0), 0);
for (size_t i = 0; i != VECLEN; ++i)
{
auto v = (1 << i);
result[index++] = makeSME(syndrome(v), v);
}
for (size_t i = 0; i != VECLEN - 1; ++i)
{
for (size_t j = i + 1; j != VECLEN; ++j)
{
auto v = (1 << i) | (1 << j);
result[index++] = makeSME(syndrome(v), v);
}
}
for (size_t i = 0; i != VECLEN - 2; ++i)
{
for (size_t j = i + 1; j != VECLEN - 1; ++j)
{
for (size_t k = j + 1; k != VECLEN; ++k)
{
auto v = (1 << i) | (1 << j) | (1 << k);
result[index++] = makeSME(syndrome(v), v);
}
}
}
result = detail::sort(result);
std::array<SyndromeMapEntry, LUT_SIZE> tmp;
for (size_t i = 0; i != LUT_SIZE; ++i)
{
tmp[i] = makeSyndromeMapEntry(result[i]);
}
return tmp;
}
inline auto LUT = make_lut();
/**
* Calculate [23,12] Golay codeword.
*
* @return checkbits(11)|data(12).
*/
uint32_t encode23(uint16_t data)
{
// data &= 0xfff;
uint32_t codeword = data;
for (size_t i = 0; i != 12; ++i)
{
if (codeword & 1)
codeword ^= POLY;
codeword >>= 1;
}
return codeword | (data << 11);
}
uint32_t encode24(uint16_t data)
{
auto codeword = encode23(data);
return ((codeword << 1) | parity(codeword));
}
bool decode(uint32_t input, uint32_t& output)
{
auto syndrm = syndrome(input >> 1);
auto it = std::lower_bound(LUT.begin(), LUT.end(), syndrm,
[](const SyndromeMapEntry& sme, uint32_t val){
return (sme.a >> 8) < val;
});
if ((it->a >> 8) == syndrm)
{
// Build the correction from the compressed entry.
auto correction = ((((it->a & 0xFF) << 16) | it->b) << 1);
// Apply the correction to the input.
output = input ^ correction;
// Only test parity for 3-bit errors.
return popcount(syndrm) < 3 || !parity(output);
}
return false;
}
} // Golay24
} // mobilinkd

View File

@ -0,0 +1,112 @@
#include <lsf_decode.h>
#include <string.h>
#include <base40.h>
#include <stdio.h>
#include <inttypes.h>
#include <crc16.h>
#include <spdlog/spdlog.h>
bool M17CheckCRC(uint8_t* data, int len) {
// TODO: Implement
return true;
}
const char* M17DataTypesTxt[4] = {
"Unknown",
"Data",
"Voice",
"Voice & Data"
};
const char* M17EncryptionTypesTxt[4] = {
"None",
"AES",
"Scrambler",
"Unknown"
};
M17LSF M17DecodeLSF(uint8_t* _lsf) {
M17LSF lsf;
// Extract CRC
lsf.rawCRC = 0;
for (int i = 0; i < 16; i++) {
lsf.rawCRC |= (((uint16_t)_lsf[(i+48+48+16+112) / 8] >> (7 - (i%8))) & 1) << (15 - i);
}
// Check CRC
mobilinkd::CRC16 crc16;
crc16.reset();
for (int i = 0; i < 28; i++) {
crc16(_lsf[i]);
}
if (crc16.get() != lsf.rawCRC) {
lsf.valid = false;
return lsf;
}
lsf.valid = true;
// Extract DST
lsf.rawDst = 0;
for (int i = 0; i < 48; i++) {
lsf.rawDst |= (((uint64_t)_lsf[i / 8] >> (7 - (i%8))) & 1) << (47 - i);
}
// Extract SRC
lsf.rawSrc = 0;
for (int i = 0; i < 48; i++) {
lsf.rawSrc |= (((uint64_t)_lsf[(i+48) / 8] >> (7 - (i%8))) & 1) << (47 - i);
}
// Extract TYPE
lsf.rawType = 0;
for (int i = 0; i < 16; i++) {
lsf.rawType |= (((uint16_t)_lsf[(i+48+48) / 8] >> (7 - (i%8))) & 1) << (15 - i);
}
// Extract META
memcpy(lsf.meta, &_lsf[14], 14);
// Decode DST
if (lsf.rawDst == 0) {
lsf.dst = "Invalid";
}
else if (lsf.rawDst <= 262143999999999) {
char buf[128];
decode_callsign_base40(lsf.rawDst, buf);
lsf.dst = buf;
}
else if (lsf.rawDst == 0xFFFFFFFFFFFF) {
lsf.dst = "Broadcast";
}
else {
char buf[128];
sprintf(buf, "%" PRIX64, lsf.rawDst);
lsf.dst = buf;
}
// Decode SRC
if (lsf.rawSrc == 0 || lsf.rawSrc == 0xFFFFFFFFFFFF) {
lsf.src = "Invalid";
}
else if (lsf.rawSrc <= 262143999999999) {
char buf[128];
decode_callsign_base40(lsf.rawSrc, buf);
lsf.src = buf;
}
else {
char buf[128];
sprintf(buf, "%" PRIX64, lsf.rawSrc);
lsf.src = buf;
}
// Decode TYPE
lsf.isStream = (lsf.rawType >> 0) & 0b1;
lsf.dataType = (M17DataType)((lsf.rawType >> 1) & 0b11);
lsf.encryptionType = (M17EncryptionType)((lsf.rawType >> 3) & 0b11);
lsf.encryptionSubType = (lsf.rawType >> 5) & 0b11;
lsf.channelAccessNum = (lsf.rawType >> 7) & 0b1111;
return lsf;
}

View File

@ -0,0 +1,42 @@
#pragma once
#include <string>
#include <stdint.h>
enum M17DataType {
M17_DATATYPE_UNKNOWN = 0b00,
M17_DATATYPE_DATA = 0b01,
M17_DATATYPE_VOICE = 0b10,
M17_DATATYPE_DATA_VOICE = 0b11
};
enum M17EncryptionType {
M17_ENCRYPTION_NONE = 0b00,
M17_ENCRYPTION_AES = 0b01,
M17_ENCRYPTION_SCRAMBLE = 0b10,
M17_ENCRYPTION_UNKNOWN = 0b11
};
extern const char* M17DataTypesTxt[4];
extern const char* M17EncryptionTypesTxt[4];
struct M17LSF {
uint64_t rawDst;
uint64_t rawSrc;
uint16_t rawType;
uint8_t meta[14];
uint16_t rawCRC;
std::string dst;
std::string src;
bool isStream;
M17DataType dataType;
M17EncryptionType encryptionType;
uint8_t encryptionSubType;
uint8_t channelAccessNum;
bool valid;
};
bool M17CheckCRC(uint8_t* data, int len);
M17LSF M17DecodeLSF(uint8_t* _lsf);

View File

@ -0,0 +1,667 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
#include <codec2.h>
#include <dsp/demodulator.h>
#include <golay24.h>
#include <lsf_decode.h>
extern "C" {
#include <correct.h>
}
#define M17_DEVIATION 2400.0f
#define M17_BAUDRATE 4800.0f
#define M17_RRC_ALPHA 0.5f
#define M17_4FSK_HIGH_CUT 0.5f
#define M17_SYNC_SIZE 16
#define M17_LICH_SIZE 96
#define M17_PAYLOAD_SIZE 144
#define M17_ENCODED_PAYLOAD_SIZE 296
#define M17_LSF_SIZE 240
#define M17_ENCODED_LSF_SIZE 488
#define M17_RAW_FRAME_SIZE 384
#define M17_CUT_FRAME_SIZE 368
const uint8_t M17_LSF_SYNC[16] = { 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1 };
const uint8_t M17_STF_SYNC[16] = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1 };
const uint8_t M17_PKF_SYNC[16] = { 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
const uint8_t M17_SCRAMBLER[368] = { 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1,
1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0,
1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1,
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0,
0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1,
1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1,
1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1,
0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0,
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0,
1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1,
1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1,
1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1 };
const uint16_t M17_INTERLEAVER[368] = { 0, 137, 90, 227, 180, 317, 270, 39, 360, 129, 82, 219, 172, 309, 262, 31,
352, 121, 74, 211, 164, 301, 254, 23, 344, 113, 66, 203, 156, 293, 246, 15,
336, 105, 58, 195, 148, 285, 238, 7, 328, 97, 50, 187, 140, 277, 230, 367,
320, 89, 42, 179, 132, 269, 222, 359, 312, 81, 34, 171, 124, 261, 214, 351,
304, 73, 26, 163, 116, 253, 206, 343, 296, 65, 18, 155, 108, 245, 198, 335,
288, 57, 10, 147, 100, 237, 190, 327, 280, 49, 2, 139, 92, 229, 182, 319,
272, 41, 362, 131, 84, 221, 174, 311, 264, 33, 354, 123, 76, 213, 166, 303,
256, 25, 346, 115, 68, 205, 158, 295, 248, 17, 338, 107, 60, 197, 150, 287,
240, 9, 330, 99, 52, 189, 142, 279, 232, 1, 322, 91, 44, 181, 134, 271,
224, 361, 314, 83, 36, 173, 126, 263, 216, 353, 306, 75, 28, 165, 118, 255,
208, 345, 298, 67, 20, 157, 110, 247, 200, 337, 290, 59, 12, 149, 102, 239,
192, 329, 282, 51, 4, 141, 94, 231, 184, 321, 274, 43, 364, 133, 86, 223,
176, 313, 266, 35, 356, 125, 78, 215, 168, 305, 258, 27, 348, 117, 70, 207,
160, 297, 250, 19, 340, 109, 62, 199, 152, 289, 242, 11, 332, 101, 54, 191,
144, 281, 234, 3, 324, 93, 46, 183, 136, 273, 226, 363, 316, 85, 38, 175,
128, 265, 218, 355, 308, 77, 30, 167, 120, 257, 210, 347, 300, 69, 22, 159,
112, 249, 202, 339, 292, 61, 14, 151, 104, 241, 194, 331, 284, 53, 6, 143,
96, 233, 186, 323, 276, 45, 366, 135, 88, 225, 178, 315, 268, 37, 358, 127,
80, 217, 170, 307, 260, 29, 350, 119, 72, 209, 162, 299, 252, 21, 342, 111,
64, 201, 154, 291, 244, 13, 334, 103, 56, 193, 146, 283, 236, 5, 326, 95,
48, 185, 138, 275, 228, 365, 318, 87, 40, 177, 130, 267, 220, 357, 310, 79,
32, 169, 122, 259, 212, 349, 302, 71, 24, 161, 114, 251, 204, 341, 294, 63,
16, 153, 106, 243, 196, 333, 286, 55, 8, 145, 98, 235, 188, 325, 278, 47 };
const uint8_t M17_PUNCTURING_P1[61] = { 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1 };
const uint8_t M17_PUNCTURING_P2[12] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 };
static const correct_convolutional_polynomial_t correct_conv_m17_polynomial[] = {0b11001, 0b10111};
namespace dsp {
class M17Slice4FSK : public generic_block<M17Slice4FSK> {
public:
M17Slice4FSK() {}
M17Slice4FSK(stream<float>* in) { init(in); }
void init(stream<float>* in) {
_in = in;
generic_block<M17Slice4FSK>::registerInput(_in);
generic_block<M17Slice4FSK>::registerOutput(&out);
generic_block<M17Slice4FSK>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<M17Slice4FSK>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17Slice4FSK>::ctrlMtx);
generic_block<M17Slice4FSK>::tempStop();
generic_block<M17Slice4FSK>::unregisterInput(_in);
_in = in;
generic_block<M17Slice4FSK>::registerInput(_in);
generic_block<M17Slice4FSK>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
float val;
for (int i = 0; i < count; i++) {
val = _in->readBuf[i];
out.writeBuf[i*2] = (val < 0.0f);
out.writeBuf[(i*2)+1] = (fabsf(val) > M17_4FSK_HIGH_CUT);
}
_in->flush();
if (!out.swap(count * 2)) { return -1; }
return count;
}
stream<uint8_t> out;
private:
stream<float>* _in;
};
class M17FrameDemux : public generic_block<M17FrameDemux> {
public:
M17FrameDemux() {}
M17FrameDemux(stream<uint8_t>* in) { init(in); }
~M17FrameDemux() {
if (!generic_block<M17FrameDemux>::_block_init) { return; }
generic_block<M17FrameDemux>::stop();
delete[] delay;
}
void init(stream<uint8_t>* in) {
_in = in;
delay = new uint8_t[STREAM_BUFFER_SIZE];
generic_block<M17FrameDemux>::registerInput(_in);
generic_block<M17FrameDemux>::registerOutput(&linkSetupOut);
generic_block<M17FrameDemux>::registerOutput(&lichOut);
generic_block<M17FrameDemux>::registerOutput(&streamOut);
generic_block<M17FrameDemux>::registerOutput(&packetOut);
generic_block<M17FrameDemux>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17FrameDemux>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17FrameDemux>::ctrlMtx);
generic_block<M17FrameDemux>::tempStop();
generic_block<M17FrameDemux>::unregisterInput(_in);
_in = in;
generic_block<M17FrameDemux>::registerInput(_in);
generic_block<M17FrameDemux>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
memcpy(&delay[M17_SYNC_SIZE], _in->readBuf, count);
for (int i = 0; i < count;) {
if (detect) {
if (outCount < M17_SYNC_SIZE) { outCount++; i++; }
else {
int id = M17_INTERLEAVER[outCount - M17_SYNC_SIZE];
if (type == 0) {
linkSetupOut.writeBuf[id] = delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE];
}
else if ((type == 1 || type == 2) && id < M17_LICH_SIZE) {
lichOut.writeBuf[id] = delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE];
}
else if (type == 1) {
streamOut.writeBuf[id - M17_LICH_SIZE] = (delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE]);
}
else if (type == 2) {
packetOut.writeBuf[id - M17_LICH_SIZE] = (delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE]);
}
outCount++;
}
if (outCount >= M17_RAW_FRAME_SIZE) {
detect = false;
if (type == 0) {
if (!linkSetupOut.swap(M17_CUT_FRAME_SIZE)) { return -1; }
}
else if (type == 1) {
if (!lichOut.swap(M17_LICH_SIZE)) {return -1; }
if (!streamOut.swap(M17_CUT_FRAME_SIZE)) {return -1; }
}
else if (type == 2) {
if (!lichOut.swap(M17_LICH_SIZE)) {return -1; }
if (!packetOut.swap(M17_CUT_FRAME_SIZE)) { return -1; }
}
}
continue;
}
// Check for link setup syncword
if (!memcmp(&delay[i], M17_LSF_SYNC, M17_SYNC_SIZE)) {
detect = true;
outCount = 0;
type = 0;
//spdlog::warn("Found sync frame");
continue;
}
// Check for stream syncword
if (!memcmp(&delay[i], M17_STF_SYNC, M17_SYNC_SIZE)) {
detect = true;
outCount = 0;
type = 1;
//spdlog::warn("Found stream frame");
continue;
}
// Check for packet syncword
if (!memcmp(&delay[i], M17_PKF_SYNC, M17_SYNC_SIZE)) {
detect = true;
outCount = 0;
type = 2;
//spdlog::warn("Found packet frame");
continue;
}
i++;
}
memmove(delay, &delay[count], 16);
_in->flush();
return count;
}
stream<uint8_t> linkSetupOut;
stream<uint8_t> lichOut;
stream<uint8_t> streamOut;
stream<uint8_t> packetOut;
private:
stream<uint8_t>* _in;
uint8_t* delay;
bool detect = false;
int type;
int outCount = 0;
};
class M17LSFDecoder : public generic_block<M17LSFDecoder> {
public:
M17LSFDecoder() {}
M17LSFDecoder(stream<uint8_t>* in, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) { init(in, handler, ctx); }
~M17LSFDecoder() {
if (!generic_block<M17LSFDecoder>::_block_init) { return; }
generic_block<M17LSFDecoder>::stop();
correct_convolutional_destroy(conv);
}
void init(stream<uint8_t>* in, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) {
_in = in;
_handler = handler;
_ctx = ctx;
conv = correct_convolutional_create(2, 5, correct_conv_m17_polynomial);
generic_block<M17LSFDecoder>::registerInput(_in);
generic_block<M17LSFDecoder>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17LSFDecoder>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17LSFDecoder>::ctrlMtx);
generic_block<M17LSFDecoder>::tempStop();
generic_block<M17LSFDecoder>::unregisterInput(_in);
_in = in;
generic_block<M17LSFDecoder>::registerInput(_in);
generic_block<M17LSFDecoder>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Depuncture the data
int inOffset = 0;
for (int i = 0; i < M17_ENCODED_LSF_SIZE; i++) {
if (!M17_PUNCTURING_P1[i % 61]) {
depunctured[i] = 0;
continue;
}
depunctured[i] = _in->readBuf[inOffset++];
}
_in->flush();
// Pack into bytes
memset(packed, 0, 61);
for (int i = 0; i < M17_ENCODED_LSF_SIZE; i++) {
packed[i/8] |= depunctured[i] << (7 - (i%8));
}
// Run through convolutional decoder
correct_convolutional_decode(conv, packed, M17_ENCODED_LSF_SIZE, lsf);
// Decode it and call the handler
M17LSF decLsf = M17DecodeLSF(lsf);
if (decLsf.valid) { _handler(decLsf, _ctx); }
return count;
}
private:
stream<uint8_t>* _in;
void (*_handler)(M17LSF& lsf, void* ctx);
void* _ctx;
uint8_t depunctured[488];
uint8_t packed[61];
uint8_t lsf[30];
correct_convolutional* conv;
};
class M17PayloadFEC : public generic_block<M17PayloadFEC> {
public:
M17PayloadFEC() {}
M17PayloadFEC(stream<uint8_t>* in) { init(in); }
~M17PayloadFEC() {
if (!generic_block<M17PayloadFEC>::_block_init) { return; }
generic_block<M17PayloadFEC>::stop();
correct_convolutional_destroy(conv);
}
void init(stream<uint8_t>* in) {
_in = in;
conv = correct_convolutional_create(2, 5, correct_conv_m17_polynomial);
generic_block<M17PayloadFEC>::registerInput(_in);
generic_block<M17PayloadFEC>::registerOutput(&out);
generic_block<M17PayloadFEC>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17PayloadFEC>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17PayloadFEC>::ctrlMtx);
generic_block<M17PayloadFEC>::tempStop();
generic_block<M17PayloadFEC>::unregisterInput(_in);
_in = in;
generic_block<M17PayloadFEC>::registerInput(_in);
generic_block<M17PayloadFEC>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Depuncture the data
int inOffset = 0;
for (int i = 0; i < M17_ENCODED_PAYLOAD_SIZE; i++) {
if (!M17_PUNCTURING_P2[i % 12]) {
depunctured[i] = 0;
continue;
}
depunctured[i] = _in->readBuf[inOffset++];
}
// Pack into bytes
memset(packed, 0, 37);
for (int i = 0; i < M17_ENCODED_PAYLOAD_SIZE; i++) {
if (!(i%8)) { packed[i/8] = 0; }
packed[i/8] |= depunctured[i] << (7 - (i%8));
}
// Run through convolutional decoder
correct_convolutional_decode(conv, packed, M17_ENCODED_PAYLOAD_SIZE, out.writeBuf);
_in->flush();
if (!out.swap(M17_PAYLOAD_SIZE / 8)) { return -1; }
return count;
}
stream<uint8_t> out;
private:
stream<uint8_t>* _in;
uint8_t depunctured[296];
uint8_t packed[37];
correct_convolutional* conv;
};
class M17Codec2Decode : public generic_block<M17Codec2Decode> {
public:
M17Codec2Decode() {}
M17Codec2Decode(stream<uint8_t>* in) { init(in); }
~M17Codec2Decode() {
if (!generic_block<M17Codec2Decode>::_block_init) { return; }
generic_block<M17Codec2Decode>::stop();
codec2_destroy(codec);
delete[] int16Audio;
delete[] floatAudio;
}
void init(stream<uint8_t>* in) {
_in = in;
codec = codec2_create(CODEC2_MODE_3200);
sampsPerC2Frame = codec2_samples_per_frame(codec);
sampsPerC2FrameDouble = sampsPerC2Frame * 2;
int16Audio = new int16_t[sampsPerC2FrameDouble];
floatAudio = new float[sampsPerC2FrameDouble];
generic_block<M17Codec2Decode>::registerInput(_in);
generic_block<M17Codec2Decode>::registerOutput(&out);
generic_block<M17Codec2Decode>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17Codec2Decode>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17Codec2Decode>::ctrlMtx);
generic_block<M17Codec2Decode>::tempStop();
generic_block<M17Codec2Decode>::unregisterInput(_in);
_in = in;
generic_block<M17Codec2Decode>::registerInput(_in);
generic_block<M17Codec2Decode>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Decode both parts using codec
codec2_decode(codec, int16Audio, &_in->readBuf[2]);
codec2_decode(codec, &int16Audio[sampsPerC2Frame], &_in->readBuf[2+8]);
// Convert to float
volk_16i_s32f_convert_32f(floatAudio, int16Audio, 32768.0f, sampsPerC2FrameDouble);
// Interleave into stereo samples
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, floatAudio, floatAudio, sampsPerC2FrameDouble);
_in->flush();
if (!out.swap(sampsPerC2FrameDouble)) { return -1; }
return count;
}
stream<stereo_t> out;
private:
stream<uint8_t>* _in;
int16_t* int16Audio;
float* floatAudio;
CODEC2* codec;
int sampsPerC2Frame = 0;
int sampsPerC2FrameDouble = 0;
};
class M17LICHDecoder : public generic_block<M17LICHDecoder> {
public:
M17LICHDecoder() {}
M17LICHDecoder(stream<uint8_t>* in, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) { init(in, handler, ctx); }
void init(stream<uint8_t>* in, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) {
_in = in;
_handler = handler;
_ctx = ctx;
generic_block<M17LICHDecoder>::registerInput(_in);
generic_block<M17LICHDecoder>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<M17LICHDecoder>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<M17LICHDecoder>::ctrlMtx);
generic_block<M17LICHDecoder>::tempStop();
generic_block<M17LICHDecoder>::unregisterInput(_in);
_in = in;
generic_block<M17LICHDecoder>::registerInput(_in);
generic_block<M17LICHDecoder>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Zero out block
memset(chunk, 0, 6);
// Decode the 4 Golay(24, 12) blocks
uint32_t encodedBlock;
uint32_t decodedBlock;
for (int b = 0; b < 4; b++) {
// Pack the 24bit block into a byte
encodedBlock = 0;
decodedBlock = 0;
for (int i = 0; i < 24; i++) { encodedBlock |= _in->readBuf[(b * 24) + i] << (23 - i); }
// Decode
if (!mobilinkd::Golay24::decode(encodedBlock, decodedBlock)) {
_in->flush();
return count;
}
// Pack the decoded bits into the output
int id = 0;
uint8_t temp;
for (int i = 0; i < 12; i++) {
id = (b * 12) + i;
chunk[id / 8] |= ((decodedBlock >> (23 - i)) & 1) << (7 - (id%8));
}
}
_in->flush();
int partId = chunk[5] >> 5;
// If the ID of the chunk is zero, start a new LSF
if (partId == 0) {
newFrame = true;
lastId = 0;
memcpy(&lsf[partId*5], chunk, 5);
return count;
}
// If we're recording a LSF and a discontinuity shows up, cancel
if (newFrame && partId != lastId + 1) {
newFrame = false;
return count;
}
// If we're recording and there's no discontinuity (see above), add the data to the full frame
if (newFrame) {
lastId = partId;
memcpy(&lsf[partId*5], chunk, 5);
// If the lsf is complete, send it out
if (partId == 5) {
newFrame = false;
M17LSF decLsf = M17DecodeLSF(lsf);
if (decLsf.valid) { _handler(decLsf, _ctx); }
}
}
return count;
}
private:
stream<uint8_t>* _in;
void (*_handler)(M17LSF& lsf, void* ctx);
void* _ctx;
uint8_t chunk[6];
uint8_t lsf[240];
bool newFrame = false;
int lastId = 0;
};
class M17Decoder : public generic_hier_block<M17Decoder> {
public:
M17Decoder() {}
M17Decoder(stream<complex_t>* input, float sampleRate, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) {
init(input, sampleRate, handler, ctx);
}
void init(stream<complex_t>* input, float sampleRate, void (*handler)(M17LSF& lsf, void* ctx), void* ctx) {
_sampleRate = sampleRate;
demod.init(input, _sampleRate, M17_DEVIATION);
rrc.init(31, _sampleRate, M17_BAUDRATE, M17_RRC_ALPHA);
fir.init(&demod.out, &rrc);
recov.init(&fir.out, _sampleRate / M17_BAUDRATE, 1e-6f, 0.01f, 0.01f);
doubler.init(&recov.out);
slice.init(&doubler.outA);
demux.init(&slice.out);
lsfFEC.init(&demux.linkSetupOut, handler, ctx);
payloadFEC.init(&demux.streamOut);
decodeLICH.init(&demux.lichOut, handler, ctx);
decodeAudio.init(&payloadFEC.out);
ns2.init(&demux.packetOut);
diagOut = &doubler.outB;
out = &decodeAudio.out;
generic_hier_block<M17Decoder>::registerBlock(&demod);
generic_hier_block<M17Decoder>::registerBlock(&fir);
generic_hier_block<M17Decoder>::registerBlock(&recov);
generic_hier_block<M17Decoder>::registerBlock(&doubler);
generic_hier_block<M17Decoder>::registerBlock(&slice);
generic_hier_block<M17Decoder>::registerBlock(&demux);
generic_hier_block<M17Decoder>::registerBlock(&lsfFEC);
generic_hier_block<M17Decoder>::registerBlock(&payloadFEC);
generic_hier_block<M17Decoder>::registerBlock(&decodeLICH);
generic_hier_block<M17Decoder>::registerBlock(&decodeAudio);
generic_hier_block<M17Decoder>::registerBlock(&ns2);
generic_hier_block<M17Decoder>::_block_init = true;
}
void setInput(stream<complex_t>* input) {
assert(generic_hier_block<M17Decoder>::_block_init);
demod.setInput(input);
}
stream<float>* diagOut = NULL;
stream<stereo_t>* out = NULL;
private:
FloatFMDemod demod;
RRCTaps rrc;
FIR<float> fir;
MMClockRecovery<float> recov;
StreamDoubler<float> doubler;
M17Slice4FSK slice;
M17FrameDemux demux;
M17LSFDecoder lsfFEC;
M17PayloadFEC payloadFEC;
M17LICHDecoder decodeLICH;
M17Codec2Decode decodeAudio;
NullSink<uint8_t> ns2;
float _sampleRate;
};
}

View File

@ -0,0 +1,315 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <filesystem>
#include <dsp/pll.h>
#include <dsp/stream.h>
#include <dsp/demodulator.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <dsp/routing.h>
#include <dsp/sink.h>
#include <gui/widgets/folder_select.h>
#include <gui/widgets/symbol_diagram.h>
#include <m17dsp.h>
#include <fstream>
#include <chrono>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "m17_decoder",
/* Description: */ "M17 Digital Voice Decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
#define INPUT_SAMPLE_RATE 14400
class M17DecoderModule : public ModuleManager::Instance {
public:
M17DecoderModule(std::string name) : diag(0.8, 480) {
this->name = name;
lsf.valid = false;
// Load config
config.acquire();
if (!config.conf.contains(name)) {
config.conf[name]["showLines"] = false;
}
showLines = config.conf[name]["showLines"];
if (showLines) {
diag.lines.push_back(-0.75f);
diag.lines.push_back(-0.25f);
diag.lines.push_back(0.25f);
diag.lines.push_back(0.75f);
}
config.release(true);
// Initialize VFO
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 9600, INPUT_SAMPLE_RATE, 9600, 9600, true);
vfo->setSnapInterval(250);
// Initialize DSP here
decoder.init(vfo->output, INPUT_SAMPLE_RATE, lsfHandler, this);
resampWin.init(4000, 4000, audioSampRate);
resamp.init(decoder.out, &resampWin, 8000, audioSampRate);
resampWin.setSampleRate(8000 * resamp.getInterpolation());
resamp.updateWindow(&resampWin);
reshape.init(decoder.diagOut, 480, 0);
diagHandler.init(&reshape.out, _diagHandler, this);
// Start DSO Here
decoder.start();
resamp.start();
reshape.start();
diagHandler.start();
// Setup audio stream
srChangeHandler.ctx = this;
srChangeHandler.handler = sampleRateChangeHandler;
stream.init(&resamp.out, &srChangeHandler, audioSampRate);
sigpath::sinkManager.registerStream(name, &stream);
stream.start();
gui::menu.registerEntry(name, menuHandler, this, this);
}
~M17DecoderModule() {
gui::menu.removeEntry(name);
// Stop DSP Here
stream.stop();
if (enabled) {
decoder.stop();
resamp.stop();
reshape.stop();
diagHandler.stop();
sigpath::vfoManager.deleteVFO(vfo);
}
sigpath::sinkManager.unregisterStream(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw/2.0, bw/2.0), 9600, INPUT_SAMPLE_RATE, 9600, 9600, true);
vfo->setSnapInterval(250);
// Set Input of demod here
decoder.setInput(vfo->output);
// Start DSP here
decoder.start();
resamp.start();
reshape.start();
diagHandler.start();
enabled = true;
}
void disable() {
// Stop DSP here
decoder.stop();
resamp.stop();
reshape.stop();
diagHandler.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (!_this->enabled) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
_this->diag.draw();
{
std::lock_guard lck(_this->lsfMtx);
auto now = std::chrono::high_resolution_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now-_this->lastUpdated).count() > 1000) {
_this->lsf.valid = false;
}
ImGui::BeginTable(CONCAT("##m17_info_tbl_", _this->name), 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
if (!_this->lsf.valid) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Source");
ImGui::TableSetColumnIndex(1);
ImGui::Text("--");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Destination");
ImGui::TableSetColumnIndex(1);
ImGui::Text("--");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Data Type");
ImGui::TableSetColumnIndex(1);
ImGui::Text("--");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Encryption");
ImGui::TableSetColumnIndex(1);
ImGui::Text("-- (Subtype --)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("CAN");
ImGui::TableSetColumnIndex(1);
ImGui::Text("--");
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Source");
ImGui::TableSetColumnIndex(1);
ImGui::Text(_this->lsf.src.c_str());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Destination");
ImGui::TableSetColumnIndex(1);
ImGui::Text(_this->lsf.dst.c_str());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Data Type");
ImGui::TableSetColumnIndex(1);
ImGui::Text(M17DataTypesTxt[_this->lsf.dataType]);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Encryption");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s (Subtype %d)", M17EncryptionTypesTxt[_this->lsf.encryptionType], _this->lsf.encryptionSubType);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("CAN");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", _this->lsf.channelAccessNum);
}
ImGui::EndTable();
}
if (ImGui::Checkbox(CONCAT("Show Reference TEST Lines##m17_showlines_", _this->name), &_this->showLines)) {
if (_this->showLines) {
_this->diag.lines.push_back(-0.75f);
_this->diag.lines.push_back(-0.25f);
_this->diag.lines.push_back(0.25f);
_this->diag.lines.push_back(0.75f);
}
else {
_this->diag.lines.clear();
}
config.acquire();
config.conf[_this->name]["showLines"] = _this->showLines;
config.release(true);
}
if (!_this->enabled) { style::endDisabled(); }
}
static void _diagHandler(float* data, int count, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
static void sampleRateChangeHandler(float sampleRate, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
// TODO: If too slow, change all demods here and not when setting
_this->audioSampRate = sampleRate;
_this->resampWin.setCutoff(std::min<float>(sampleRate/2, 4000));
_this->resamp.tempStop();
_this->resamp.setOutSampleRate(sampleRate);
_this->resampWin.setSampleRate(8000 * _this->resamp.getInterpolation());
_this->resamp.updateWindow(&_this->resampWin);
_this->resamp.tempStart();
}
static void lsfHandler(M17LSF& lsf, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
std::lock_guard lck(_this->lsfMtx);
_this->lastUpdated = std::chrono::high_resolution_clock::now();
_this->lsf = lsf;
}
std::string name;
bool enabled = true;
// DSP Chain
VFOManager::VFO* vfo;
dsp::M17Decoder decoder;
dsp::Reshaper<float> reshape;
dsp::HandlerSink<float> diagHandler;
dsp::filter_window::BlackmanWindow resampWin;
dsp::PolyphaseResampler<dsp::stereo_t> resamp;
ImGui::SymbolDiagram diag;
double audioSampRate = 48000;
EventHandler<float> srChangeHandler;
SinkManager::Stream stream;
bool showLines = false;
M17LSF lsf;
std::mutex lsfMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> lastUpdated;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(options::opts.root + "/m17_decoder_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new M17DecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (M17DecoderModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.13)
project(meteor_demodulator)
if (MSVC)
add_compile_options(/O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
add_compile_options(-O3 -std=c++17)
endif ()
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include_directories("src/")
include_directories("src/libcorrect/")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native")
add_library(meteor_demodulator SHARED ${SRC})
target_link_libraries(meteor_demodulator PRIVATE sdrpp_core)
set_target_properties(meteor_demodulator PROPERTIES PREFIX "")
# Install directives
install(TARGETS meteor_demodulator DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,271 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <filesystem>
#include <dsp/pll.h>
#include <dsp/stream.h>
#include <dsp/demodulator.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <dsp/routing.h>
#include <dsp/sink.h>
#include <meteor_demodulator_interface.h>
#include <gui/widgets/folder_select.h>
#include <gui/widgets/constellation_diagram.h>
#include <fstream>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "meteor_demodulator",
/* Description: */ "Meteor demodulator for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
std::string genFileName(std::string prefix, std::string suffix) {
time_t now = time(0);
tm *ltm = localtime(&now);
char buf[1024];
sprintf(buf, "%s_%02d-%02d-%02d_%02d-%02d-%02d%s", prefix.c_str(), ltm->tm_hour, ltm->tm_min, ltm->tm_sec, ltm->tm_mday, ltm->tm_mon + 1, ltm->tm_year + 1900, suffix.c_str());
return buf;
}
#define INPUT_SAMPLE_RATE 150000
class MeteorDemodulatorModule : public ModuleManager::Instance {
public:
MeteorDemodulatorModule(std::string name) : folderSelect("%ROOT%/recordings") {
this->name = name;
writeBuffer = new int8_t[STREAM_BUFFER_SIZE];
// Load config
config.acquire();
bool created = false;
if (!config.conf.contains(name)) {
config.conf[name]["recPath"] = "%ROOT%/recordings";
created = true;
}
folderSelect.setPath(config.conf[name]["recPath"]);
config.release(created);
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
demod.init(vfo->output, INPUT_SAMPLE_RATE, 72000.0f, 32, 0.6f, 0.1f, 0.005f);
split.init(demod.out);
split.bindStream(&symSinkStream);
split.bindStream(&sinkStream);
reshape.init(&symSinkStream, 1024, (72000 / 30) - 1024);
symSink.init(&reshape.out, symSinkHandler, this);
sink.init(&sinkStream, sinkHandler, this);
demod.start();
split.start();
reshape.start();
symSink.start();
sink.start();
gui::menu.registerEntry(name, menuHandler, this, this);
core::modComManager.registerInterface("meteor_demodulator", name, moduleInterfaceHandler, this);
}
~MeteorDemodulatorModule() {
if (recording) {
std::lock_guard<std::mutex> lck(recMtx);
recording = false;
recFile.close();
}
demod.stop();
split.stop();
reshape.stop();
symSink.stop();
sink.stop();
sigpath::vfoManager.deleteVFO(vfo);
gui::menu.removeEntry(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw/2.0, bw/2.0), 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
demod.setInput(vfo->output);
demod.start();
split.start();
reshape.start();
symSink.start();
sink.start();
enabled = true;
}
void disable() {
demod.stop();
split.stop();
reshape.stop();
symSink.stop();
sink.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
MeteorDemodulatorModule* _this = (MeteorDemodulatorModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (!_this->enabled) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
_this->constDiagram.draw();
if (_this->folderSelect.render("##meteor-recorder-" + _this->name)) {
if (_this->folderSelect.pathIsValid()) {
config.acquire();
config.conf[_this->name]["recPath"] = _this->folderSelect.path;
config.release(true);
}
}
if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); }
if (_this->recording) {
if (ImGui::Button(CONCAT("Stop##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) {
_this->stopRecording();
}
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %.2fMB", (float)_this->dataWritten / 1000000.0f);
}
else {
if (ImGui::Button(CONCAT("Record##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) {
_this->startRecording();
}
ImGui::Text("Idle --.--MB");
}
if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::endDisabled(); }
if (!_this->enabled) { style::endDisabled(); }
}
static void symSinkHandler(dsp::complex_t* data, int count, void* ctx) {
MeteorDemodulatorModule* _this = (MeteorDemodulatorModule*)ctx;
dsp::complex_t* buf = _this->constDiagram.acquireBuffer();
memcpy(buf, data, 1024 * sizeof(dsp::complex_t));
_this->constDiagram.releaseBuffer();
}
static void sinkHandler(dsp::complex_t* data, int count, void* ctx) {
MeteorDemodulatorModule* _this = (MeteorDemodulatorModule*)ctx;
std::lock_guard<std::mutex> lck(_this->recMtx);
if (!_this->recording) { return; }
for (int i = 0; i < count; i++) {
_this->writeBuffer[(2 * i)] = std::clamp<int>(data[i].re * 84.0f, -127, 127);
_this->writeBuffer[(2 * i) + 1] = std::clamp<int>(data[i].im * 84.0f, -127, 127);
}
_this->recFile.write((char*)_this->writeBuffer, count * 2);
_this->dataWritten += count * 2;
}
void startRecording() {
std::lock_guard<std::mutex> lck(recMtx);
dataWritten = 0;
std::string filename = genFileName(folderSelect.expandString(folderSelect.path) + "/meteor", ".s");
recFile = std::ofstream(filename, std::ios::binary);
if (recFile.is_open()) {
spdlog::info("Recording to '{0}'", filename);
recording = true;
}
else {
spdlog::error("Could not open file for recording!");
}
}
void stopRecording() {
std::lock_guard<std::mutex> lck(recMtx);
recording = false;
recFile.close();
dataWritten = 0;
}
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
MeteorDemodulatorModule* _this = (MeteorDemodulatorModule*)ctx;
if (code == METEOR_DEMODULATOR_IFACE_CMD_START) {
if (!_this->recording) { _this->startRecording(); }
}
else if (code == METEOR_DEMODULATOR_IFACE_CMD_STOP) {
if (_this->recording) { _this->stopRecording(); }
}
}
std::string name;
bool enabled = true;
// DSP Chain
VFOManager::VFO* vfo;
dsp::PSKDemod<4, false> demod;
dsp::Splitter<dsp::complex_t> split;
dsp::stream<dsp::complex_t> symSinkStream;
dsp::stream<dsp::complex_t> sinkStream;
dsp::Reshaper<dsp::complex_t> reshape;
dsp::HandlerSink<dsp::complex_t> symSink;
dsp::HandlerSink<dsp::complex_t> sink;
ImGui::ConstellationDiagram constDiagram;
FolderSelect folderSelect;
std::mutex recMtx;
bool recording = false;
uint64_t dataWritten = 0;
std::ofstream recFile;
int8_t* writeBuffer;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
if (!std::filesystem::exists(options::opts.root + "/recordings")) {
spdlog::warn("Recordings directory does not exist, creating it");
if (!std::filesystem::create_directory(options::opts.root + "/recordings")) {
spdlog::error("Could not create recordings directory");
}
}
json def = json({});
config.setPath(options::opts.root + "/meteor_demodulator_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new MeteorDemodulatorModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (MeteorDemodulatorModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -0,0 +1,6 @@
#pragma once
enum {
METEOR_DEMODULATOR_IFACE_CMD_START,
METEOR_DEMODULATOR_IFACE_CMD_STOP
};

View File

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.13)
project(radio)
if (MSVC)
add_compile_options(/O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
add_compile_options(-O3 -std=c++17)
endif ()
file(GLOB SRC "src/*.cpp")
include_directories("src/")
add_library(radio SHARED ${SRC})
target_link_libraries(radio PRIVATE sdrpp_core)
set_target_properties(radio PROPERTIES PREFIX "")
# Install directives
install(TARGETS radio DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,219 @@
#pragma once
#include <radio_demod.h>
#include <dsp/demodulator.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <dsp/audio.h>
#include <string>
#include <config.h>
#include <imgui.h>
class AMDemodulator : public Demodulator {
public:
AMDemodulator() {}
AMDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
init(prefix, vfo, audioSampleRate, bandWidth, config);
}
void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
uiPrefix = prefix;
_vfo = vfo;
audioSampRate = audioSampleRate;
bw = bandWidth;
_config = config;
_config->acquire();
if(_config->conf.contains(prefix)) {
if(!_config->conf[prefix].contains("AM")) {
_config->conf[prefix]["AM"]["bandwidth"] = bw;
_config->conf[prefix]["AM"]["snapInterval"] = snapInterval;
_config->conf[prefix]["AM"]["squelchLevel"] = squelchLevel;
}
json conf = _config->conf[prefix]["AM"];
if (conf.contains("bandwidth")) { bw = conf["bandwidth"]; }
if (conf.contains("snapInterval")) { snapInterval = conf["snapInterval"]; }
if (conf.contains("squelchLevel")) { squelchLevel = conf["squelchLevel"]; }
}
else {
_config->conf[prefix]["AM"]["bandwidth"] = bw;
_config->conf[prefix]["AM"]["snapInterval"] = snapInterval;
_config->conf[prefix]["AM"]["squelchLevel"] = squelchLevel;
}
_config->release(true);
squelch.init(_vfo->output, squelchLevel);
demod.init(&squelch.out);
agc.init(&demod.out, 20.0f, bbSampRate);
float audioBW = std::min<float>(audioSampRate / 2.0f, bw / 2.0f);
win.init(audioBW, audioBW, bbSampRate);
resamp.init(&agc.out, &win, bbSampRate, audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
resamp.updateWindow(&win);
m2s.init(&resamp.out);
onUserChangedBandwidthHandler.handler = vfoUserChangedBandwidthHandler;
onUserChangedBandwidthHandler.ctx = this;
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
void start() {
squelch.start();
demod.start();
agc.start();
resamp.start();
m2s.start();
running = true;
}
void stop() {
squelch.stop();
demod.stop();
agc.stop();
resamp.stop();
m2s.stop();
running = false;
}
bool isRunning() {
return running;
}
void select() {
_vfo->setSampleRate(bbSampRate, bw);
_vfo->setSnapInterval(snapInterval);
_vfo->setReference(ImGui::WaterfallVFO::REF_CENTER);
_vfo->setBandwidthLimits(bwMin, bwMax, false);
}
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
squelch.setInput(_vfo->output);
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
VFOManager::VFO* getVFO() {
return _vfo;
}
void setAudioSampleRate(float sampleRate) {
if (running) {
resamp.stop();
}
audioSampRate = sampleRate;
float audioBW = std::min<float>(audioSampRate / 2.0f, bw / 2.0f);
resamp.setOutSampleRate(audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
if (running) {
resamp.start();
}
}
float getAudioSampleRate() {
return audioSampRate;
}
dsp::stream<dsp::stereo_t>* getOutput() {
return &m2s.out;
}
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::InputFloat(("##_radio_am_bw_" + uiPrefix).c_str(), &bw, 1, 100, "%.0f", 0)) {
bw = std::clamp<float>(bw, bwMin, bwMax);
setBandwidth(bw);
_config->acquire();
_config->conf[uiPrefix]["AM"]["bandwidth"] = bw;
_config->release(true);
}
ImGui::LeftLabel("Snap Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputFloat(("##_radio_am_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, "%.0f", 0)) {
if (snapInterval < 1) { snapInterval = 1; }
setSnapInterval(snapInterval);
_config->acquire();
_config->conf[uiPrefix]["AM"]["snapInterval"] = snapInterval;
_config->release(true);
}
ImGui::LeftLabel("Squelch");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_am_squelch_" + uiPrefix).c_str(), &squelchLevel, -100.0f, 0.0f, "%.3fdB")) {
squelch.setLevel(squelchLevel);
_config->acquire();
_config->conf[uiPrefix]["AM"]["squelchLevel"] = squelchLevel;
_config->release(true);
}
}
static void vfoUserChangedBandwidthHandler(double newBw, void* ctx) {
AMDemodulator* _this = (AMDemodulator*)ctx;
if (_this->running) {
_this->bw = newBw;
_this->setBandwidth(_this->bw, false);
_this->_config->acquire();
_this->_config->conf[_this->uiPrefix]["AM"]["bandwidth"] = _this->bw;
_this->_config->release(true);
}
}
void setBandwidth(float bandWidth, bool updateWaterfall = true) {
bandWidth = std::clamp<float>(bandWidth, bwMin, bwMax);
bw = bandWidth;
_vfo->setBandwidth(bw, updateWaterfall);
float audioBW = std::min<float>(audioSampRate / 2.0f, bw / 2.0f);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
}
void saveParameters(bool lock = true) {
if (lock) { _config->acquire(); }
_config->conf[uiPrefix]["AM"]["bandwidth"] = bw;
_config->conf[uiPrefix]["AM"]["snapInterval"] = snapInterval;
_config->conf[uiPrefix]["AM"]["squelchLevel"] = squelchLevel;
if (lock) { _config->release(true); }
}
private:
void setSnapInterval(float snapInt) {
snapInterval = snapInt;
_vfo->setSnapInterval(snapInterval);
}
const float bwMax = 15000;
const float bwMin = 1000;
const float bbSampRate = 15000;
std::string uiPrefix;
float snapInterval = 1000;
float audioSampRate = 48000;
float bw = 12500;
bool running = false;
float squelchLevel = -100.0f;
VFOManager::VFO* _vfo;
dsp::Squelch squelch;
dsp::AMDemod demod;
dsp::AGC agc;
dsp::filter_window::BlackmanWindow win;
dsp::PolyphaseResampler<float> resamp;
dsp::MonoToStereo m2s;
ConfigManager* _config;
EventHandler<double> onUserChangedBandwidthHandler;
};

View File

@ -0,0 +1,224 @@
#pragma once
#include <radio_demod.h>
#include <dsp/demodulator.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <dsp/audio.h>
#include <string>
#include <config.h>
#include <imgui.h>
class CWDemodulator : public Demodulator {
public:
CWDemodulator() {}
CWDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
init(prefix, vfo, audioSampleRate, bandWidth, config);
}
void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
uiPrefix = prefix;
_vfo = vfo;
audioSampRate = audioSampleRate;
bw = bandWidth;
_config = config;
_config->acquire();
if(_config->conf.contains(prefix)) {
if(!_config->conf[prefix].contains("CW")) {
_config->conf[prefix]["CW"]["bandwidth"] = bw;
_config->conf[prefix]["CW"]["snapInterval"] = snapInterval;
_config->conf[prefix]["CW"]["squelchLevel"] = squelchLevel;
}
json conf = _config->conf[prefix]["CW"];
if (conf.contains("bandwidth")) { bw = conf["bandwidth"]; }
if (conf.contains("snapInterval")) { snapInterval = conf["snapInterval"]; }
if (conf.contains("squelchLevel")) { squelchLevel = conf["squelchLevel"]; }
}
else {
_config->conf[prefix]["CW"]["bandwidth"] = bw;
_config->conf[prefix]["CW"]["snapInterval"] = snapInterval;
_config->conf[prefix]["CW"]["squelchLevel"] = squelchLevel;
}
_config->release(true);
squelch.init(_vfo->output, squelchLevel);
xlator.init(&squelch.out, bbSampRate, 1000.0f);
c2r.init(&xlator.out);
agc.init(&c2r.out, 20.0f, bbSampRate);
float audioBW = std::min<float>(audioSampRate / 2.0f, (bw / 2.0f) + 1000.0f);
win.init(audioBW, audioBW, bbSampRate);
resamp.init(&agc.out, &win, bbSampRate, audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
resamp.updateWindow(&win);
m2s.init(&resamp.out);
onUserChangedBandwidthHandler.handler = vfoUserChangedBandwidthHandler;
onUserChangedBandwidthHandler.ctx = this;
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
void start() {
squelch.start();
xlator.start();
c2r.start();
agc.start();
resamp.start();
m2s.start();
running = true;
}
void stop() {
squelch.stop();
xlator.stop();
c2r.stop();
agc.stop();
resamp.stop();
m2s.stop();
running = false;
}
bool isRunning() {
return running;
}
void select() {
_vfo->setSampleRate(bbSampRate, bw);
_vfo->setSnapInterval(snapInterval);
_vfo->setReference(ImGui::WaterfallVFO::REF_CENTER);
_vfo->setBandwidthLimits(bwMin, bwMax, false);
}
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
squelch.setInput(_vfo->output);
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
VFOManager::VFO* getVFO() {
return _vfo;
}
void setAudioSampleRate(float sampleRate) {
if (running) {
resamp.stop();
}
audioSampRate = sampleRate;
float audioBW = std::min<float>(audioSampRate / 2.0f, (bw / 2.0f) + 1000.0f);
resamp.setOutSampleRate(audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
if (running) {
resamp.start();
}
}
float getAudioSampleRate() {
return audioSampRate;
}
dsp::stream<dsp::stereo_t>* getOutput() {
return &m2s.out;
}
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::InputFloat(("##_radio_cw_bw_" + uiPrefix).c_str(), &bw, 1, 100, "%.0f", 0)) {
bw = std::clamp<float>(bw, bwMin, bwMax);
setBandwidth(bw);
_config->acquire();
_config->conf[uiPrefix]["CW"]["bandwidth"] = bw;
_config->release(true);
}
ImGui::LeftLabel("Snap Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputFloat(("##_radio_cw_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, "%.0f", 0)) {
if (snapInterval < 1) { snapInterval = 1; }
setSnapInterval(snapInterval);
_config->acquire();
_config->conf[uiPrefix]["CW"]["snapInterval"] = snapInterval;
_config->release(true);
}
ImGui::LeftLabel("Squelch");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_cw_squelch_" + uiPrefix).c_str(), &squelchLevel, -100.0f, 0.0f, "%.3fdB")) {
squelch.setLevel(squelchLevel);
_config->acquire();
_config->conf[uiPrefix]["CW"]["squelchLevel"] = squelchLevel;
_config->release(true);
}
}
static void vfoUserChangedBandwidthHandler(double newBw, void* ctx) {
CWDemodulator* _this = (CWDemodulator*)ctx;
if (_this->running) {
_this->bw = newBw;
_this->setBandwidth(_this->bw, false);
_this->_config->acquire();
_this->_config->conf[_this->uiPrefix]["CW"]["bandwidth"] = _this->bw;
_this->_config->release(true);
}
}
void setBandwidth(float bandWidth, bool updateWaterfall = true) {
bandWidth = std::clamp<float>(bandWidth, bwMin, bwMax);
bw = bandWidth;
_vfo->setBandwidth(bw, updateWaterfall);
float audioBW = std::min<float>(audioSampRate / 2.0f, (bw / 2.0f) + 1000.0f);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
}
void saveParameters(bool lock = true) {
if (lock) { _config->acquire(); }
_config->conf[uiPrefix]["CW"]["bandwidth"] = bw;
_config->conf[uiPrefix]["CW"]["snapInterval"] = snapInterval;
_config->conf[uiPrefix]["CW"]["squelchLevel"] = squelchLevel;
if (lock) { _config->release(true); }
}
private:
void setSnapInterval(float snapInt) {
snapInterval = snapInt;
_vfo->setSnapInterval(snapInterval);
}
const float bwMax = 500;
const float bwMin = 50;
const float bbSampRate = 3000;
std::string uiPrefix;
float snapInterval = 10;
float audioSampRate = 48000;
float bw = 500;
bool running = false;
float squelchLevel = -100.0f;
VFOManager::VFO* _vfo;
dsp::Squelch squelch;
dsp::FrequencyXlator<dsp::complex_t> xlator;
dsp::ComplexToReal c2r;
dsp::AGC agc;
dsp::filter_window::BlackmanWindow win;
dsp::PolyphaseResampler<float> resamp;
dsp::MonoToStereo m2s;
ConfigManager* _config;
EventHandler<double> onUserChangedBandwidthHandler;
};

View File

@ -0,0 +1,214 @@
#pragma once
#include <radio_demod.h>
#include <dsp/demodulator.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <dsp/audio.h>
#include <string>
#include <config.h>
#include <imgui.h>
class DSBDemodulator : public Demodulator {
public:
DSBDemodulator() {}
DSBDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
init(prefix, vfo, audioSampleRate, bandWidth, config);
}
void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
uiPrefix = prefix;
_vfo = vfo;
audioSampRate = audioSampleRate;
bw = bandWidth;
_config = config;
_config->acquire();
if(_config->conf.contains(prefix)) {
if(!_config->conf[prefix].contains("DSB")) {
_config->conf[prefix]["DSB"]["bandwidth"] = bw;
_config->conf[prefix]["DSB"]["snapInterval"] = snapInterval;
_config->conf[prefix]["DSB"]["squelchLevel"] = squelchLevel;
}
json conf = _config->conf[prefix]["DSB"];
if (conf.contains("bandwidth")) { bw = conf["bandwidth"]; }
if (conf.contains("snapInterval")) { snapInterval = conf["snapInterval"]; }
if (conf.contains("squelchLevel")) { squelchLevel = conf["squelchLevel"]; }
}
else {
_config->conf[prefix]["DSB"]["bandwidth"] = bw;
_config->conf[prefix]["DSB"]["snapInterval"] = snapInterval;
_config->conf[prefix]["DSB"]["squelchLevel"] = squelchLevel;
}
_config->release(true);
squelch.init(_vfo->output, squelchLevel);
demod.init(&squelch.out, bbSampRate, bw, dsp::SSBDemod::MODE_DSB);
agc.init(&demod.out, 20.0f, bbSampRate);
float audioBW = std::min<float>(audioSampRate / 2.0f, bw / 2.0f);
win.init(audioBW, audioBW, bbSampRate);
resamp.init(&agc.out, &win, bbSampRate, audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
resamp.updateWindow(&win);
m2s.init(&resamp.out);
onUserChangedBandwidthHandler.handler = vfoUserChangedBandwidthHandler;
onUserChangedBandwidthHandler.ctx = this;
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
void start() {
squelch.start();
demod.start();
agc.start();
resamp.start();
m2s.start();
running = true;
}
void stop() {
squelch.stop();
demod.stop();
agc.stop();
resamp.stop();
m2s.stop();
running = false;
}
bool isRunning() {
return running;
}
void select() {
_vfo->setSampleRate(bbSampRate, bw);
_vfo->setSnapInterval(snapInterval);
_vfo->setReference(ImGui::WaterfallVFO::REF_CENTER);
_vfo->setBandwidthLimits(bwMin, bwMax, false);
}
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
squelch.setInput(_vfo->output);
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
VFOManager::VFO* getVFO() {
return _vfo;
}
void setAudioSampleRate(float sampleRate) {
if (running) {
resamp.stop();
}
audioSampRate = sampleRate;
float audioBW = std::min<float>(audioSampRate / 2.0f, bw / 2.0f);
resamp.setOutSampleRate(audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
if (running) {
resamp.start();
}
}
float getAudioSampleRate() {
return audioSampRate;
}
dsp::stream<dsp::stereo_t>* getOutput() {
return &m2s.out;
}
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::InputFloat(("##_radio_dsb_bw_" + uiPrefix).c_str(), &bw, 1, 100, "%.0f", 0)) {
bw = std::clamp<float>(bw, bwMin, bwMax);
setBandwidth(bw);
_config->acquire();
_config->conf[uiPrefix]["DSB"]["bandwidth"] = bw;
_config->release(true);
}
ImGui::LeftLabel("Snap Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputFloat(("##_radio_dsb_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, "%.0f", 0)) {
if (snapInterval < 1) { snapInterval = 1; }
setSnapInterval(snapInterval);
_config->acquire();
_config->conf[uiPrefix]["DSB"]["snapInterval"] = snapInterval;
_config->release(true);
}
ImGui::LeftLabel("Squelch");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_dsb_squelch_" + uiPrefix).c_str(), &squelchLevel, -100.0f, 0.0f, "%.3fdB")) {
squelch.setLevel(squelchLevel);
_config->acquire();
_config->conf[uiPrefix]["DSB"]["squelchLevel"] = squelchLevel;
_config->release(true);
}
}
static void vfoUserChangedBandwidthHandler(double newBw, void* ctx) {
DSBDemodulator* _this = (DSBDemodulator*)ctx;
if (_this->running) {
_this->bw = newBw;
_this->setBandwidth(_this->bw, false);
_this->_config->acquire();
_this->_config->conf[_this->uiPrefix]["DSB"]["bandwidth"] = _this->bw;
_this->_config->release(true);
}
}
void setBandwidth(float bandWidth, bool updateWaterfall = true) {
bandWidth = std::clamp<float>(bandWidth, bwMin, bwMax);
bw = bandWidth;
_vfo->setBandwidth(bw, updateWaterfall);
}
void saveParameters(bool lock = true) {
if (lock) { _config->acquire(); }
_config->conf[uiPrefix]["DSB"]["bandwidth"] = bw;
_config->conf[uiPrefix]["DSB"]["snapInterval"] = snapInterval;
_config->conf[uiPrefix]["DSB"]["squelchLevel"] = squelchLevel;
if (lock) { _config->release(true); }
}
private:
void setSnapInterval(float snapInt) {
snapInterval = snapInt;
_vfo->setSnapInterval(snapInterval);
}
const float bwMax = 12000;
const float bwMin = 1000;
const float bbSampRate = 12000;
std::string uiPrefix;
float snapInterval = 100;
float audioSampRate = 48000;
float bw = 6000;
bool running = false;
float squelchLevel = -100.0f;
VFOManager::VFO* _vfo;
dsp::Squelch squelch;
dsp::SSBDemod demod;
dsp::AGC agc;
dsp::filter_window::BlackmanWindow win;
dsp::PolyphaseResampler<float> resamp;
dsp::MonoToStereo m2s;
ConfigManager* _config;
EventHandler<double> onUserChangedBandwidthHandler;
};

View File

@ -0,0 +1,206 @@
#pragma once
#include <radio_demod.h>
#include <dsp/demodulator.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <dsp/audio.h>
#include <string>
#include <config.h>
#include <imgui.h>
class FMDemodulator : public Demodulator {
public:
FMDemodulator() {}
FMDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
init(prefix, vfo, audioSampleRate, bandWidth, config);
}
void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
uiPrefix = prefix;
_vfo = vfo;
audioSampRate = audioSampleRate;
bw = bandWidth;
_config = config;
_config->acquire();
if(_config->conf.contains(prefix)) {
if(!_config->conf[prefix].contains("FM")) {
_config->conf[prefix]["FM"]["bandwidth"] = bw;
_config->conf[prefix]["FM"]["snapInterval"] = snapInterval;
_config->conf[prefix]["FM"]["squelchLevel"] = squelchLevel;
}
json conf = _config->conf[prefix]["FM"];
if (conf.contains("bandwidth")) { bw = conf["bandwidth"]; }
if (conf.contains("snapInterval")) { snapInterval = conf["snapInterval"]; }
if (conf.contains("squelchLevel")) { squelchLevel = conf["squelchLevel"]; }
}
else {
_config->conf[prefix]["FM"]["bandwidth"] = bw;
_config->conf[prefix]["FM"]["snapInterval"] = snapInterval;
_config->conf[prefix]["FM"]["squelchLevel"] = squelchLevel;
}
_config->release(true);
squelch.init(_vfo->output, squelchLevel);
demod.init(&squelch.out, bbSampRate, bw / 2.0f);
float audioBW = std::min<float>(audioSampleRate / 2.0f, bw / 2.0f);
win.init(audioBW, audioBW, bbSampRate);
resamp.init(&demod.out, &win, bbSampRate, audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
resamp.updateWindow(&win);
onUserChangedBandwidthHandler.handler = vfoUserChangedBandwidthHandler;
onUserChangedBandwidthHandler.ctx = this;
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
void start() {
squelch.start();
demod.start();
resamp.start();
running = true;
}
void stop() {
squelch.stop();
demod.stop();
resamp.stop();
running = false;
}
bool isRunning() {
return running;
}
void select() {
_vfo->setSampleRate(bbSampRate, bw);
_vfo->setSnapInterval(snapInterval);
_vfo->setReference(ImGui::WaterfallVFO::REF_CENTER);
_vfo->setBandwidthLimits(bwMin, bwMax, false);
}
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
squelch.setInput(_vfo->output);
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
VFOManager::VFO* getVFO() {
return _vfo;
}
void setAudioSampleRate(float sampleRate) {
if (running) {
resamp.stop();
}
audioSampRate = sampleRate;
float audioBW = std::min<float>(audioSampRate / 2.0f, bw / 2.0f);
resamp.setOutSampleRate(audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
if (running) {
resamp.start();
}
}
float getAudioSampleRate() {
return audioSampRate;
}
dsp::stream<dsp::stereo_t>* getOutput() {
return &resamp.out;
}
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::InputFloat(("##_radio_fm_bw_" + uiPrefix).c_str(), &bw, 1, 100, "%.0f", 0)) {
bw = std::clamp<float>(bw, bwMin, bwMax);
setBandwidth(bw);
_config->acquire();
_config->conf[uiPrefix]["FM"]["bandwidth"] = bw;
_config->release(true);
}
ImGui::LeftLabel("Snap Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputFloat(("##_radio_fm_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, "%.0f", 0)) {
if (snapInterval < 1) { snapInterval = 1; }
setSnapInterval(snapInterval);
_config->acquire();
_config->conf[uiPrefix]["FM"]["snapInterval"] = snapInterval;
_config->release(true);
}
ImGui::LeftLabel("Squelch");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_fm_squelch_" + uiPrefix).c_str(), &squelchLevel, -100.0f, 0.0f, "%.3fdB")) {
squelch.setLevel(squelchLevel);
_config->acquire();
_config->conf[uiPrefix]["FM"]["squelchLevel"] = squelchLevel;
_config->release(true);
}
}
static void vfoUserChangedBandwidthHandler(double newBw, void* ctx) {
FMDemodulator* _this = (FMDemodulator*)ctx;
if (_this->running) {
_this->bw = newBw;
_this->setBandwidth(_this->bw, false);
_this->_config->acquire();
_this->_config->conf[_this->uiPrefix]["FM"]["bandwidth"] = _this->bw;
_this->_config->release(true);
}
}
void setBandwidth(float bandWidth, bool updateWaterfall = true) {
bandWidth = std::clamp<float>(bandWidth, bwMin, bwMax);
bw = bandWidth;
_vfo->setBandwidth(bw, updateWaterfall);
demod.setDeviation(bw / 2.0f);
setAudioSampleRate(audioSampRate);
}
void saveParameters(bool lock = true) {
if (lock) { _config->acquire(); }
_config->conf[uiPrefix]["FM"]["bandwidth"] = bw;
_config->conf[uiPrefix]["FM"]["snapInterval"] = snapInterval;
_config->conf[uiPrefix]["FM"]["squelchLevel"] = squelchLevel;
if (lock) { _config->release(true); }
}
private:
void setSnapInterval(float snapInt) {
snapInterval = snapInt;
_vfo->setSnapInterval(snapInterval);
}
const float bwMax = 50000;
const float bwMin = 1000;
const float bbSampRate = 50000;
std::string uiPrefix;
float snapInterval = 2500;
float audioSampRate = 48000;
float bw = 50000;
bool running = false;
float squelchLevel = -100.0f;
VFOManager::VFO* _vfo;
dsp::Squelch squelch;
dsp::FMDemod demod;
dsp::filter_window::BlackmanWindow win;
dsp::PolyphaseResampler<dsp::stereo_t> resamp;
ConfigManager* _config;
EventHandler<double> onUserChangedBandwidthHandler;
};

View File

@ -0,0 +1,220 @@
#pragma once
#include <radio_demod.h>
#include <dsp/demodulator.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <dsp/audio.h>
#include <string>
#include <config.h>
#include <imgui.h>
class LSBDemodulator : public Demodulator {
public:
LSBDemodulator() {}
LSBDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
init(prefix, vfo, audioSampleRate, bandWidth, config);
}
void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
uiPrefix = prefix;
_vfo = vfo;
audioSampRate = audioSampleRate;
bw = bandWidth;
_config = config;
_config->acquire();
if(_config->conf.contains(prefix)) {
if(!_config->conf[prefix].contains("LSB")) {
_config->conf[prefix]["LSB"]["bandwidth"] = bw;
_config->conf[prefix]["LSB"]["snapInterval"] = snapInterval;
_config->conf[prefix]["LSB"]["squelchLevel"] = squelchLevel;
}
json conf = _config->conf[prefix]["LSB"];
if (conf.contains("bandwidth")) { bw = conf["bandwidth"]; }
if (conf.contains("snapInterval")) { snapInterval = conf["snapInterval"]; }
if (conf.contains("squelchLevel")) { squelchLevel = conf["squelchLevel"]; }
}
else {
_config->conf[prefix]["LSB"]["bandwidth"] = bw;
_config->conf[prefix]["LSB"]["snapInterval"] = snapInterval;
_config->conf[prefix]["LSB"]["squelchLevel"] = squelchLevel;
}
_config->release(true);
squelch.init(_vfo->output, squelchLevel);
demod.init(&squelch.out, bbSampRate, bw, dsp::SSBDemod::MODE_LSB);
agc.init(&demod.out, 20.0f, bbSampRate);
float audioBW = std::min<float>(audioSampRate / 2.0f, bw);
win.init(audioBW, audioBW, bbSampRate);
resamp.init(&agc.out, &win, bbSampRate, audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
resamp.updateWindow(&win);
m2s.init(&resamp.out);
onUserChangedBandwidthHandler.handler = vfoUserChangedBandwidthHandler;
onUserChangedBandwidthHandler.ctx = this;
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
void start() {
squelch.start();
demod.start();
agc.start();
resamp.start();
m2s.start();
running = true;
}
void stop() {
squelch.stop();
demod.stop();
agc.stop();
resamp.stop();
m2s.stop();
running = false;
}
bool isRunning() {
return running;
}
void select() {
_vfo->setSampleRate(bbSampRate, bw);
_vfo->setSnapInterval(snapInterval);
_vfo->setReference(ImGui::WaterfallVFO::REF_UPPER);
_vfo->setBandwidthLimits(bwMin, bwMax, false);
}
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
squelch.setInput(_vfo->output);
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
VFOManager::VFO* getVFO() {
return _vfo;
}
void setAudioSampleRate(float sampleRate) {
if (running) {
resamp.stop();
}
audioSampRate = sampleRate;
float audioBW = std::min<float>(audioSampRate / 2.0f, bw);
resamp.setOutSampleRate(audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
if (running) {
resamp.start();
}
}
float getAudioSampleRate() {
return audioSampRate;
}
dsp::stream<dsp::stereo_t>* getOutput() {
return &m2s.out;
}
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::InputFloat(("##_radio_lsb_bw_" + uiPrefix).c_str(), &bw, 1, 100, "%.0f", 0)) {
bw = std::clamp<float>(bw, bwMin, bwMax);
setBandwidth(bw);
_config->acquire();
_config->conf[uiPrefix]["LSB"]["bandwidth"] = bw;
_config->release(true);
}
ImGui::LeftLabel("Snap Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputFloat(("##_radio_lsb_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, "%.0f", 0)) {
if (snapInterval < 1) { snapInterval = 1; }
setSnapInterval(snapInterval);
_config->acquire();
_config->conf[uiPrefix]["LSB"]["snapInterval"] = snapInterval;
_config->release(true);
}
ImGui::LeftLabel("Squelch");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_lsb_squelch_" + uiPrefix).c_str(), &squelchLevel, -100.0f, 0.0f, "%.3fdB")) {
squelch.setLevel(squelchLevel);
_config->acquire();
_config->conf[uiPrefix]["LSB"]["squelchLevel"] = squelchLevel;
_config->release(true);
}
}
static void vfoUserChangedBandwidthHandler(double newBw, void* ctx) {
LSBDemodulator* _this = (LSBDemodulator*)ctx;
if (_this->running) {
_this->bw = newBw;
_this->setBandwidth(_this->bw, false);
_this->_config->acquire();
_this->_config->conf[_this->uiPrefix]["LSB"]["bandwidth"] = _this->bw;
_this->_config->release(true);
}
}
void setBandwidth(float bandWidth, bool updateWaterfall = true) {
bandWidth = std::clamp<float>(bandWidth, bwMin, bwMax);
bw = bandWidth;
_vfo->setBandwidth(bw, updateWaterfall);
demod.setBandWidth(bw);
float audioBW = std::min<float>(audioSampRate / 2.0f, bw);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
}
void saveParameters(bool lock = true) {
if (lock) { _config->acquire(); }
_config->conf[uiPrefix]["LSB"]["bandwidth"] = bw;
_config->conf[uiPrefix]["LSB"]["snapInterval"] = snapInterval;
_config->conf[uiPrefix]["LSB"]["squelchLevel"] = squelchLevel;
if (lock) { _config->release(true); }
}
private:
void setSnapInterval(float snapInt) {
snapInterval = snapInt;
_vfo->setSnapInterval(snapInterval);
}
const float bwMax = 12000;
const float bwMin = 500;
const float bbSampRate = 24000;
std::string uiPrefix;
float snapInterval = 100;
float audioSampRate = 48000;
float bw = 3000;
bool running = false;
float squelchLevel = -100.0f;
VFOManager::VFO* _vfo;
dsp::Squelch squelch;
dsp::SSBDemod demod;
dsp::AGC agc;
dsp::filter_window::BlackmanWindow win;
dsp::PolyphaseResampler<float> resamp;
dsp::MonoToStereo m2s;
ConfigManager* _config;
EventHandler<double> onUserChangedBandwidthHandler;
};

View File

@ -0,0 +1,253 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/gui.h>
#include <gui/style.h>
#include <signal_path/signal_path.h>
#include <radio_demod.h>
#include <module.h>
#include <wfm_demod.h>
#include <fm_demod.h>
#include <am_demod.h>
#include <usb_demod.h>
#include <lsb_demod.h>
#include <dsb_demod.h>
#include <raw_demod.h>
#include <cw_demod.h>
#include <options.h>
#include <radio_interface.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "radio",
/* Description: */ "Radio module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 3, 0,
/* Max instances */ -1
};
static ConfigManager config;
class RadioModule : public ModuleManager::Instance {
public:
RadioModule(std::string name) {
this->name = name;
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 200000, 200000, 50000, 200000, false);
ns.init(vfo->output);
config.acquire();
if (!config.conf.contains(name)) {
config.conf[name]["selectedDemodId"] = 1;
}
demodId = config.conf[name]["selectedDemodId"];
config.release(true);
wfmDemod.init(name, vfo, audioSampRate, 200000, &config);
fmDemod.init(name, vfo, audioSampRate, 12500, &config);
amDemod.init(name, vfo, audioSampRate, 12500, &config);
usbDemod.init(name, vfo, audioSampRate, 3000, &config);
lsbDemod.init(name, vfo, audioSampRate, 3000, &config);
dsbDemod.init(name, vfo, audioSampRate, 6000, &config);
rawDemod.init(name, vfo, audioSampRate, audioSampRate, &config);
cwDemod.init(name, vfo, audioSampRate, 200, &config);
srChangeHandler.ctx = this;
srChangeHandler.handler = sampleRateChangeHandler;
stream.init(wfmDemod.getOutput(), &srChangeHandler, audioSampRate);
sigpath::sinkManager.registerStream(name, &stream);
selectDemodById(demodId);
stream.start();
gui::menu.registerEntry(name, menuHandler, this, this);
core::modComManager.registerInterface("radio", name, moduleInterfaceHandler, this);
}
~RadioModule() {
core::modComManager.unregisterInterface(name);
gui::menu.removeEntry(name);
stream.stop();
if (enabled) {
currentDemod->stop();
sigpath::vfoManager.deleteVFO(vfo);
}
sigpath::sinkManager.unregisterStream(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw/2.0, bw/2.0), 200000, 200000, 50000, 200000, false);
wfmDemod.setVFO(vfo);
fmDemod.setVFO(vfo);
amDemod.setVFO(vfo);
usbDemod.setVFO(vfo);
lsbDemod.setVFO(vfo);
dsbDemod.setVFO(vfo);
rawDemod.setVFO(vfo);
cwDemod.setVFO(vfo);
currentDemod->select();
currentDemod->start();
enabled = true;
}
void disable() {
currentDemod->stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
RadioModule* _this = (RadioModule*)ctx;
if (!_this->enabled) { style::beginDisabled(); }
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::BeginGroup();
// TODO: Change VFO ref in signal path
ImGui::Columns(4, CONCAT("RadioModeColumns##_", _this->name), false);
if (ImGui::RadioButton(CONCAT("NFM##_", _this->name), _this->demodId == 0) && _this->demodId != 0) {
_this->selectDemodById(0);
}
if (ImGui::RadioButton(CONCAT("WFM##_", _this->name), _this->demodId == 1) && _this->demodId != 1) {
_this->selectDemodById(1);
}
ImGui::NextColumn();
if (ImGui::RadioButton(CONCAT("AM##_", _this->name), _this->demodId == 2) && _this->demodId != 2) {
_this->selectDemodById(2);
}
if (ImGui::RadioButton(CONCAT("DSB##_", _this->name), _this->demodId == 3) && _this->demodId != 3) {
_this->selectDemodById(3);
}
ImGui::NextColumn();
if (ImGui::RadioButton(CONCAT("USB##_", _this->name), _this->demodId == 4) && _this->demodId != 4) {
_this->selectDemodById(4);
}
if (ImGui::RadioButton(CONCAT("CW##_", _this->name), _this->demodId == 5) && _this->demodId != 5) {
_this->selectDemodById(5);
};
ImGui::NextColumn();
if (ImGui::RadioButton(CONCAT("LSB##_", _this->name), _this->demodId == 6) && _this->demodId != 6) {
_this->selectDemodById(6);
}
if (ImGui::RadioButton(CONCAT("RAW##_", _this->name), _this->demodId == 7) && _this->demodId != 7) {
_this->selectDemodById(7);
};
ImGui::Columns(1, CONCAT("EndRadioModeColumns##_", _this->name), false);
ImGui::EndGroup();
_this->currentDemod->showMenu();
if (!_this->enabled) { style::endDisabled(); }
}
static void sampleRateChangeHandler(float sampleRate, void* ctx) {
RadioModule* _this = (RadioModule*)ctx;
// TODO: If too slow, change all demods here and not when setting
_this->audioSampRate = sampleRate;
if (_this->currentDemod != NULL) {
_this->currentDemod->setAudioSampleRate(_this->audioSampRate);
}
}
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
RadioModule* _this = (RadioModule*)ctx;
if (code == RADIO_IFACE_CMD_GET_MODE) {
int* _out = (int*)out;
*_out = _this->demodId;
}
else if (code == RADIO_IFACE_CMD_SET_MODE) {
int* _in = (int*)in;
if (*_in != _this->demodId) { _this->selectDemodById(*_in); }
}
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH) {
float* _in = (float*)in;
_this->currentDemod->setBandwidth(*_in, true);
_this->currentDemod->saveParameters();
}
}
void selectDemod(Demodulator* demod) {
if (currentDemod != NULL) { currentDemod->stop(); }
currentDemod = demod;
currentDemod->setAudioSampleRate(audioSampRate);
stream.setInput(currentDemod->getOutput());
currentDemod->select();
vfo->output->flush();
currentDemod->start();
}
void selectDemodById(int id) {
demodId = id;
if (id == 0) { selectDemod(&fmDemod); }
if (id == 1) { selectDemod(&wfmDemod); }
if (id == 2) { selectDemod(&amDemod); }
if (id == 3) { selectDemod(&dsbDemod); }
if (id == 4) { selectDemod(&usbDemod); }
if (id == 5) { selectDemod(&cwDemod); }
if (id == 6) { selectDemod(&lsbDemod); }
if (id == 7) { selectDemod(&rawDemod); }
config.acquire();
config.conf[name]["selectedDemodId"] = demodId;
config.release(true);
}
std::string name;
bool enabled = true;
int demodId = 0;
float audioSampRate = 48000;
Demodulator* currentDemod = NULL;
VFOManager::VFO* vfo;
WFMDemodulator wfmDemod;
FMDemodulator fmDemod;
AMDemodulator amDemod;
USBDemodulator usbDemod;
LSBDemodulator lsbDemod;
DSBDemodulator dsbDemod;
RAWDemodulator rawDemod;
CWDemodulator cwDemod;
dsp::NullSink<dsp::complex_t> ns;
EventHandler<float> srChangeHandler;
SinkManager::Stream stream;
};
MOD_EXPORT void _INIT_() {
json def = json({});
config.setPath(options::opts.root + "/radio_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new RadioModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (RadioModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <dsp/stream.h>
#include <signal_path/vfo_manager.h>
class Demodulator {
public:
virtual void start() = 0;
virtual void stop() = 0;
virtual bool isRunning() = 0;
virtual void select() = 0;
virtual void setVFO(VFOManager::VFO* vfo) = 0;
virtual VFOManager::VFO* getVFO() = 0;
virtual void setAudioSampleRate(float sampleRate) = 0;
virtual float getAudioSampleRate() = 0;
virtual void setBandwidth(float bandWidth, bool updateWaterfall = true) = 0;
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
virtual void showMenu() = 0;
virtual void saveParameters(bool lock = true) = 0;
};

View File

@ -0,0 +1,18 @@
#pragma once
enum {
RADIO_IFACE_CMD_GET_MODE,
RADIO_IFACE_CMD_SET_MODE,
RADIO_IFACE_CMD_SET_BANDWIDTH,
};
enum {
RADIO_IFACE_MODE_NFM,
RADIO_IFACE_MODE_WFM,
RADIO_IFACE_MODE_AM,
RADIO_IFACE_MODE_DSB,
RADIO_IFACE_MODE_USB,
RADIO_IFACE_MODE_CW,
RADIO_IFACE_MODE_LSB,
RADIO_IFACE_MODE_RAW
};

View File

@ -0,0 +1,144 @@
#pragma once
#include <radio_demod.h>
#include <dsp/conversion.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <dsp/audio.h>
#include <string>
#include <config.h>
#include <imgui.h>
class RAWDemodulator : public Demodulator {
public:
RAWDemodulator() {}
RAWDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
init(prefix, vfo, audioSampleRate, bandWidth, config);
}
void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
uiPrefix = prefix;
_vfo = vfo;
audioSampRate = audioSampleRate;
bw = bandWidth;
_config = config;
_config->acquire();
if(_config->conf.contains(prefix)) {
if(!_config->conf[prefix].contains("RAW")) {
_config->conf[prefix]["RAW"]["snapInterval"] = snapInterval;
_config->conf[prefix]["RAW"]["squelchLevel"] = squelchLevel;
}
json conf = _config->conf[prefix]["RAW"];
if (conf.contains("snapInterval")) { snapInterval = conf["snapInterval"]; }
if (conf.contains("squelchLevel")) { squelchLevel = conf["squelchLevel"]; }
}
else {
_config->conf[prefix]["RAW"]["snapInterval"] = snapInterval;
_config->conf[prefix]["RAW"]["squelchLevel"] = squelchLevel;
}
_config->release(true);
squelch.init(_vfo->output, squelchLevel);
}
void start() {
squelch.start();
running = true;
}
void stop() {
squelch.stop();
running = false;
}
bool isRunning() {
return running;
}
void select() {
_vfo->setSampleRate(audioSampRate, audioSampRate);
_vfo->setSnapInterval(snapInterval);
_vfo->setReference(ImGui::WaterfallVFO::REF_CENTER);
_vfo->setBandwidthLimits(0, 0, true);
}
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
squelch.setInput(_vfo->output);
}
VFOManager::VFO* getVFO() {
return _vfo;
}
void setAudioSampleRate(float sampleRate) {
audioSampRate = sampleRate;
if (running) {
_vfo->setSampleRate(audioSampRate, audioSampRate);
}
}
float getAudioSampleRate() {
return audioSampRate;
}
dsp::stream<dsp::stereo_t>* getOutput() {
return (dsp::stream<dsp::stereo_t>*)&squelch.out;
}
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::LeftLabel("Snap Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputFloat(("##_radio_raw_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, "%.0f", 0)) {
if (snapInterval < 1) { snapInterval = 1; }
setSnapInterval(snapInterval);
_config->acquire();
_config->conf[uiPrefix]["RAW"]["snapInterval"] = snapInterval;
_config->release(true);
}
ImGui::LeftLabel("Squelch");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_raw_squelch_" + uiPrefix).c_str(), &squelchLevel, -100.0f, 0.0f, "%.3fdB")) {
squelch.setLevel(squelchLevel);
_config->acquire();
_config->conf[uiPrefix]["RAW"]["squelchLevel"] = squelchLevel;
_config->release(true);
}
// TODO: Allow selection of the bandwidth
}
void setBandwidth(float bandWidth, bool updateWaterfall = true) {
// Do nothing
}
void saveParameters(bool lock = true) {
if (lock) { _config->acquire(); }
_config->conf[uiPrefix]["RAW"]["snapInterval"] = snapInterval;
_config->conf[uiPrefix]["RAW"]["squelchLevel"] = squelchLevel;
if (lock) { _config->release(true); }
}
private:
void setSnapInterval(float snapInt) {
snapInterval = snapInt;
_vfo->setSnapInterval(snapInterval);
}
std::string uiPrefix;
float snapInterval = 10000;
float audioSampRate = 48000;
float bw = 12500;
bool running = false;
float squelchLevel = -100.0f;
VFOManager::VFO* _vfo;
dsp::Squelch squelch;
ConfigManager* _config;
};

View File

@ -0,0 +1,220 @@
#pragma once
#include <radio_demod.h>
#include <dsp/demodulator.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <dsp/audio.h>
#include <string>
#include <config.h>
#include <imgui.h>
class USBDemodulator : public Demodulator {
public:
USBDemodulator() {}
USBDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
init(prefix, vfo, audioSampleRate, bandWidth, config);
}
void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
uiPrefix = prefix;
_vfo = vfo;
audioSampRate = audioSampleRate;
bw = bandWidth;
_config = config;
_config->acquire();
if(_config->conf.contains(prefix)) {
if(!_config->conf[prefix].contains("USB")) {
_config->conf[prefix]["USB"]["bandwidth"] = bw;
_config->conf[prefix]["USB"]["snapInterval"] = snapInterval;
_config->conf[prefix]["USB"]["squelchLevel"] = squelchLevel;
}
json conf = _config->conf[prefix]["USB"];
if (conf.contains("bandwidth")) { bw = conf["bandwidth"]; }
if (conf.contains("snapInterval")) { snapInterval = conf["snapInterval"]; }
if (conf.contains("squelchLevel")) { squelchLevel = conf["squelchLevel"]; }
}
else {
_config->conf[prefix]["USB"]["bandwidth"] = bw;
_config->conf[prefix]["USB"]["snapInterval"] = snapInterval;
_config->conf[prefix]["USB"]["squelchLevel"] = squelchLevel;
}
_config->release(true);
squelch.init(_vfo->output, squelchLevel);
demod.init(&squelch.out, bbSampRate, bw, dsp::SSBDemod::MODE_USB);
agc.init(&demod.out, 20.0f, bbSampRate);
float audioBW = std::min<float>(audioSampRate / 2.0f, bw);
win.init(audioBW, audioBW, bbSampRate);
resamp.init(&agc.out, &win, bbSampRate, audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
resamp.updateWindow(&win);
m2s.init(&resamp.out);
onUserChangedBandwidthHandler.handler = vfoUserChangedBandwidthHandler;
onUserChangedBandwidthHandler.ctx = this;
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
void start() {
squelch.start();
demod.start();
agc.start();
resamp.start();
m2s.start();
running = true;
}
void stop() {
squelch.stop();
demod.stop();
agc.stop();
resamp.stop();
m2s.stop();
running = false;
}
bool isRunning() {
return running;
}
void select() {
_vfo->setSampleRate(bbSampRate, bw);
_vfo->setSnapInterval(snapInterval);
_vfo->setReference(ImGui::WaterfallVFO::REF_LOWER);
_vfo->setBandwidthLimits(bwMin, bwMax, false);
}
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
squelch.setInput(_vfo->output);
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
VFOManager::VFO* getVFO() {
return _vfo;
}
void setAudioSampleRate(float sampleRate) {
if (running) {
resamp.stop();
}
audioSampRate = sampleRate;
float audioBW = std::min<float>(audioSampRate / 2.0f, bw);
resamp.setOutSampleRate(audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
if (running) {
resamp.start();
}
}
float getAudioSampleRate() {
return audioSampRate;
}
dsp::stream<dsp::stereo_t>* getOutput() {
return &m2s.out;
}
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::InputFloat(("##_radio_usb_bw_" + uiPrefix).c_str(), &bw, 1, 100, "%.0f", 0)) {
bw = std::clamp<float>(bw, bwMin, bwMax);
setBandwidth(bw);
_config->acquire();
_config->conf[uiPrefix]["USB"]["bandwidth"] = bw;
_config->release(true);
}
ImGui::LeftLabel("Snap Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputFloat(("##_radio_usb_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, "%.0f", 0)) {
if (snapInterval < 1) { snapInterval = 1; }
setSnapInterval(snapInterval);
_config->acquire();
_config->conf[uiPrefix]["USB"]["snapInterval"] = snapInterval;
_config->release(true);
}
ImGui::LeftLabel("Squelch");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_usb_squelch_" + uiPrefix).c_str(), &squelchLevel, -100.0f, 0.0f, "%.3fdB")) {
squelch.setLevel(squelchLevel);
_config->acquire();
_config->conf[uiPrefix]["USB"]["squelchLevel"] = squelchLevel;
_config->release(true);
}
}
static void vfoUserChangedBandwidthHandler(double newBw, void* ctx) {
USBDemodulator* _this = (USBDemodulator*)ctx;
if (_this->running) {
_this->bw = newBw;
_this->setBandwidth(_this->bw, false);
_this->_config->acquire();
_this->_config->conf[_this->uiPrefix]["USB"]["bandwidth"] = _this->bw;
_this->_config->release(true);
}
}
void setBandwidth(float bandWidth, bool updateWaterfall = true) {
bandWidth = std::clamp<float>(bandWidth, bwMin, bwMax);
bw = bandWidth;
_vfo->setBandwidth(bw, updateWaterfall);
demod.setBandWidth(bw);
float audioBW = std::min<float>(audioSampRate / 2.0f, bw);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
}
void saveParameters(bool lock = true) {
if (lock) { _config->acquire(); }
_config->conf[uiPrefix]["USB"]["bandwidth"] = bw;
_config->conf[uiPrefix]["USB"]["snapInterval"] = snapInterval;
_config->conf[uiPrefix]["USB"]["squelchLevel"] = squelchLevel;
if (lock) { _config->release(true); }
}
private:
void setSnapInterval(float snapInt) {
snapInterval = snapInt;
_vfo->setSnapInterval(snapInterval);
}
const float bwMax = 12000;
const float bwMin = 500;
const float bbSampRate = 24000;
std::string uiPrefix;
float snapInterval = 100;
float audioSampRate = 48000;
float bw = 3000;
bool running = false;
float squelchLevel = -100.0f;
VFOManager::VFO* _vfo;
dsp::Squelch squelch;
dsp::SSBDemod demod;
dsp::AGC agc;
dsp::filter_window::BlackmanWindow win;
dsp::PolyphaseResampler<float> resamp;
dsp::MonoToStereo m2s;
ConfigManager* _config;
EventHandler<double> onUserChangedBandwidthHandler;
};

View File

@ -0,0 +1,295 @@
#pragma once
#include <radio_demod.h>
#include <dsp/demodulator.h>
#include <dsp/resampling.h>
#include <dsp/filter.h>
#include <dsp/audio.h>
#include <string>
#include <config.h>
#include <imgui.h>
class WFMDemodulator : public Demodulator {
public:
WFMDemodulator() {}
WFMDemodulator(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
init(prefix, vfo, audioSampleRate, bandWidth, config);
}
void init(std::string prefix, VFOManager::VFO* vfo, float audioSampleRate, float bandWidth, ConfigManager* config) {
uiPrefix = prefix;
_vfo = vfo;
audioSampRate = audioSampleRate;
bw = bandWidth;
_config = config;
_config->acquire();
if(_config->conf.contains(prefix)) {
if(!_config->conf[prefix].contains("WFM")) {
_config->conf[prefix]["WFM"]["bandwidth"] = bw;
_config->conf[prefix]["WFM"]["snapInterval"] = snapInterval;
_config->conf[prefix]["WFM"]["deempMode"] = deempId;
_config->conf[prefix]["WFM"]["squelchLevel"] = squelchLevel;
_config->conf[prefix]["WFM"]["stereo"] = false;
}
// Correct for new settings
if (!config->conf[prefix]["WFM"].contains("stereo")) { _config->conf[prefix]["WFM"]["stereo"] = false;}
json conf = _config->conf[prefix]["WFM"];
bw = conf["bandwidth"];
snapInterval = conf["snapInterval"];
deempId = conf["deempMode"];
squelchLevel = conf["squelchLevel"];
stereo = conf["stereo"];
}
else {
_config->conf[prefix]["WFM"]["bandwidth"] = bw;
_config->conf[prefix]["WFM"]["snapInterval"] = snapInterval;
_config->conf[prefix]["WFM"]["deempMode"] = deempId;
_config->conf[prefix]["WFM"]["squelchLevel"] = squelchLevel;
_config->conf[prefix]["WFM"]["stereo"] = false;
}
_config->release(true);
squelch.init(_vfo->output, squelchLevel);
demod.init(&squelch.out, bbSampRate, bw / 2.0f);
demodStereo.init(&squelch.out, bbSampRate, bw / 2.0f);
float audioBW = std::min<float>(audioSampleRate / 2.0f, 16000.0f);
win.init(audioBW, audioBW, bbSampRate);
if (stereo) {
resamp.init(demodStereo.out, &win, bbSampRate, audioSampRate);
}
else {
resamp.init(&demod.out, &win, bbSampRate, audioSampRate);
}
win.setSampleRate(bbSampRate * resamp.getInterpolation());
resamp.updateWindow(&win);
deemp.init(&resamp.out, audioSampRate, tau);
if (deempId == 2) { deemp.bypass = true; }
onUserChangedBandwidthHandler.handler = vfoUserChangedBandwidthHandler;
onUserChangedBandwidthHandler.ctx = this;
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
void start() {
squelch.start();
if (stereo) {
demodStereo.start();
}
else {
demod.start();
}
resamp.start();
deemp.start();
running = true;
}
void stop() {
squelch.stop();
if (stereo) {
demodStereo.stop();
}
else {
demod.stop();
}
resamp.stop();
deemp.stop();
running = false;
}
bool isRunning() {
return running;
}
void select() {
_vfo->setSampleRate(bbSampRate, bw);
_vfo->setSnapInterval(snapInterval);
_vfo->setReference(ImGui::WaterfallVFO::REF_CENTER);
_vfo->setBandwidthLimits(bwMin, bwMax, false);
}
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
squelch.setInput(_vfo->output);
_vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
VFOManager::VFO* getVFO() {
return _vfo;
}
void setAudioSampleRate(float sampleRate) {
if (running) {
resamp.stop();
deemp.stop();
}
audioSampRate = sampleRate;
float audioBW = std::min<float>(audioSampRate / 2.0f, 16000.0f);
resamp.setOutSampleRate(audioSampRate);
win.setSampleRate(bbSampRate * resamp.getInterpolation());
win.setCutoff(audioBW);
win.setTransWidth(audioBW);
resamp.updateWindow(&win);
deemp.setSampleRate(audioSampRate);
if (running) {
resamp.start();
deemp.start();
}
}
float getAudioSampleRate() {
return audioSampRate;
}
dsp::stream<dsp::stereo_t>* getOutput() {
return &deemp.out;
}
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::InputFloat(("##_radio_wfm_bw_" + uiPrefix).c_str(), &bw, 1, 100, "%.0f", 0)) {
bw = std::clamp<float>(bw, bwMin, bwMax);
setBandwidth(bw);
_config->acquire();
_config->conf[uiPrefix]["WFM"]["bandwidth"] = bw;
_config->release(true);
}
ImGui::LeftLabel("Snap Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputFloat(("##_radio_wfm_snap_" + uiPrefix).c_str(), &snapInterval, 1, 100, "%.0f", 0)) {
if (snapInterval < 1) { snapInterval = 1; }
setSnapInterval(snapInterval);
_config->acquire();
_config->conf[uiPrefix]["WFM"]["snapInterval"] = snapInterval;
_config->release(true);
}
ImGui::LeftLabel("De-emphasis");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(("##_radio_wfm_deemp_" + uiPrefix).c_str(), &deempId, deempModes)) {
setDeempIndex(deempId);
_config->acquire();
_config->conf[uiPrefix]["WFM"]["deempMode"] = deempId;
_config->release(true);
}
ImGui::LeftLabel("Squelch");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_wfm_sqelch_" + uiPrefix).c_str(), &squelchLevel, -100.0f, 0.0f, "%.3fdB")) {
squelch.setLevel(squelchLevel);
_config->acquire();
_config->conf[uiPrefix]["WFM"]["squelchLevel"] = squelchLevel;
_config->release(true);
}
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + uiPrefix).c_str(), &stereo)) {
setStereo(stereo);
_config->acquire();
_config->conf[uiPrefix]["WFM"]["stereo"] = stereo;
_config->release(true);
}
}
static void vfoUserChangedBandwidthHandler(double newBw, void* ctx) {
WFMDemodulator* _this = (WFMDemodulator*)ctx;
if (_this->running) {
_this->bw = newBw;
_this->setBandwidth(_this->bw, false);
_this->_config->acquire();
_this->_config->conf[_this->uiPrefix]["WFM"]["bandwidth"] = _this->bw;
_this->_config->release(true);
}
}
void setDeempIndex(int id) {
if (id >= 2 || id < 0) {
deemp.bypass = true;
return;
}
deemp.setTau(deempVals[id]);
deemp.bypass = false;
}
void setSnapInterval(float snapInt) {
snapInterval = snapInt;
_vfo->setSnapInterval(snapInterval);
}
void setBandwidth(float bandWidth, bool updateWaterfall = true) {
bandWidth = std::clamp<float>(bandWidth, bwMin, bwMax);
bw = bandWidth;
_vfo->setBandwidth(bw, updateWaterfall);
demod.setDeviation(bw / 2.0f);
demodStereo.setDeviation(bw / 2.0f);
}
void setStereo(bool enabled) {
if (running) {
demod.stop();
demodStereo.stop();
}
if (enabled) {
resamp.setInput(demodStereo.out);
if (running) { demodStereo.start(); }
}
else {
resamp.setInput(&demod.out);
if (running) { demod.start(); }
}
}
void saveParameters(bool lock = true) {
if (lock) { _config->acquire(); }
_config->conf[uiPrefix]["WFM"]["bandwidth"] = bw;
_config->conf[uiPrefix]["WFM"]["snapInterval"] = snapInterval;
_config->conf[uiPrefix]["WFM"]["deempMode"] = deempId;
_config->conf[uiPrefix]["WFM"]["squelchLevel"] = squelchLevel;
_config->conf[uiPrefix]["WFM"]["stereo"] = stereo;
if (lock) { _config->release(true); }
}
private:
const float bwMax = 250000;
const float bwMin = 50000;
const float bbSampRate = 250000;
const char* deempModes = "50µs\00075µs\000none\000";
const float deempVals[2] = { 50e-6, 75e-6 };
std::string uiPrefix;
float snapInterval = 100000;
float audioSampRate = 48000;
float squelchLevel = -100.0f;
float bw = 200000;
bool stereo = false;
int deempId = 0;
float tau = 50e-6;
bool running = false;
VFOManager::VFO* _vfo;
dsp::Squelch squelch;
dsp::FMDemod demod;
dsp::StereoFMDemod demodStereo;
dsp::filter_window::BlackmanWindow win;
dsp::PolyphaseResampler<dsp::stereo_t> resamp;
dsp::BFMDeemp deemp;
ConfigManager* _config;
EventHandler<double> onUserChangedBandwidthHandler;
};

View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.13)
project(weather_sat_decoder)
if (MSVC)
add_compile_options(/O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
add_compile_options(-O3 -std=c++17)
endif ()
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include_directories("src/")
include_directories("src/libcorrect/")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native")
add_library(weather_sat_decoder SHARED ${SRC})
target_link_libraries(weather_sat_decoder PRIVATE sdrpp_core)
set_target_properties(weather_sat_decoder PROPERTIES PREFIX "")
# Install directives
install(TARGETS weather_sat_decoder DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,149 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <dsp/pll.h>
#include <dsp/stream.h>
#include <dsp/demodulator.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <dsp/routing.h>
#include <dsp/sink.h>
#include <gui/widgets/folder_select.h>
#include <gui/widgets/constellation_diagram.h>
#include <sat_decoder.h>
#include <noaa_hrpt_decoder.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "weather_sat_decoder",
/* Description: */ "Weather Satellite Decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
std::string genFileName(std::string prefix, std::string suffix) {
time_t now = time(0);
tm *ltm = localtime(&now);
char buf[1024];
sprintf(buf, "%s_%02d-%02d-%02d_%02d-%02d-%02d%s", prefix.c_str(), ltm->tm_hour, ltm->tm_min, ltm->tm_sec, ltm->tm_mday, ltm->tm_mon + 1, ltm->tm_year + 1900, suffix.c_str());
return buf;
}
class WeatherSatDecoderModule : public ModuleManager::Instance {
public:
WeatherSatDecoderModule(std::string name) {
this->name = name;
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 1000000, 1000000, 1000000, 1000000, true);
decoders["NOAA HRPT"] = new NOAAHRPTDecoder(vfo, name);
// Generate the list
decoderNames.clear();
decoderNamesStr = "";
for (auto const& [name, dec] : decoders) {
decoderNames.push_back(name);
decoderNamesStr += name;
decoderNamesStr += '\0';
}
selectDecoder(decoderNames[0], false);
gui::menu.registerEntry(name, menuHandler, this, this);
}
~WeatherSatDecoderModule() {
decoder->stop();
}
void postInit() {}
void enable() {
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 1000000, 1000000, 1000000, 1000000, true);
for (auto const& [name, dec] : decoders) { dec->setVFO(vfo); }
decoder->select();
decoder->start();
enabled = true;
}
void disable() {
// Stop decoder
decoder->stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
void selectDecoder(std::string name, bool deselectLast = true) {
if (deselectLast) {
decoder->stop();
}
decoder = decoders[name];
decoder->select();
decoder->start();
}
static void menuHandler(void* ctx) {
WeatherSatDecoderModule* _this = (WeatherSatDecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (!_this->enabled) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo("##todo", &_this->decoderId, _this->decoderNamesStr.c_str())) {
_this->selectDecoder(_this->decoderNames[_this->decoderId]);
}
_this->decoder->drawMenu(menuWidth);
ImGui::Button("Record##testdsdfsds", ImVec2(menuWidth, 0));
if (!_this->enabled) { style::endDisabled(); }
}
std::string name;
bool enabled = true;
VFOManager::VFO* vfo;
std::map<std::string, SatDecoder*> decoders;
std::vector<std::string> decoderNames;
std::string decoderNamesStr = "";
int decoderId = 0;
SatDecoder* decoder;
};
MOD_EXPORT void _INIT_() {
// Nothing
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new WeatherSatDecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (WeatherSatDecoderModule*)instance;
}
MOD_EXPORT void _END_() {
// Nothing either
}

View File

@ -0,0 +1,572 @@
#pragma once
#include <sat_decoder.h>
#include <dsp/demodulator.h>
#include <dsp/deframing.h>
#include <dsp/noaa/hrpt.h>
#include <dsp/noaa/tip.h>
#include <dsp/sink.h>
#include <gui/widgets/symbol_diagram.h>
#include <gui/widgets/line_push_image.h>
#include <gui/gui.h>
#define NOAA_HRPT_VFO_SR 3000000.0f
#define NOAA_HRPT_VFO_BW 2000000.0f
class NOAAHRPTDecoder : public SatDecoder {
public:
NOAAHRPTDecoder(VFOManager::VFO* vfo, std::string name) : avhrrRGBImage(2048, 256), avhrr1Image(2048, 256), avhrr2Image(2048, 256), avhrr3Image(2048, 256), avhrr4Image(2048, 256), avhrr5Image(2048, 256), symDiag(0.5f) {
_vfo = vfo;
_name = name;
// Core DSP
demod.init(vfo->output, NOAA_HRPT_VFO_SR, 665400.0f * 2.0f, 0.02e-3, (0.06f*0.06f) / 2.0f, 32, 0.6f, (0.01f*0.01f) / 4.0f, 0.01f, 0.005);
split.init(demod.out);
split.bindStream(&dataStream);
split.bindStream(&visStream);
reshape.init(&visStream, 1024, (NOAA_HRPT_VFO_SR/30) - 1024);
visSink.init(&reshape.out, visHandler, this);
deframe.init(&dataStream, 11090 * 10 * 2, (uint8_t*)dsp::noaa::HRPTSyncWord, 60);
manDec.init(&deframe.out, false);
packer.init(&manDec.out);
demux.init(&packer.out);
tipDemux.init(&demux.TIPOut);
hirsDemux.init(&tipDemux.HIRSOut);
// All Sinks
avhrr1Sink.init(&demux.AVHRRChan1Out, avhrr1Handler, this);
avhrr2Sink.init(&demux.AVHRRChan2Out, avhrr2Handler, this);
avhrr3Sink.init(&demux.AVHRRChan3Out, avhrr3Handler, this);
avhrr4Sink.init(&demux.AVHRRChan4Out, avhrr4Handler, this);
avhrr5Sink.init(&demux.AVHRRChan5Out, avhrr5Handler, this);
sbuvSink.init(&tipDemux.SBUVOut);
dcsSink.init(&tipDemux.DCSOut);
semSink.init(&tipDemux.SEMOut);
aipSink.init(&demux.AIPOut);
hirs1Sink.init(&hirsDemux.radChannels[0], hirs1Handler, this);
hirs2Sink.init(&hirsDemux.radChannels[1], hirs2Handler, this);
hirs3Sink.init(&hirsDemux.radChannels[2], hirs3Handler, this);
hirs4Sink.init(&hirsDemux.radChannels[3], hirs4Handler, this);
hirs5Sink.init(&hirsDemux.radChannels[4], hirs5Handler, this);
hirs6Sink.init(&hirsDemux.radChannels[5], hirs6Handler, this);
hirs7Sink.init(&hirsDemux.radChannels[6], hirs7Handler, this);
hirs8Sink.init(&hirsDemux.radChannels[7], hirs8Handler, this);
hirs9Sink.init(&hirsDemux.radChannels[8], hirs9Handler, this);
hirs10Sink.init(&hirsDemux.radChannels[9], hirs10Handler, this);
hirs11Sink.init(&hirsDemux.radChannels[10], hirs11Handler, this);
hirs12Sink.init(&hirsDemux.radChannels[11], hirs12Handler, this);
hirs13Sink.init(&hirsDemux.radChannels[12], hirs13Handler, this);
hirs14Sink.init(&hirsDemux.radChannels[13], hirs14Handler, this);
hirs15Sink.init(&hirsDemux.radChannels[14], hirs15Handler, this);
hirs16Sink.init(&hirsDemux.radChannels[15], hirs16Handler, this);
hirs17Sink.init(&hirsDemux.radChannels[16], hirs17Handler, this);
hirs18Sink.init(&hirsDemux.radChannels[17], hirs18Handler, this);
hirs19Sink.init(&hirsDemux.radChannels[18], hirs19Handler, this);
hirs20Sink.init(&hirsDemux.radChannels[19], hirs20Handler, this);
}
void select() {
_vfo->setSampleRate(NOAA_HRPT_VFO_SR, NOAA_HRPT_VFO_BW);
_vfo->setReference(ImGui::WaterfallVFO::REF_CENTER);
_vfo->setBandwidthLimits(NOAA_HRPT_VFO_BW, NOAA_HRPT_VFO_BW, true);
};
void start() {
demod.start();
split.start();
reshape.start();
visSink.start();
deframe.start();
manDec.start();
packer.start();
demux.start();
tipDemux.start();
hirsDemux.start();
avhrr1Sink.start();
avhrr2Sink.start();
avhrr3Sink.start();
avhrr4Sink.start();
avhrr5Sink.start();
sbuvSink.start();
dcsSink.start();
semSink.start();
aipSink.start();
hirs1Sink.start();
hirs2Sink.start();
hirs3Sink.start();
hirs4Sink.start();
hirs5Sink.start();
hirs6Sink.start();
hirs7Sink.start();
hirs8Sink.start();
hirs9Sink.start();
hirs10Sink.start();
hirs11Sink.start();
hirs12Sink.start();
hirs13Sink.start();
hirs14Sink.start();
hirs15Sink.start();
hirs16Sink.start();
hirs17Sink.start();
hirs18Sink.start();
hirs19Sink.start();
hirs20Sink.start();
compositeThread = std::thread(&NOAAHRPTDecoder::avhrrCompositeWorker, this);
};
void stop() {
compositeIn1.stopReader();
compositeIn1.stopWriter();
compositeIn2.stopReader();
compositeIn2.stopWriter();
demod.stop();
split.stop();
reshape.stop();
visSink.stop();
deframe.stop();
manDec.stop();
packer.stop();
demux.stop();
tipDemux.stop();
hirsDemux.stop();
avhrr1Sink.stop();
avhrr2Sink.stop();
avhrr3Sink.stop();
avhrr4Sink.stop();
avhrr5Sink.stop();
sbuvSink.stop();
dcsSink.stop();
semSink.stop();
aipSink.stop();
hirs1Sink.stop();
hirs2Sink.stop();
hirs3Sink.stop();
hirs4Sink.stop();
hirs5Sink.stop();
hirs6Sink.stop();
hirs7Sink.stop();
hirs8Sink.stop();
hirs9Sink.stop();
hirs10Sink.stop();
hirs11Sink.stop();
hirs12Sink.stop();
hirs13Sink.stop();
hirs14Sink.stop();
hirs15Sink.stop();
hirs16Sink.stop();
hirs17Sink.stop();
hirs18Sink.stop();
hirs19Sink.stop();
hirs20Sink.stop();
if (compositeThread.joinable()) {
compositeThread.join();
}
compositeIn1.clearReadStop();
compositeIn1.clearWriteStop();
compositeIn2.clearReadStop();
compositeIn2.clearWriteStop();
};
void setVFO(VFOManager::VFO* vfo) {
_vfo = vfo;
demod.setInput(_vfo->output);
};
virtual bool canRecord() {
return false;
}
// bool startRecording(std::string recPath) {
// };
// void stopRecording() {
// };
// bool isRecording() {
// };
void drawMenu(float menuWidth) {
ImGui::SetNextItemWidth(menuWidth);
symDiag.draw();
if (showWindow) {
gui::mainWindow.lockWaterfallControls = true;
ImGui::Begin("NOAA HRPT Decoder");
ImGui::BeginTabBar("NOAAHRPTTabs");
if (ImGui::BeginTabItem("AVHRR RGB(221)")) {
ImGui::BeginChild("AVHRRRGBChild");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
avhrrRGBImage.draw();
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("AVHRR 1")) {
ImGui::BeginChild("AVHRR1Child");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
avhrr1Image.draw();
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("AVHRR 2")) {
ImGui::BeginChild("AVHRR2Child");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
avhrr2Image.draw();
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("AVHRR 3")) {
ImGui::BeginChild("AVHRR3Child");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
avhrr3Image.draw();
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("AVHRR 4")) {
ImGui::BeginChild("AVHRR4Child");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
avhrr4Image.draw();
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("AVHRR 5")) {
ImGui::BeginChild("AVHRR5Child");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
avhrr5Image.draw();
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("HIRS")) {
ImGui::BeginChild("HIRSChild");
ImGui::EndChild();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
ImGui::End();
}
ImGui::Checkbox("Show Image", &showWindow);
};
private:
// AVHRR Data Handlers
void avhrrCompositeWorker() {
compositeIn1.flush();
compositeIn2.flush();
while (true) {
if (compositeIn1.read() < 0) { return; }
if (compositeIn2.read() < 0) { return; }
uint8_t* buf = avhrrRGBImage.acquireNextLine();
float rg, b;
for (int i = 0; i < 2048; i++) {
b = ((float)compositeIn1.readBuf[i] * 255.0f) / 1024.0f;
rg = ((float)compositeIn2.readBuf[i] * 255.0f) / 1024.0f;
buf[(i*4)] = rg;
buf[(i*4) + 1] = rg;
buf[(i*4) + 2] = b;
buf[(i*4) + 3] = 255;
}
avhrrRGBImage.releaseNextLine();
compositeIn1.flush();
compositeIn2.flush();
}
}
static void avhrr1Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
uint8_t* buf = _this->avhrr1Image.acquireNextLine();
float val;
for (int i = 0; i < 2048; i++) {
val = ((float)data[i] * 255.0f) / 1024.0f;
buf[(i*4)] = val;
buf[(i*4) + 1] = val;
buf[(i*4) + 2] = val;
buf[(i*4) + 3] = 255;
}
_this->avhrr1Image.releaseNextLine();
memcpy(_this->compositeIn1.writeBuf, data, count * sizeof(uint16_t));
_this->compositeIn1.swap(count);
}
static void avhrr2Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
uint8_t* buf = _this->avhrr2Image.acquireNextLine();
float val;
for (int i = 0; i < 2048; i++) {
val = ((float)data[i] * 255.0f) / 1024.0f;
buf[(i*4)] = val;
buf[(i*4) + 1] = val;
buf[(i*4) + 2] = val;
buf[(i*4) + 3] = 255;
}
_this->avhrr2Image.releaseNextLine();
memcpy(_this->compositeIn2.writeBuf, data, count * sizeof(uint16_t));
_this->compositeIn2.swap(count);
}
static void avhrr3Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
uint8_t* buf = _this->avhrr3Image.acquireNextLine();
float val;
for (int i = 0; i < 2048; i++) {
val = ((float)data[i] * 255.0f) / 1024.0f;
buf[(i*4)] = val;
buf[(i*4) + 1] = val;
buf[(i*4) + 2] = val;
buf[(i*4) + 3] = 255;
}
_this->avhrr3Image.releaseNextLine();
}
static void avhrr4Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
uint8_t* buf = _this->avhrr4Image.acquireNextLine();
float val;
for (int i = 0; i < 2048; i++) {
val = ((float)data[i] * 255.0f) / 1024.0f;
buf[(i*4)] = val;
buf[(i*4) + 1] = val;
buf[(i*4) + 2] = val;
buf[(i*4) + 3] = 255;
}
_this->avhrr4Image.releaseNextLine();
}
static void avhrr5Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
uint8_t* buf = _this->avhrr5Image.acquireNextLine();
float val;
for (int i = 0; i < 2048; i++) {
val = ((float)data[i] * 255.0f) / 1024.0f;
buf[(i*4)] = val;
buf[(i*4) + 1] = val;
buf[(i*4) + 2] = val;
buf[(i*4) + 3] = 255;
}
_this->avhrr5Image.releaseNextLine();
}
// HIRS Data Handlers
static void hirs1Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs2Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs3Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs4Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs5Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs6Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs7Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs8Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs9Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs10Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs11Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs12Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs13Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs14Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs15Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs16Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs17Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs18Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs19Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void hirs20Handler(uint16_t* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
}
static void visHandler(float* data, int count, void* ctx) {
NOAAHRPTDecoder* _this = (NOAAHRPTDecoder*)ctx;
memcpy(_this->symDiag.acquireBuffer(), data, 1024 * sizeof(float));
_this->symDiag.releaseBuffer();
}
std::string _name;
std::string _recPath;
VFOManager::VFO* _vfo;
// DSP
dsp::PMDemod demod;
dsp::stream<float> visStream;
dsp::stream<float> dataStream;
dsp::Splitter<float> split;
dsp::Reshaper<float> reshape;
dsp::ManchesterDeframer deframe;
dsp::ManchesterDecoder manDec;
dsp::BitPacker packer;
dsp::noaa::HRPTDemux demux;
dsp::noaa::TIPDemux tipDemux;
dsp::noaa::HIRSDemux hirsDemux;
// AHVRR Handlers
dsp::HandlerSink<uint16_t> avhrr1Sink;
dsp::HandlerSink<uint16_t> avhrr2Sink;
dsp::HandlerSink<uint16_t> avhrr3Sink;
dsp::HandlerSink<uint16_t> avhrr4Sink;
dsp::HandlerSink<uint16_t> avhrr5Sink;
// (at the moment) Unused TIP handlers
dsp::NullSink<uint8_t> sbuvSink;
dsp::NullSink<uint8_t> dcsSink;
dsp::NullSink<uint8_t> semSink;
// (at the moment) Unused AIP handlers
dsp::NullSink<uint8_t> aipSink;
// HIRS Handlers
dsp::HandlerSink<uint16_t> hirs1Sink;
dsp::HandlerSink<uint16_t> hirs2Sink;
dsp::HandlerSink<uint16_t> hirs3Sink;
dsp::HandlerSink<uint16_t> hirs4Sink;
dsp::HandlerSink<uint16_t> hirs5Sink;
dsp::HandlerSink<uint16_t> hirs6Sink;
dsp::HandlerSink<uint16_t> hirs7Sink;
dsp::HandlerSink<uint16_t> hirs8Sink;
dsp::HandlerSink<uint16_t> hirs9Sink;
dsp::HandlerSink<uint16_t> hirs10Sink;
dsp::HandlerSink<uint16_t> hirs11Sink;
dsp::HandlerSink<uint16_t> hirs12Sink;
dsp::HandlerSink<uint16_t> hirs13Sink;
dsp::HandlerSink<uint16_t> hirs14Sink;
dsp::HandlerSink<uint16_t> hirs15Sink;
dsp::HandlerSink<uint16_t> hirs16Sink;
dsp::HandlerSink<uint16_t> hirs17Sink;
dsp::HandlerSink<uint16_t> hirs18Sink;
dsp::HandlerSink<uint16_t> hirs19Sink;
dsp::HandlerSink<uint16_t> hirs20Sink;
dsp::HandlerSink<float> visSink;
ImGui::LinePushImage avhrrRGBImage;
ImGui::LinePushImage avhrr1Image;
ImGui::LinePushImage avhrr2Image;
ImGui::LinePushImage avhrr3Image;
ImGui::LinePushImage avhrr4Image;
ImGui::LinePushImage avhrr5Image;
ImGui::SymbolDiagram symDiag;
dsp::stream<uint16_t> compositeIn1;
dsp::stream<uint16_t> compositeIn2;
std::thread compositeThread;
bool showWindow = false;
};

View File

@ -0,0 +1,16 @@
#pragma once
#include <string>
#include <signal_path/vfo_manager.h>
class SatDecoder {
public:
virtual void select() = 0;
virtual void start() = 0;
virtual void stop() = 0;
virtual void setVFO(VFOManager::VFO* vfo) = 0;
virtual bool canRecord() = 0;
virtual bool startRecording(std::string recPath) { return false; };
virtual void stopRecording() { };
virtual bool isRecording() { return false; };
virtual void drawMenu(float menuWidth) = 0;
};