Compare commits

1 Commits

Author SHA1 Message Date
98d971d92d thing 2026-07-20 23:18:26 +02:00
13 changed files with 799 additions and 711 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>
+19 -19
View File
@@ -27,25 +27,25 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCoroutineCallResolveError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesLocal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationSpecifierWithoutDeclarators/@EntryIndexedValue" value="WARNING" type="string" />
+2 -1
View File
@@ -30,5 +30,6 @@ add_executable(audiocoder
decoder.c
decoder.h
goertzel.c
goertzel.h)
goertzel.h
protocol.h)
target_link_libraries(audiocoder SDL2 SDL2_ttf m)
+351 -341
View File
@@ -12,24 +12,18 @@
#include <string.h>
#include "texteditor.h"
#include "modem_settings.h"
#include "packet.h"
#include "protocol.h"
Decoder decoder;
float tone_energy(
Decoder *dec,
int index) {
return goertzel_energy(
&dec->tones[index]
);
}
static void decoder_reset_symbol_filters(Decoder *dec) {
const int tones = dec->bitCount * 2 + 2;
for (int i = 0; i < tones; ++i) {
dec->tones[i].s1 = 0.0f;
dec->tones[i].s2 = 0.0f;
}
// Return the current accumulated Goertzel energy without clearing state.
Goertzel *g = &dec->tones[index];
return g->s1 * g->s1 + g->s2 * g->s2 - g->coeff * g->s1 * g->s2;
}
static void decoder_history_push(Decoder *dec, float sample) {
@@ -43,308 +37,147 @@ static void decoder_history_push(Decoder *dec, float sample) {
dec->totalSamples++;
}
static bool decoder_history_get(
const Decoder *dec,
uint64_t sampleIndex,
float *sample)
{
if (sampleIndex >= dec->totalSamples)
static bool decoder_history_get(const Decoder *dec, uint64_t sampleIndex, float *sample) {
if (sampleIndex >= dec->totalSamples) {
return false;
}
uint64_t oldest =
dec->totalSamples - dec->sampleHistoryCount;
uint64_t oldest = dec->totalSamples - dec->sampleHistoryCount;
if (sampleIndex < oldest)
if (sampleIndex < oldest) {
return false;
}
uint32_t start =
(dec->sampleHistoryWrite + DECODER_SAMPLE_HISTORY - dec->sampleHistoryCount)
% DECODER_SAMPLE_HISTORY;
uint32_t rel =
(uint32_t)(sampleIndex - oldest);
*sample =
dec->sampleHistory[(start + rel) % DECODER_SAMPLE_HISTORY];
uint32_t rel = (uint32_t) (sampleIndex - oldest);
*sample = dec->sampleHistory[(start + rel) % DECODER_SAMPLE_HISTORY];
return true;
}
static uint8_t decode_symbol_from_work(
Goertzel *tones,
uint8_t bitCount)
{
uint8_t byte = 0;
int index = 0;
for (int bit = 0; bit < bitCount; ++bit) {
float e0 = goertzel_energy(&tones[index++]);
float e1 = goertzel_energy(&tones[index++]);
if (e1 > e0)
byte |= 1u << bit;
}
return byte;
}
static bool decode_symbol_at(
Decoder *dec,
uint64_t sampleIndex,
uint8_t *out)
{
const int tones = dec->bitCount * 2 + 2;
Goertzel work[MAX_BITS * 2 + 2];
memcpy(work, dec->tones, sizeof(work));
for (int i = 0; i < tones; ++i) {
work[i].s1 = 0.0f;
work[i].s2 = 0.0f;
}
for (uint32_t s = 0; s < dec->symbolSamples; ++s) {
float sample;
if (!decoder_history_get(dec, sampleIndex + s, &sample))
return false;
for (int t = 0; t < tones; ++t)
goertzel_add(&work[t], sample);
}
*out = decode_symbol_from_work(work, dec->bitCount);
return true;
}
static void decoder_seed_live_symbol(
Decoder *dec,
uint64_t startSample)
{
const int tones = dec->bitCount * 2 + 2;
decoder_reset_symbol_filters(dec);
dec->sampleCount = 0;
for (uint64_t index = startSample; index < dec->totalSamples; ++index) {
float sample;
if (!decoder_history_get(dec, index, &sample))
break;
for (int t = 0; t < tones; ++t)
goertzel_add(&dec->tones[t], sample);
dec->sampleCount++;
}
}
static bool try_phase_search(Decoder *dec) {
const uint32_t phaseStep = 16;
const uint32_t syncSymbols = (uint32_t)sizeof(syncBytes);
const uint64_t syncSamples =
(uint64_t)syncSymbols * (uint64_t)dec->symbolSamples;
const uint64_t oldest =
dec->totalSamples - dec->sampleHistoryCount;
int bestScore = -1;
int bestPhase = 0;
uint64_t bestStart = 0;
if (dec->sampleHistoryCount < syncSamples + dec->symbolSamples)
return false;
for (uint32_t phase = 0; phase < dec->symbolSamples; phase += phaseStep) {
int64_t start =
(int64_t)dec->totalSamples -
(int64_t)syncSamples -
(int64_t)phase;
if (start < (int64_t)oldest)
continue;
int score = 0;
for (size_t i = 0; i < sizeof(syncBytes); ++i) {
uint8_t byte;
if (!decode_symbol_at(
dec,
(uint64_t)start +
(uint64_t)i * dec->symbolSamples,
&byte)) {
score = -1;
break;
}
if (byte != syncBytes[i])
break;
score++;
}
if (score > bestScore) {
bestScore = score;
bestPhase = (int)phase;
bestStart = (uint64_t)start;
}
}
dec->bestSyncScore = bestScore;
dec->bestSyncPhase = bestPhase;
if (bestScore != (int)sizeof(syncBytes)) {
printf("PHASE SEARCH FAILED bestScore=%d (need %zu)\n", bestScore, sizeof(syncBytes));
return false;
}
uint64_t dataStart =
bestStart + syncSamples;
printf("SYNC ALIGN phase=%d score=%d\n", bestPhase, bestScore);
dec->phaseOffset = bestPhase;
dec->phaseWait = 0;
dec->phaseSearchReady = false;
dec->rxState = RX_LOCKED;
dec->locked = true;
dec->bufferSize = 0;
dec->clockHistory = 0;
dec->clockCount = 0;
decoder_seed_live_symbol(dec, dataStart);
return true;
}
void decoder_init(
Decoder *dec) {
memset(dec, 0, sizeof(*dec));
dec->bitCount = 8;
dec->bitsPerSymbol = 3;
dec->toneCount = 8;
dec->startFreq = 1000;
dec->endFreq = 5000;
dec->dataSpacing = 250;
dec->startFreq = DEFAULT_START_FREQ;
dec->endFreq = DEFAULT_END_FREQ;
uint16_t freqRange = dec->endFreq - dec->startFreq;
dec->dataSpacing =
freqRange /
(encoder.toneCount - 1);
dec->sampleRate = SAMPLE_RATE;
dec->symbolSamples =
SAMPLE_RATE / 50;
SAMPLE_RATE / DEFAULT_SYMBOL_RATE;
dec->clock0 = goertzel_bin_freq(
6000,
SAMPLE_RATE,
dec->symbolSamples
);
dec->clock1 = goertzel_bin_freq(
6300,
SAMPLE_RATE,
DEFAULT_CLOCK0_FREQ,
dec->sampleRate,
dec->symbolSamples
);
/*
float spacing =
(float) (dec->endFreq - dec->startFreq)
/
(dec->bitCount * 2 - 1);
*/
dec->clock1 = goertzel_bin_freq(
DEFAULT_CLOCK1_FREQ,
dec->sampleRate,
dec->symbolSamples
);
decoder_rebuild_tones(dec);
}
void decoder_rebuild_tones(
Decoder *dec)
{
int index = 0;
void decoder_rebuild_tones(Decoder *dec) {
dec->toneCount = 1 << dec->bitsPerSymbol;
for (int i = 0; i < dec->bitCount; i++) {
printf("Decoder tones: %d\n", dec->toneCount);
// clock tones
goertzel_init(
&dec->tones[index++],
dec->startFreq +
dec->dataSpacing * (i * 2),
SAMPLE_RATE,
&dec->tones[0],
dec->clock0,
dec->sampleRate,
dec->symbolSamples
);
goertzel_init(
&dec->tones[index++],
dec->startFreq +
dec->dataSpacing * (i * 2 + 1),
SAMPLE_RATE,
&dec->tones[1],
dec->clock1,
dec->sampleRate,
dec->symbolSamples
);
// data tones
for (int i = 0; i < dec->toneCount; i++) {
uint16_t freq =
dec->startFreq +
i * dec->dataSpacing;
// MUST match encoder
freq = goertzel_bin_freq(
freq,
dec->sampleRate,
dec->symbolSamples
);
goertzel_init(
&dec->tones[i + 2],
freq,
dec->sampleRate,
dec->symbolSamples
);
printf(
"Tone %d = %dHz\n",
i,
freq
);
}
goertzel_init(
&dec->tones[index++],
dec->clock0,
SAMPLE_RATE,
dec->symbolSamples
memset(
dec->energies,
0,
sizeof(dec->energies)
);
goertzel_init(
&dec->tones[index++],
dec->clock1,
SAMPLE_RATE,
dec->symbolSamples
);
dec->sampleCount = 0;
dec->sampleHistoryWrite = 0;
dec->sampleHistoryCount = 0;
dec->totalSamples = 0;
dec->maxEnergy = 0.0f;
dec->clockEnergy0 = 0.0f;
dec->clockEnergy1 = 0.0f;
dec->clockRatio = 0.0f;
dec->bestSyncScore = 0;
dec->bestSyncPhase = 0;
dec->bufferSize = 0;
dec->rxState = RX_SEARCH;
dec->phaseSearchReady = false;
dec->phaseWait = 0;
dec->phaseOffset = 0;
dec->clockHistory = 0;
dec->clockCount = 0;
dec->goodClocks = 0;
dec->searchOffset = 0;
dec->locked = false;
memset(dec->energies, 0, sizeof(dec->energies));
}
static int detect_clock(Decoder *dec) {
float e0 = tone_energy(dec, 16);
float e1 = tone_energy(dec, 17);
// Use instantaneous per-symbol energies (dec->energies) for fast detection
float e0 = dec->energies[0];
float e1 = dec->energies[1];
float max = fmaxf(e0, e1);
float min = fminf(e0, e1);
dec->clockEnergy0 = e0;
dec->clockEnergy1 = e1;
dec->clockRatio = min > 0.0f ? max / min : 0.0f;
dec->energies[16] = e0;
dec->energies[17] = e1;
dec->maxEnergy = fmaxf(dec->maxEnergy * 0.85f, max);
/*
Ignore silence/noise.
Your real signal is around 0.1+
noise is around 0.001-0.01
*/
if (max < 0.001f)
// Simple silence check
float peak = fmaxf(e0, e1);
if (peak < 1e-7f)
return -1;
return e1 > e0;
// Determine which clock tone is stronger with a small hysteresis
if (e1 > e0 * 1.05f)
return 1;
if (e0 > e1 * 1.05f)
return 0;
return -1; // uncertain
}
static void clock_search(
Decoder *dec,
int clock)
{
if(clock < 0)
{
int clock) {
if (clock < 0) {
dec->clockHistory = 0;
dec->clockCount = 0;
return;
@@ -356,17 +189,15 @@ static void clock_search(
(clock & 1);
if(dec->clockCount < 8)
if (dec->clockCount < 8)
dec->clockCount++;
if(dec->clockCount >= 8)
{
if (dec->clockCount >= 8) {
uint8_t h = dec->clockHistory & 0xff;
if(h == 0x55 || h == 0xAA)
{
if (h == 0x55 || h == 0xAA) {
printf("CLOCK LOCK pattern=%02X\n", h);
dec->goodClocks = 8;
dec->clockHistory = 0;
@@ -378,112 +209,293 @@ static void clock_search(
dec->goodClocks = 0;
}
}
static uint8_t decode_symbol(Decoder *dec) {
uint8_t byte = 0;
int index = 0;
for (int bit = 0; bit < dec->bitCount; ++bit) {
float e0 = tone_energy(dec, index);
float e1 = tone_energy(dec, index + 1);
float max = fmaxf(e0, e1);
static void update_all_energies(Decoder *dec) {
const int tones = dec->toneCount + 2;
dec->energies[index++] = e0;
dec->energies[index++] = e1;
dec->maxEnergy = fmaxf(dec->maxEnergy * 0.85f, max);
uint64_t oldest = dec->totalSamples - dec->sampleHistoryCount;
if (dec->totalSamples < dec->symbolSamples)
return; // not enough samples yet
if (e1 > e0)
byte |= 1u << bit;
uint64_t start = dec->totalSamples - dec->symbolSamples;
if (start < oldest) start = oldest;
Goertzel work[MAX_FSK_TONES + 2];
memcpy(work, dec->tones, sizeof(Goertzel) * tones);
for (int i = 0; i < tones; ++i) {
work[i].s1 = 0.0f;
work[i].s2 = 0.0f;
}
dec->lastDecodedByte = byte;
return byte;
for (uint32_t s = 0; s < dec->symbolSamples; ++s) {
float sample;
if (!decoder_history_get(dec, start + s, &sample)) { sample = 0.0f; }
for (int t = 0; t < tones; ++t)
goertzel_add(&work[t], sample);
}
float maxE = 0.0f;
for (int t = 0; t < tones; ++t) {
float e = goertzel_energy(&work[t]);
// Instantaneous per-symbol energy
dec->energies[t] = e;
if (t == 0) dec->clockEnergy0 = e;
if (t == 1) dec->clockEnergy1 = e;
if (e > maxE) maxE = e;
}
// Faster decay so visualization follows symbol changes
dec->maxEnergy = fmaxf(dec->maxEnergy * 0.8f, maxE);
// Maintain a short visual smoothing for clocks so bars don't flicker
if (!isfinite(dec->clockEnergy0)) dec->clockEnergy0 = dec->energies[0];
if (!isfinite(dec->clockEnergy1)) dec->clockEnergy1 = dec->energies[1];
dec->clockEnergy0 = dec->clockEnergy0 * 0.7f + dec->energies[0] * 0.3f;
dec->clockEnergy1 = dec->clockEnergy1 * 0.7f + dec->energies[1] * 0.3f;
#ifdef DEBUG_ENERGIES
int show = dec->toneCount + 2;
int upto = show < 12 ? show : 12;
printf("ENERGIES(start=%llu):", (unsigned long long) start);
for (int i = 0; i < upto; ++i) {
printf(" %d:%.6f", i, dec->energies[i]);
}
printf(" maxE=%.6f clk0=%.6f clk1=%.6f\n", dec->maxEnergy, dec->clockEnergy0, dec->clockEnergy1);
#endif
}
static int decode_symbol(
Decoder *dec) {
float bestEnergy = 0;
int bestTone = 0;
/*
data tones start after clock tones
*/
for (int i = 0; i < dec->toneCount; i++) {
float e =
dec->energies[i + 2];
if (e > bestEnergy) {
bestEnergy = e;
bestTone = i;
}
}
return bestTone;
}
bool decoder_push_byte(
Decoder *dec,
uint8_t b) {
if (dec->rxState == RX_MAGIC) {
dec->magicBuffer[dec->magicIndex++] = b;
printf("MAGIC %02X\n", b);
if (dec->magicIndex >= MAGIC_SIZE) {
if (memcmp(dec->magicBuffer,
PACKET_MAGIC,
MAGIC_SIZE) == 0) {
printf("MAGIC OK\n");
dec->rxState = RX_DATA;
dec->bufferSize = 0;
dec->currentByte = 0;
dec->bitIndex = 0;
}
printf("BAD MAGIC\n");
dec->magicIndex = 0;
dec->preambleCount = 0;
dec->rxState = RX_SEARCH;
}
return false;
}
if (dec->rxState == RX_DATA) {
if (dec->bufferSize < sizeof(dec->buffer)) {
dec->buffer[dec->bufferSize++] = b;
}
if(decoder_parse_packet(dec))
{
printf("PACKET OK\n");
decoder_reset_rx(dec);
}
}
return false;
}
static void decoder_push_symbol(
Decoder *dec,
unsigned symbol) {
for (int i = 0; i < dec->bitsPerSymbol; i++) {
int bit = (symbol >> i) & 1;
dec->currentByte |= bit << dec->bitIndex;
if (++dec->bitIndex == 8) {
printf("BYTE %02X\n", dec->currentByte);
decoder_push_byte(
dec,
dec->currentByte);
dec->currentByte = 0;
dec->bitIndex = 0;
}
}
}
static bool clock_valid(Decoder *dec) {
float e0 = dec->energies[0];
float e1 = dec->energies[1];
float peak = fmaxf(e0, e1);
if (peak < dec->maxEnergy * 0.10f)
return false;
float ratio = fmaxf(e0, e1) /
(fminf(e0, e1) + 1e-12f);
return ratio > 1.3f;
}
void decoder_reset_rx(Decoder *dec) {
dec->rxState = RX_SEARCH;
dec->locked = false;
dec->preambleCount = 0;
dec->magicIndex = 0;
dec->bufferSize = 0;
dec->currentByte = 0;
dec->bitIndex = 0;
dec->clockHistory = 0;
dec->clockCount = 0;
dec->goodClocks = 0;
}
void decoder_process(
Decoder *dec,
float *samples,
uint32_t count) {
const int tones = dec->bitCount * 2 + 2;
const int tones = dec->toneCount + 2;
for (uint32_t i = 0; i < count; i++) {
decoder_history_push(dec, samples[i]);
if (dec->rxState == RX_SEARCH || dec->rxState == RX_LOCKED) {
// Always feed samples to Goertzel for continuous analysis
for (int t = 0; t < tones; t++)
goertzel_add(&dec->tones[t], samples[i]);
}
if (dec->rxState == RX_ALIGN) {
if (dec->phaseWait > 0)
dec->phaseWait--;
dec->sampleCount += 1;
if (dec->phaseWait == 0) {
decoder_reset_symbol_filters(dec);
dec->sampleCount = 0;
dec->rxState = RX_LOCKED;
printf("PHASE LOCKED offset=%d\n", dec->phaseOffset);
}
continue;
}
dec->sampleCount++;
if (dec->sampleCount < dec->symbolSamples)
if (dec->sampleCount != dec->symbolSamples)
continue;
dec->sampleCount = 0;
switch (dec->rxState) {
case RX_SEARCH: {
// Update all energies for real-time visual feedback
update_all_energies(dec);
int clock = detect_clock(dec);
clock_search(dec, clock);
if (dec->goodClocks >= 8) {
dec->searchOffset++;
if (dec->searchOffset >= 4) {
dec->searchOffset = 0;
if (try_phase_search(dec)) {
dec->phaseSearchReady = false;
dec->sampleCount = 0;
printf("CLOCK LOCKED\n");
dec->rxState = RX_SYNC;
dec->locked = true;
dec->clockHistory = 0;
dec->clockCount = 0;
dec->goodClocks = 0;
}
// DON'T reset filters - keep energy visible
break;
}
case RX_SYNC: {
update_all_energies(dec);
int symbol = decode_symbol(dec);
if (clock_valid(dec)) {
dec->lostClockCount = 0;
printf("Symbol %d energy=%f\n", symbol, dec->energies[symbol + 2]);
} else {
// Phase search failed, reset and try again
dec->goodClocks = 0;
dec->clockHistory = 0;
dec->clockCount = 0;
}
}
}
if (++dec->lostClockCount > 8) {
printf("CLOCK LOST\n");
decoder_reset_rx(dec);
break;
}
case RX_LOCKED: {
int clock = detect_clock(dec);
if (clock < 0) {
printf("clock lost\n");
dec->rxState = RX_SEARCH;
dec->locked = false;
dec->sampleCount = 0;
dec->clockHistory = 0;
dec->clockCount = 0;
dec->goodClocks = 0;
dec->searchOffset = 0;
decoder_reset_symbol_filters(dec);
break;
}
uint8_t b = decode_symbol(dec);
if (dec->bufferSize < sizeof(dec->buffer))
dec->buffer[dec->bufferSize++] = b;
if (symbol == (dec->preambleCount & 7)) {
if (dec->preambleCount < 255)
dec->preambleCount++;
if (dec->preambleCount >= 16) {
printf("PREAMBLE OK\n");
dec->rxState = RX_MAGIC;
dec->magicIndex = 0;
dec->currentByte = 0;
dec->bitIndex = 0;
printf("DATA %02X\n", b);
break;
}
} else {
if (dec->preambleCount > 4)
dec->preambleCount -= 4;
else
dec->preambleCount = 0;
}
break;
}
case RX_MAGIC:
case RX_DATA: {
update_all_energies(dec);
if (!clock_valid(dec)) {
if (++dec->lostClockCount > 8) {
printf("CLOCK LOST\n");
decoder_reset_rx(dec);
break;
}
} else {
dec->lostClockCount = 0;
}
int symbol = decode_symbol(dec);
decoder_push_symbol(dec, symbol);
break;
}
}
}
}
@@ -494,19 +506,10 @@ void input_callback(
int len) {
Decoder *dec = userdata;
// stream contains audio captured from sound card input
float *samples = (float *) stream;
int count = len / sizeof(float);
// Read from internal bus for loopback
for (int i = 0; i < count; i++) {
if (internalBusRead != internalBusWrite) {
samples[i] = internalBusSamples[internalBusRead];
internalBusRead = (internalBusRead + 1) % INTERNAL_BUFFER_SIZE;
} else {
samples[i] = 0.0f;
}
}
decoder_process(
dec,
samples,
@@ -526,7 +529,7 @@ void initAudioDecoder(SDL_Renderer *renderer) {
inSpec.freq = SAMPLE_RATE;
inSpec.format = AUDIO_F32SYS;
inSpec.channels = 1;
inSpec.samples = 512;
inSpec.samples = 960; // Match symbolSamples (48000/50)
inSpec.callback = input_callback;
inSpec.userdata = &decoder;
@@ -538,7 +541,14 @@ void initAudioDecoder(SDL_Renderer *renderer) {
printf("Failed to open audio decoder: %s\n", SDL_GetError());
SDL_Quit();
}
printf("Input device spec: freq=%d channels=%d samples=%d\n",
realSpec.freq, realSpec.channels, realSpec.samples);
printf("Input device spec: freq=%d channels=%d samples=%d format=%d\n",
realSpec.freq, realSpec.channels, realSpec.samples, realSpec.format);
// Update decoder sample rate and symbolSamples based on actual device
decoder.sampleRate = realSpec.freq;
decoder.symbolSamples = realSpec.freq / DEFAULT_SYMBOL_RATE;
// Rebuild tones to match actual sample rate
decoder_rebuild_tones(&decoder);
SDL_PauseAudioDevice(inputDev, 0);
}
+31 -9
View File
@@ -5,25 +5,36 @@
#ifndef AUDIOCODER_DECODER_H
#define AUDIOCODER_DECODER_H
#define MAX_BITS 16
#define PREAMBLE_SYMBOLS 128
#define DECODER_SAMPLE_HISTORY 262144
#define MAX_FSK_TONES 32
#include <SDL_audio.h>
#include <SDL_render.h>
#include <stdint.h>
#include "goertzel.h"
typedef enum {
typedef enum
{
RX_SEARCH,
RX_ALIGN,
RX_SYNC,
RX_LOCKED
RX_MAGIC,
RX_DATA
} RxState;
typedef enum
{
PKT_WAIT_PREAMBLE,
PKT_WAIT_MAGIC,
PKT_WAIT_LENGTH,
PKT_WAIT_DATA,
PKT_WAIT_CRC1,
PKT_WAIT_CRC2
} PacketState;
typedef struct {
uint8_t bitCount;
uint8_t bitsPerSymbol;
uint8_t toneCount;
uint16_t startFreq;
uint16_t endFreq;
@@ -32,9 +43,16 @@ typedef struct {
uint16_t clock0;
uint16_t clock1;
uint8_t magicBuffer[16];
uint32_t magicIndex;
uint32_t preambleCount;
uint8_t lostClockCount;
uint8_t goodClockCount;
uint32_t symbolSamples;
uint32_t sampleRate;
uint32_t sampleCount;
@@ -43,7 +61,9 @@ typedef struct {
uint32_t sampleHistoryCount;
uint64_t totalSamples;
Goertzel tones[MAX_BITS * 2 + 2];
Goertzel tones[MAX_FSK_TONES + 2];
float energies[MAX_FSK_TONES + 2];
uint8_t currentByte;
uint8_t bitIndex;
@@ -55,7 +75,6 @@ typedef struct {
bool lastClock;
float energies[MAX_BITS * 2 + 2];
float maxEnergy;
float clockEnergy0;
float clockEnergy1;
@@ -65,6 +84,8 @@ typedef struct {
int bestSyncPhase;
RxState rxState;
PacketState packetState;
bool expectedClock;
float lastClockEnergy;
@@ -116,5 +137,6 @@ extern volatile uint32_t internalBusWrite;
extern volatile uint32_t internalBusRead;
void initAudioDecoder(SDL_Renderer *renderer);
void decoder_reset_rx(Decoder *dec);
#endif //AUDIOCODER_DECODER_H
+166 -114
View File
@@ -9,72 +9,93 @@
#include <SDL_stdinc.h>
#include "goertzel.h"
#include "modem_settings.h"
#include "packet.h"
SDL_AudioDeviceID dev;
AudioData encodedAudioData;
Encoder encoder;
// Internal audio bus
float internalBusSamples[INTERNAL_BUFFER_SIZE] = {0};
volatile uint32_t internalBusWrite = 0;
volatile uint32_t internalBusRead = 0;
#define PREAMBLE_SYMBOLS 128
const uint8_t syncBytes[4] =
const uint8_t syncSymbols[] =
{
'B',
'R',
'N',
'Q'
2,0,
2,2,
6,2,
1,2
};
static void encoder_set_bit(
static void encoder_set_symbol(
AudioData *audio,
Encoder *enc,
unsigned bitIndex,
int bit)
unsigned symbol)
{
/*
float spacing =
(float)(enc->endFreq - enc->startFreq) /
(enc->bitCount * 2 - 1);
symbol:
0 ... number of FSK tones-1
*/
uint16_t freq =
enc->startFreq +
enc->dataSpacing * (bitIndex * 2 + bit);
symbol * enc->dataSpacing;
freq = goertzel_bin_freq(
freq,
SAMPLE_RATE,
enc->sampleRate,
enc->symbolSamples
);
);
SynthVoice *v =
&audio->synthVoices[bitIndex + 1];
&audio->synthVoices[DATA_VOICE];
v->frequency = freq;
v->phaseStep =
((uint64_t)freq << 32) / SAMPLE_RATE;
((uint64_t)freq << 32) /
enc->sampleRate;
v->volume = 255;
}
static int encoder_get_bit(Encoder *enc) {
if (enc->byteIndex >= enc->bufferSize)
static int encoder_get_symbol(
Encoder *enc)
{
unsigned symbol=0;
for(unsigned i=0;
i<enc->bitsPerSymbol;
i++)
{
if(enc->packetIndex >= enc->packetSize)
return -1;
int bit = (enc->buffer[enc->byteIndex] >> enc->bitIndex) & 1;
int bit =
(enc->packet[enc->packetIndex]
>>enc->bitIndex)&1;
symbol |= bit<<i;
enc->bitIndex++;
if (enc->bitIndex == 8) {
enc->bitIndex = 0;
enc->byteIndex++;
if(enc->bitIndex==8)
{
enc->bitIndex=0;
enc->packetIndex++;
}
}
return bit;
return symbol;
}
static void encoder_clock(
@@ -94,109 +115,91 @@ static void encoder_clock(
clock->frequency = freq;
clock->phaseStep =
((uint64_t)freq << 32) /
SAMPLE_RATE;
(enc->sampleRate ? enc->sampleRate : SAMPLE_RATE);
clock->volume = 255;
// Debug: print clock toggle
//printf("ENCODER CLOCK: toggled to %u Hz (state=%d)\n", freq, enc->clockState);
}
void encoder_next_symbol(
Encoder *enc,
AudioData *audio)
{
encoder_clock(enc, audio);
encoder_clock(enc,audio);
switch(enc->state)
{
case TX_PREAMBLE:
{
unsigned symbol =
enc->preambleSymbols %
enc->toneCount;
/* transmit all-zero symbol */
for(unsigned i=0;i<enc->bitCount;i++)
encoder_set_bit(audio,enc,i,0);
encoder_set_symbol(
audio,
enc,
symbol);
enc->preambleSymbols++;
if(enc->preambleSymbols >= PREAMBLE_SYMBOLS)
if(enc->preambleSymbols>=PREAMBLE_SYMBOLS)
{
enc->syncIndex = 0;
enc->bitIndex = 0;
enc->state = TX_SYNC;
enc->packetIndex=0;
enc->bitIndex=0;
enc->state=TX_PACKET;
}
break;
}
case TX_SYNC:
{
uint8_t byte =
syncBytes[enc->syncIndex];
for(unsigned i=0;i<enc->bitCount;i++)
case TX_PACKET:
{
encoder_set_bit(
int symbol =
encoder_get_symbol(enc);
if(symbol<0)
{
enc->state=TX_END;
break;
}
encoder_set_symbol(
audio,
enc,
i,
(byte >> i) & 1);
}
enc->syncIndex++;
if(enc->syncIndex ==
sizeof(syncBytes))
{
enc->state = TX_DATA;
enc->byteIndex = 0;
enc->bitIndex = 0;
}
symbol);
break;
}
case TX_DATA:
{
int finished = 0;
for(unsigned i=0;i<enc->bitCount;i++)
{
int bit =
encoder_get_bit(enc);
if(bit < 0)
{
bit = 0;
finished++;
}
encoder_set_bit(
audio,
enc,
i,
bit);
}
if(finished ==
enc->bitCount)
{
enc->state = TX_END;
}
break;
}
case TX_END:
stopEncoder(enc);
break;
default:
break;
}
enc->samplesRemaining =
enc->samplesRemaining=
enc->symbolSamples;
}
void startEncoder(Encoder *enc)
{
encoder_build_packet(enc, enc->buffer, enc->bufferSize);
enc->state = TX_PREAMBLE;
enc->transmitting = true;
@@ -214,7 +217,7 @@ void stopEncoder(Encoder *enc)
enc->transmitting=false;
enc->state=TX_IDLE;
for(int i=0;i<MAX_BITS+1;i++)
for(int i=0;i<2;i++)
{
encodedAudioData.synthVoices[i].volume=0;
}
@@ -234,15 +237,16 @@ void audio_callback(void *userdata, Uint8 *stream, int len) {
int samples = len / sizeof(float);
for (int i = 0; i < samples; i++) {
if (enc->samplesRemaining == 0)
if (enc->samplesRemaining == 0) {
encoder_next_symbol(enc, audio);
}
enc->samplesRemaining--;
float mix = 0.0f;
int active = 0;
for (unsigned v = 0; v < enc->bitCount + 1; v++) {
for(unsigned v=0; v<2; v++) {
SynthVoice *voice = &audio->synthVoices[v];
if (voice->volume == 0)
@@ -261,10 +265,6 @@ void audio_callback(void *userdata, Uint8 *stream, int len) {
float output = active ? mix / (active + 1) : 0.0f;
out[i] = output;
// Write to internal bus for loopback
internalBusSamples[internalBusWrite] = output;
internalBusWrite = (internalBusWrite + 1) % INTERNAL_BUFFER_SIZE;
}
}
@@ -272,31 +272,64 @@ void initEncoder()
{
memset(&encoder,0,sizeof(Encoder));
encoder.bufferSize = 0;
encoder.bitCount = 8;
/*
8-FSK:
3 bits per symbol
8 frequencies
*/
encoder.bitsPerSymbol = 3;
encoder.toneCount = 8;
encoder.byteIndex = 0;
encoder.startFreq = 1000;
encoder.endFreq = 5000;
encoder.dataSpacing = 250;
encoder.symbolSamples = SAMPLE_RATE / 50;
encoder.clock0 = goertzel_bin_freq(
6000,
SAMPLE_RATE,
encoder.symbolSamples
);
encoder.startFreq =
DEFAULT_START_FREQ;
encoder.clock1 = goertzel_bin_freq(
6300,
encoder.endFreq =
DEFAULT_END_FREQ;
uint16_t freqRange =
encoder.endFreq -
encoder.startFreq;
/*
N tones need N-1 intervals
*/
encoder.dataSpacing =
freqRange /
(encoder.toneCount-1);
encoder.symbolSamples =
SAMPLE_RATE /
DEFAULT_SYMBOL_RATE;
encoder.clock0 =
goertzel_bin_freq(
DEFAULT_CLOCK0_FREQ,
SAMPLE_RATE,
encoder.symbolSamples
);
encoder.symbolSamples);
encoder.clock1 =
goertzel_bin_freq(
DEFAULT_CLOCK1_FREQ,
SAMPLE_RATE,
encoder.symbolSamples);
encoder.state = TX_IDLE;
encoder.transmitting = false;
}
void initAudioEncoder() {
@@ -307,16 +340,35 @@ void initAudioEncoder() {
spec.freq = SAMPLE_RATE;
spec.format = AUDIO_F32SYS;
spec.channels = 1;
spec.samples = 4096;
spec.samples = 960; // requested buffer
spec.callback = audio_callback;
encodedAudioData.encoder = &encoder;
spec.userdata = &encodedAudioData;
dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
printf("Encoder device opened: dev=%u\n", dev);
SDL_AudioSpec realSpec = {0};
dev = SDL_OpenAudioDevice(NULL, 0, &spec, &realSpec, 0);
printf("Encoder device opened: dev=%u (requested %dHz/%d samples)\n", dev, spec.freq, spec.samples);
if (dev == 0) {
printf("Failed to open audio encoder: %s\n", SDL_GetError());
SDL_Quit();
}
// Update encoder sample rate and symbolSamples based on actual device
encoder.sampleRate = realSpec.freq;
encoder.symbolSamples = realSpec.freq / DEFAULT_SYMBOL_RATE;
encoder.clock0 = goertzel_bin_freq(
DEFAULT_CLOCK0_FREQ,
encoder.sampleRate,
encoder.symbolSamples
);
encoder.clock1 = goertzel_bin_freq(
DEFAULT_CLOCK1_FREQ,
encoder.sampleRate,
encoder.symbolSamples
);
printf("Encoder real spec: freq=%d channels=%d samples=%d -> symbolSamples=%u\n",
realSpec.freq, realSpec.channels, realSpec.samples, encoder.symbolSamples);
SDL_PauseAudioDevice(dev, 0);
}
+11 -14
View File
@@ -6,8 +6,8 @@
#define AUDIOCODER_ENCODER_H
#define SAMPLE_RATE 48000
#define MAX_BITS 16
#define CLOCK_VOICE MAX_BITS
#define CLOCK_VOICE 0
#define DATA_VOICE 1
#define MAX_PACKET 223
#include <SDL_audio.h>
@@ -23,21 +23,23 @@ typedef struct SynthVoice {
typedef enum {
TX_IDLE,
TX_PREAMBLE,
TX_SYNC,
TX_DATA,
TX_PACKET,
TX_END
} TxState;
typedef struct {
unsigned int bitCount;
unsigned int bitsPerSymbol;
unsigned int toneCount;
unsigned int byteIndex;
unsigned int bitIndex;
uint8_t buffer[1024 * 1024 * 8];
unsigned int bufferSize;
uint8_t packet[MAX_PACKET];
uint32_t packetSize;
uint32_t packetIndex;
uint16_t startFreq;
@@ -53,6 +55,7 @@ typedef struct {
uint32_t symbolSamples;
uint32_t samplesRemaining;
uint32_t sampleRate; // actual device sample rate
TxState state;
@@ -66,7 +69,7 @@ typedef struct {
typedef struct AudioData {
SynthVoice synthVoices[MAX_BITS + 1];
SynthVoice synthVoices[2];
Encoder *encoder;
} AudioData;
@@ -74,18 +77,12 @@ extern Encoder encoder;
extern AudioData encodedAudioData;
extern SDL_AudioDeviceID dev;
// Internal audio bus for loopback - simple ring buffer
#define INTERNAL_BUFFER_SIZE 262144
extern float internalBusSamples[INTERNAL_BUFFER_SIZE];
extern volatile uint32_t internalBusWrite;
extern volatile uint32_t internalBusRead;
void startEncoder(Encoder *enc);
void stopEncoder(Encoder *enc);
void initAudioEncoder();
extern const uint8_t syncBytes[4];
extern const uint8_t syncSymbols[8];
#endif //AUDIOCODER_ENCODER_H
+8 -47
View File
@@ -6,70 +6,31 @@
#include <math.h>
void goertzel_init(
Goertzel *g,
float freq,
float sampleRate,
int samples)
void goertzel_init(Goertzel *g, float freq, float sampleRate, int samples)
{
float omega =
2.0f * M_PI * freq / sampleRate;
g->coeff =
2.0f*cosf(omega);
float omega = 2.0f * M_PI * freq / sampleRate;
g->coeff = 2.0f*cosf(omega);
g->s1=0;
g->s2=0;
}
void goertzel_add(
Goertzel *g,
float sample)
void goertzel_add(Goertzel *g, float sample)
{
float s =
sample +
g->coeff*g->s1 -
g->s2;
float s = sample + g->coeff*g->s1 - g->s2;
g->s2=g->s1;
g->s1=s;
}
float goertzel_energy(
Goertzel *g)
float goertzel_energy(Goertzel *g)
{
float power =
g->s1*g->s1 +
g->s2*g->s2 -
g->coeff*g->s1*g->s2;
float power = g->s1*g->s1 + g->s2*g->s2 - g->coeff*g->s1*g->s2;
g->s1=0;
g->s2=0;
return power;
}
int goertzel_bin_freq(
int freq,
int sampleRate,
int samples)
int goertzel_bin_freq(int freq, int sampleRate, int samples)
{
int bin = (freq * samples + sampleRate / 2) / sampleRate;
return bin * sampleRate / samples;
}
+31 -22
View File
@@ -1,14 +1,11 @@
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <SDL2/SDL.h>
#include "font.h"
#include "cpustatusui.h"
#include "decoder.h"
#include "goertzel.h"
#include "texteditor.h"
#include "hexdump.h"
//The window we'll be rendering to
SDL_Window *window = NULL;
@@ -38,8 +35,7 @@ static const char *tx_state_name(TxState state) {
switch (state) {
case TX_IDLE: return "IDLE";
case TX_PREAMBLE: return "PREAMBLE";
case TX_SYNC: return "SYNC";
case TX_DATA: return "DATA";
case TX_PACKET: return "PACKET";
case TX_END: return "END";
}
return "?";
@@ -48,9 +44,9 @@ static const char *tx_state_name(TxState state) {
static const char *rx_state_name(RxState state) {
switch (state) {
case RX_SEARCH: return "SEARCH";
case RX_ALIGN: return "ALIGN";
case RX_SYNC: return "SYNC";
case RX_LOCKED: return "LOCKED";
case RX_MAGIC: return "MAGIC";
case RX_DATA: return "DATA";
}
return "?";
}
@@ -65,8 +61,8 @@ static const char *setting_name(int setting) {
case 5: return "CLOCK1";
case 6: return "OUT DEVICE";
case 7: return "IN DEVICE";
default: return "UNKNOWN";
}
return "?";
}
static void apply_modem_settings(void) {
@@ -79,12 +75,12 @@ static void apply_modem_settings(void) {
encoder.clock0,
SAMPLE_RATE,
encoder.symbolSamples);
encoder.clock1 = goertzel_bin_freq(
encoder.clock1,
SAMPLE_RATE,
encoder.symbolSamples);
decoder.bitCount = encoder.bitCount;
decoder.startFreq = encoder.startFreq;
decoder.endFreq = encoder.endFreq;
decoder.dataSpacing = encoder.dataSpacing;
@@ -135,6 +131,7 @@ static void adjust_modem_setting(int delta) {
static void render_modem_text(SDL_Renderer *renderer) {
char line[256];
// TX info on the left
snprintf(
line,
sizeof(line),
@@ -147,6 +144,7 @@ static void render_modem_text(SDL_Renderer *renderer) {
encoder.syncIndex);
renderText(renderer, smallerFont, line, 10, 8);
// RX info on the right
snprintf(
line,
sizeof(line),
@@ -156,18 +154,19 @@ static void render_modem_text(SDL_Renderer *renderer) {
decoder.lastDecodedByte,
decoder.bestSyncScore,
decoder.bestSyncPhase);
renderText(renderer, smallerFont, line, 10, 22);
renderText(renderer, smallerFont, line, 640, 8);
// Clock info below RX
snprintf(
line,
sizeof(line),
"CLK E0 %.5f E1 %.5f R %.1f HIST %02X/%u",
"CLK E0 %08.5f E1 %08.5f R %02.1f HIST %02X/%u",
decoder.clockEnergy0,
decoder.clockEnergy1,
decoder.clockRatio,
decoder.clockHistory,
decoder.clockCount);
renderText(renderer, smallerFont, line, 10, 36);
renderText(renderer, smallerFont, line, 640, 22);
// Settings display with highlighting
const char *settingStr = setting_name(selectedModemSetting);
@@ -192,10 +191,11 @@ static void render_modem_text(SDL_Renderer *renderer) {
}
static void render_tone_bars(SDL_Renderer *renderer) {
const int tones = decoder.bitCount * 2 + 2;
const int dataTones = MAX_FSK_TONES;
const int tones = dataTones + 2; // clocks are indices 0,1
const int x = 500;
const int y = 560;
const int maxHeight = 72;
const int y = 650;
const int maxHeight = 192;
const int barWidth = 8;
const int gap = 4;
@@ -203,10 +203,15 @@ static void render_tone_bars(SDL_Renderer *renderer) {
SDL_Rect frame = {x - 4, y - maxHeight - 4, tones * (barWidth + gap) + 4, maxHeight + 24};
SDL_RenderDrawRect(renderer, &frame);
// Draw clocks first (indices 0 and 1)
for (int i = 0; i < tones; i++) {
int idx;
if (i < 2) idx = i; // clock indices 0,1
else idx = 2 + (i - 2); // data indices start at 2
float ratio = 0.0f;
if (decoder.maxEnergy > 0.0f)
ratio = decoder.energies[i] / decoder.maxEnergy;
ratio = decoder.energies[idx] / decoder.maxEnergy;
if (ratio > 1.0f)
ratio = 1.0f;
@@ -216,18 +221,18 @@ static void render_tone_bars(SDL_Renderer *renderer) {
barRect.x = x + i * (barWidth + gap);
barRect.y = y - barRect.h;
if (i >= decoder.bitCount * 2)
SDL_SetRenderDrawColor(renderer, 255, 192, 64, 255);
else if (i & 1)
SDL_SetRenderDrawColor(renderer, 80, 180, 255, 255);
if (idx < 2)
SDL_SetRenderDrawColor(renderer, 255, 192, 64, 255); // clock
else if ((idx - 2) & 1)
SDL_SetRenderDrawColor(renderer, 80, 180, 255, 255); // 1-tone
else
SDL_SetRenderDrawColor(renderer, 80, 255, 140, 255);
SDL_SetRenderDrawColor(renderer, 80, 255, 140, 255); // 0-tone
SDL_RenderFillRect(renderer, &barRect);
}
char label[128];
snprintf(label, sizeof(label), "TONE ENERGY: BIT0..BIT7, CLOCK0, CLOCK1");
snprintf(label, sizeof(label), "TONE ENERGY: CLOCK0, CLOCK1, BIT0..BIT15");
renderText(renderer, smallerFont, label, x, y + 6);
}
@@ -320,6 +325,9 @@ int init() {
updateDump(&transmitEditor, renderer);
// Auto-start encoder for testing
//startEncoder(&encoder);
SDL_Rect viewport = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
SDL_RenderSetViewport(renderer, &viewport);
@@ -477,6 +485,7 @@ int processEvent(SDL_Event e) {
break;
case SDLK_F5:
updateDump(&transmitEditor, renderer);
updateDump(&receivedEditor, renderer);
break;
case SDLK_F7:
startEncoder(&encoder);
+15
View File
@@ -0,0 +1,15 @@
#ifndef MODEM_SETTINGS_H
#define MODEM_SETTINGS_H
// Shared modem configuration defaults
#define DEFAULT_BIT_COUNT 16
#define DEFAULT_START_FREQ 1500
#define DEFAULT_END_FREQ 5000
#define DEFAULT_DATA_SPACING 250
// Symbols per second (used to compute samples per symbol)
#define DEFAULT_SYMBOL_RATE 10
#define DEFAULT_CLOCK0_FREQ 1000
#define DEFAULT_CLOCK1_FREQ 1300
#endif // MODEM_SETTINGS_H
+99 -109
View File
@@ -1,136 +1,126 @@
//
// Created by bruno on 13. 7. 2026.
//
#include "packet.h"
#include "crc32.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "cpustatusui.h"
#include "decoder.h"
#include "protocol.h"
#define MAGIC 0xCA55
#define PACKET_SIZE 223
uint8_t *build_stream(
uint8_t *file,
uint32_t size,
uint32_t *outSize) {
uint32_t packets =
(size + PACKET_SIZE - 1)
/
PACKET_SIZE;
/*
estimate:
preamble 256 bytes
sync 16 bytes
packets:
header 4
payload 223
crc 4
*/
uint32_t capacity =
256 +
16 +
packets * (4 + 2 + 2 + 223 + 4);
uint8_t *out =
malloc(capacity);
uint32_t pos = 0;
/*
preamble
*/
for (int i = 0; i < 256; i++)
out[pos++] = 0x55;
/*
sync
*/
uint8_t sync[] =
{
0xD3,
0x91,
0xAC,
0x55,
0x77,
0x22,
0xF0,
0x0F
};
void encoder_build_packet(
Encoder *enc,
uint8_t *data,
uint8_t length)
{
uint32_t p=0;
memcpy(
out + pos,
sync,
sizeof(sync));
&enc->packet[p],
PACKET_MAGIC,
MAGIC_SIZE);
pos += sizeof(sync);
p+=MAGIC_SIZE;
uint32_t offset = 0;
enc->packet[p++]=length;
for (uint32_t p = 0; p < packets; p++) {
uint16_t magic = MAGIC;
memcpy(
&enc->packet[p],
data,
length);
uint16_t packet = p;
p+=length;
uint16_t len =
size - offset > PACKET_SIZE ? PACKET_SIZE : size - offset;
uint16_t crc =
crc16(
&enc->packet[0],
p);
memcpy(out + pos, &magic, 2);
pos += 2;
enc->packet[p++]=crc&0xff;
enc->packet[p++]=crc>>8;
memcpy(out + pos, &packet, 2);
pos += 2;
enc->packetSize=p;
}
bool decoder_parse_packet(Decoder *dec)
{
if(dec->bufferSize < MAGIC_SIZE + 1 + 2)
return false;
if(memcmp(dec->buffer,
PACKET_MAGIC,
MAGIC_SIZE))
{
printf("Bad magic\n");
return false;
}
uint8_t length =
dec->buffer[MAGIC_SIZE];
uint32_t packetSize =
MAGIC_SIZE +
1 +
length +
2;
if(dec->bufferSize < packetSize)
return false;
uint16_t received =
dec->buffer[packetSize-2] |
(dec->buffer[packetSize-1] << 8);
uint16_t calculated =
crc16(dec->buffer,
packetSize-2);
if(received != calculated)
{
printf("CRC FAIL\n");
return true;
}
printf("PACKET OK len=%d\n", length);
printf("Payload:\n");
for(int i=0;i<length;i++)
printf("%02X ",
dec->buffer[MAGIC_SIZE+1+i]);
printf("\n");
return true;
}
uint16_t crc16(
const uint8_t *data,
uint32_t len)
{
uint16_t crc = 0xFFFF;
memcpy(out + pos, &len, 2);
pos += 2;
for(uint32_t i=0;i<len;i++)
{
crc ^= data[i];
memcpy(out + pos,
file + offset,
len);
pos += len;
uint32_t crc =
calcCRC32(
file + offset,
len);
memcpy(out + pos, &crc, 4);
pos += 4;
offset += len;
for(int b=0;b<8;b++)
{
if(crc & 1)
crc =
(crc >> 1) ^ 0xA001;
else
crc >>= 1;
}
}
*outSize = pos;
return out;
return crc;
}
+20 -12
View File
@@ -1,17 +1,25 @@
//
// Created by bruno on 13. 7. 2026.
//
#ifndef AUDIOCODER_PACKET_H
#define AUDIOCODER_PACKET_H
#ifndef PACKET_H
#define PACKET_H
#include <stdint.h>
#include "decoder.h"
#include "encoder.h"
uint8_t *build_stream(
uint8_t *file,
uint32_t size,
uint32_t *outSize
);
#define PREAMBLE_SYMBOLS 128
#define PREAMBLE_CHECK_SYMBOLS 16
#endif //AUDIOCODER_PACKET_H
#define MAGIC0 0xA5
#define MAGIC1 0x5A
#define MAGIC2 0xC3
#define MAGIC3 0x3C
#define MAX_PACKET 1024
uint16_t crc16(const uint8_t *data, uint32_t len);
void encoder_build_packet(Encoder *enc, uint8_t *data, uint8_t length);
bool decoder_parse_packet(Decoder *dec);
#endif
+23
View File
@@ -0,0 +1,23 @@
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include <stdint.h>
#define MAGIC_SIZE 4
#define CRC_SIZE 2
static const uint8_t PACKET_MAGIC[MAGIC_SIZE] =
{
0xA5,
0x5A,
0xC3,
0x3C
};
uint16_t crc16(
const uint8_t *data,
uint32_t len);
#endif