mirror of
https://github.com/bytequill/radio-sdr.git
synced 2025-08-02 02:36:56 +02:00
early skeleton
This commit is contained in:
parent
dbfddda58a
commit
ecd259f629
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
dev/*
|
||||
build/*
|
||||
# Temporary ignore!!
|
||||
docs/*
|
||||
|
34
CMakeLists.txt
Normal file
34
CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
|
||||
|
||||
project(radio-sdr)
|
||||
|
||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||
file(GLOB_RECURSE HEADERS "src/*.hpp")
|
||||
|
||||
add_executable(main ${SOURCES} ${HEADERS})
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -ggdb")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
#==============================
|
||||
# CPM - Cmake Package Manager |
|
||||
#==============================
|
||||
include(src/get_CPM.cmake)
|
||||
|
||||
CPMAddPackage("gh:libsdl-org/SDL#release-3.2.10")
|
||||
|
||||
CPMAddPackage(gh:ocornut/imgui@1.91.9b)
|
||||
add_library(imgui STATIC
|
||||
${imgui_SOURCE_DIR}/imgui.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_demo.cpp # optionally comment this out
|
||||
${imgui_SOURCE_DIR}/imgui_draw.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_widgets.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_tables.cpp
|
||||
)
|
||||
message(STATUS "imgui_SOURCE_DIR: ${imgui_SOURCE_DIR}")
|
||||
target_include_directories(imgui INTERFACE ${imgui_SOURCE_DIR})
|
||||
target_compile_definitions(imgui PUBLIC -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS) # optional imgui setting
|
||||
|
||||
target_link_libraries(main SDL3 imgui)
|
@ -1 +1,3 @@
|
||||
# radio
|
||||
# radio-sdr - An exploration of low-level radio modulation, raw filetype parsing and displaying data
|
||||
|
||||
# FOREWORD
|
||||
|
31
src/fft.cpp
Normal file
31
src/fft.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "fft.hpp"
|
||||
using namespace std;
|
||||
|
||||
#include <cassert>
|
||||
|
||||
void fft(vector<cd> &a, bool invert) {
|
||||
assert(a.size() > 0 && (a.size() & (a.size() - 1)) == 0); // Check power of 2
|
||||
int n = a.size();
|
||||
if (n == 1)
|
||||
return;
|
||||
|
||||
vector<cd> a0(n / 2), a1(n / 2);
|
||||
for (int i = 0; 2 * i < n; i++) {
|
||||
a0[i] = a[2*i];
|
||||
a1[i] = a[2*i+1];
|
||||
}
|
||||
fft(a0, invert);
|
||||
fft(a1, invert);
|
||||
|
||||
double ang = 2 * PI / n * (invert ? -1 : 1);
|
||||
cd w(1), wn(cos(ang), sin(ang));
|
||||
for (int i = 0; 2 * i < n; i++) {
|
||||
a[i] = a0[i] + w * a1[i];
|
||||
a[i + n/2] = a0[i] - w * a1[i];
|
||||
if (invert) {
|
||||
a[i] /= 2;
|
||||
a[i + n/2] /= 2;
|
||||
}
|
||||
w *= wn;
|
||||
}
|
||||
}
|
8
src/fft.hpp
Normal file
8
src/fft.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
using cd = std::complex<double>;
|
||||
const double PI = acos(-1);
|
||||
|
||||
void fft(std::vector<cd> &a, bool invert);
|
24
src/get_CPM.cmake
Normal file
24
src/get_CPM.cmake
Normal file
@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
|
||||
|
||||
set(CPM_DOWNLOAD_VERSION 0.42.0)
|
||||
set(CPM_HASH_SUM "2020b4fc42dba44817983e06342e682ecfc3d2f484a581f11cc5731fbe4dce8a")
|
||||
|
||||
if(CPM_SOURCE_CACHE)
|
||||
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
|
||||
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
else()
|
||||
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
endif()
|
||||
|
||||
# Expand relative path. This is important if the provided path contains a tilde (~)
|
||||
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
|
||||
|
||||
file(DOWNLOAD
|
||||
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
|
||||
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
|
||||
)
|
||||
|
||||
include(${CPM_DOWNLOAD_LOCATION})
|
67
src/main.cpp
Normal file
67
src/main.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "sources/wav-baseband.hpp"
|
||||
#include "fft.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
//GUI
|
||||
#include <SDL3/SDL.h>
|
||||
#include <imgui.h>
|
||||
#include <backends/imgui_impl_sdl3.h>
|
||||
#include <backends/imgui_impl_sdlrenderer3.h>
|
||||
|
||||
int GUIMain() {
|
||||
if(!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
SDL_Window *window = SDL_CreateWindow("Title", 1280, 720, window_flags);
|
||||
if(window == NULL) {
|
||||
return 1;
|
||||
}
|
||||
SDL_Renderer *renderer = SDL_CreateRenderer(window, NULL);
|
||||
if(renderer == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_SetRenderVSync(renderer, 1);
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
ImGui_ImplSDL3_InitForSDLRenderer(window, renderer);
|
||||
ImGui_ImplSDLRenderer3_Init(renderer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int res2 = GUIMain();
|
||||
|
||||
return res2;
|
||||
RFSourceWAV src;
|
||||
|
||||
bool res = src.Load("./dev/test2.wav");
|
||||
std::vector<cd> samples;
|
||||
for (int i = 0; i != 10000 * 2; i++) {
|
||||
std::vector<cd> sampleFrame = src.GetSamples(i * pow(2,11), pow(2,11));
|
||||
fft(sampleFrame, false);
|
||||
samples.insert(samples.end(), sampleFrame.begin(), sampleFrame.end());
|
||||
}
|
||||
std::ofstream out;
|
||||
out.open("dev/out1.csv");
|
||||
out << "Magnitude\n";
|
||||
|
||||
for (size_t i = 0; i < samples.size(); i++) {
|
||||
int mag = static_cast<int>(std::abs(samples[i]));
|
||||
out << mag << "\n";
|
||||
}
|
||||
|
||||
out.close();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
19
src/sources/base.hpp
Normal file
19
src/sources/base.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
using cd = std::complex<double>;
|
||||
|
||||
class RFSourceBase {
|
||||
public:
|
||||
bool live; // TRUE - Live RF stream FALSE - Recorded file
|
||||
|
||||
// Fundamental metadata. In Hz
|
||||
long bandwidth;
|
||||
long samplerate;
|
||||
long centerFreq;
|
||||
|
||||
bool Load(std::string sourceID);
|
||||
std::vector<cd> GetSamples(uintmax_t indx, int n);
|
||||
};
|
106
src/sources/wav-baseband.cpp
Normal file
106
src/sources/wav-baseband.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
#include "wav-baseband.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
bool RFSourceWAV::Load(std::string filename) {
|
||||
file.open(filename.c_str(), std::ios::in | std::ios::binary);
|
||||
if (!file || !file.is_open()) {return false;}
|
||||
|
||||
std::cout << header.samplerate << std::endl;
|
||||
|
||||
char buff[sizeof(header)];
|
||||
file.read(buff, sizeof(header));
|
||||
|
||||
memcpy(&header, buff, sizeof(header));
|
||||
|
||||
// Assumptions about the format which if not true require refactoring of the code
|
||||
if (std::strncmp(header.Magic, "RIFF", 4) != 0 ||
|
||||
std::strncmp(header.RIFFType, "WAV", 4) != 0 ||
|
||||
std::strncmp(header.FMTMARK, "fmt ", 4) != 0 ||
|
||||
std::strncmp(header.DATAMARK, "data", 4) != 0 ||
|
||||
header.fmtType != 1 ||
|
||||
header.chnNum != 2)
|
||||
{
|
||||
// TODO: figure out a DEBUG/ERROR system for more precise data than just the failure
|
||||
return false;
|
||||
}
|
||||
|
||||
this->samplerate = header.samplerate;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// converts the `char`(1byte) raw sampleData into correct bitsPerSample based values
|
||||
int64_t convertSample(const char *sampleData, uint_fast16_t bitsPerSample) {
|
||||
assert(bitsPerSample > 0 && (bitsPerSample & (bitsPerSample - 1)) == 0); // Check power of 2
|
||||
assert(bitsPerSample <= 64);
|
||||
|
||||
if (bitsPerSample == 8) {
|
||||
// 8-bit WAV audio is typically stored as unsigned values.
|
||||
// Convert to signed: 0-255 (unsigned) -> -128 to 127.
|
||||
uint8_t raw = *(reinterpret_cast<const uint8_t*>(sampleData));
|
||||
return static_cast<int64_t>(static_cast<int16_t>(raw) - 128);
|
||||
} else if (bitsPerSample == 16) {
|
||||
int16_t sample = static_cast<int16_t>(
|
||||
(static_cast<uint16_t>(static_cast<uint8_t>(sampleData[1])) << 8) |
|
||||
static_cast<uint8_t>(sampleData[0])
|
||||
);
|
||||
return static_cast<int64_t>(sample);
|
||||
} else if (bitsPerSample == 32) {
|
||||
int32_t sample = static_cast<int32_t>(
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(sampleData[3])) << 24) |
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(sampleData[2])) << 16) |
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(sampleData[1])) << 8) |
|
||||
static_cast<uint8_t>(sampleData[0])
|
||||
);
|
||||
return static_cast<int64_t>(sample);
|
||||
} else if (bitsPerSample == 64 && sizeof(size_t) >= 8) {
|
||||
int64_t sample = static_cast<int64_t>(
|
||||
(static_cast<uint64_t>(static_cast<uint8_t>(sampleData[7])) << 56) |
|
||||
(static_cast<uint64_t>(static_cast<uint8_t>(sampleData[6])) << 48) |
|
||||
(static_cast<uint64_t>(static_cast<uint8_t>(sampleData[5])) << 40) |
|
||||
(static_cast<uint64_t>(static_cast<uint8_t>(sampleData[4])) << 32) |
|
||||
(static_cast<uint64_t>(static_cast<uint8_t>(sampleData[3])) << 24) |
|
||||
(static_cast<uint64_t>(static_cast<uint8_t>(sampleData[2])) << 16) |
|
||||
(static_cast<uint64_t>(static_cast<uint8_t>(sampleData[1])) << 8) |
|
||||
static_cast<uint8_t>(sampleData[0])
|
||||
);
|
||||
return static_cast<int64_t>(sample);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<cd> RFSourceWAV::GetSamples(uintmax_t indx, int n) {
|
||||
// an I/Q PCM stream MUST have 2 channels but setting value here for readability
|
||||
int8_t chnNum = 2;
|
||||
assert (chnNum == header.chnNum);
|
||||
|
||||
std::vector<cd> samples;
|
||||
if (!file || !file.is_open()) {return samples;}
|
||||
|
||||
std::vector<char> buf((header.bitsPerSample / 8) * chnNum * n);
|
||||
uintmax_t offset = sizeof(header) + indx * header.blockAlign;
|
||||
if (offset + buf.size() > header.dataLen + sizeof(header)) {return samples;}
|
||||
|
||||
file.seekg(offset);
|
||||
if (!file) {return samples;}
|
||||
file.read(buf.data(), buf.size());
|
||||
|
||||
for (size_t i = 0; i < buf.size(); i += header.blockAlign) {
|
||||
int sampleI = convertSample(&buf.data()[i], header.bitsPerSample);
|
||||
int sampleQ = convertSample(&buf.data()[i + (header.bitsPerSample / 8)],
|
||||
header.bitsPerSample);
|
||||
samples.push_back(cd(sampleI, sampleQ));
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
RFSourceWAV::~RFSourceWAV() {
|
||||
if(file){
|
||||
if(file.is_open()) {file.close();}
|
||||
}
|
||||
}
|
35
src/sources/wav-baseband.hpp
Normal file
35
src/sources/wav-baseband.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include "base.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct WAVHeader {
|
||||
char Magic[4]; // Always "RIFF". Magic value for all RIFF-Based formats!
|
||||
uint32_t Size;
|
||||
char RIFFType[4]; // Always "WAV"
|
||||
char FMTMARK[4];
|
||||
uint32_t fmtChunkLen; // Always 16
|
||||
uint16_t fmtType; // 1 - PCM
|
||||
uint16_t chnNum;
|
||||
uint32_t samplerate;
|
||||
uint32_t byteRate; // samplerate * bitsPerSample * chnNum / 8
|
||||
uint16_t blockAlign; // bitsPerSample * chnNum / 8
|
||||
uint16_t bitsPerSample;
|
||||
char DATAMARK[4];
|
||||
uint32_t dataLen;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class RFSourceWAV : RFSourceBase {
|
||||
public:
|
||||
bool Load(std::string filename);
|
||||
// Get `n` samples at index `indx` for both I and Q channels from the WAV file and return an
|
||||
// FFT-friendly vector
|
||||
std::vector<cd> GetSamples(uintmax_t indx, int n);
|
||||
~RFSourceWAV();
|
||||
|
||||
private:
|
||||
std::ifstream file;
|
||||
WAVHeader header;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user