mirror of
				https://github.com/bytequill/radio-sdr.git
				synced 2025-11-04 14:49:18 +01: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