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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DiscordProjectSettings"> <component name="DiscordProjectSettings">
<option name="show" value="ASK" /> <option name="show" value="PROJECT_FILES" />
<option name="description" value="" /> <option name="description" value="" />
</component> </component>
</project> </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/=CppConstParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@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/=CppCoroutineCallResolveError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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="HINT" 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="WARNING" 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="WARNING" 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="WARNING" 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="WARNING" 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/=CppDeclarationHidesLocal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="HINT" 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" /> <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.c
decoder.h decoder.h
goertzel.c goertzel.c
goertzel.h) goertzel.h
protocol.h)
target_link_libraries(audiocoder SDL2 SDL2_ttf m) target_link_libraries(audiocoder SDL2 SDL2_ttf m)
+364 -354
View File
@@ -12,30 +12,24 @@
#include <string.h> #include <string.h>
#include "texteditor.h" #include "texteditor.h"
#include "modem_settings.h"
#include "packet.h"
#include "protocol.h"
Decoder decoder; Decoder decoder;
float tone_energy( float tone_energy(
Decoder *dec, Decoder *dec,
int index) { int index) {
return goertzel_energy( // Return the current accumulated Goertzel energy without clearing state.
&dec->tones[index] Goertzel *g = &dec->tones[index];
); return g->s1 * g->s1 + g->s2 * g->s2 - g->coeff * g->s1 * g->s2;
}
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;
}
} }
static void decoder_history_push(Decoder *dec, float sample) { static void decoder_history_push(Decoder *dec, float sample) {
dec->sampleHistory[dec->sampleHistoryWrite] = sample; dec->sampleHistory[dec->sampleHistoryWrite] = sample;
dec->sampleHistoryWrite = dec->sampleHistoryWrite =
(dec->sampleHistoryWrite + 1) % DECODER_SAMPLE_HISTORY; (dec->sampleHistoryWrite + 1) % DECODER_SAMPLE_HISTORY;
if (dec->sampleHistoryCount < DECODER_SAMPLE_HISTORY) if (dec->sampleHistoryCount < DECODER_SAMPLE_HISTORY)
dec->sampleHistoryCount++; dec->sampleHistoryCount++;
@@ -43,308 +37,147 @@ static void decoder_history_push(Decoder *dec, float sample) {
dec->totalSamples++; dec->totalSamples++;
} }
static bool decoder_history_get( static bool decoder_history_get(const Decoder *dec, uint64_t sampleIndex, float *sample) {
const Decoder *dec, if (sampleIndex >= dec->totalSamples) {
uint64_t sampleIndex,
float *sample)
{
if (sampleIndex >= dec->totalSamples)
return false; return false;
}
uint64_t oldest = uint64_t oldest = dec->totalSamples - dec->sampleHistoryCount;
dec->totalSamples - dec->sampleHistoryCount;
if (sampleIndex < oldest) if (sampleIndex < oldest) {
return false; return false;
}
uint32_t start = uint32_t start =
(dec->sampleHistoryWrite + DECODER_SAMPLE_HISTORY - dec->sampleHistoryCount) (dec->sampleHistoryWrite + DECODER_SAMPLE_HISTORY - dec->sampleHistoryCount)
% DECODER_SAMPLE_HISTORY; % 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; 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( void decoder_init(
Decoder *dec) { Decoder *dec) {
memset(dec, 0, sizeof(*dec)); memset(dec, 0, sizeof(*dec));
dec->bitCount = 8; dec->bitsPerSymbol = 3;
dec->toneCount = 8;
dec->startFreq = 1000; dec->startFreq = DEFAULT_START_FREQ;
dec->endFreq = 5000; dec->endFreq = DEFAULT_END_FREQ;
dec->dataSpacing = 250; uint16_t freqRange = dec->endFreq - dec->startFreq;
dec->dataSpacing =
freqRange /
(encoder.toneCount - 1);
dec->sampleRate = SAMPLE_RATE;
dec->symbolSamples = dec->symbolSamples =
SAMPLE_RATE / 50; SAMPLE_RATE / DEFAULT_SYMBOL_RATE;
dec->clock0 = goertzel_bin_freq( dec->clock0 = goertzel_bin_freq(
6000, DEFAULT_CLOCK0_FREQ,
SAMPLE_RATE, dec->sampleRate,
dec->symbolSamples
);
dec->clock1 = goertzel_bin_freq(
6300,
SAMPLE_RATE,
dec->symbolSamples dec->symbolSamples
); );
dec->clock1 = goertzel_bin_freq(
/* DEFAULT_CLOCK1_FREQ,
float spacing = dec->sampleRate,
(float) (dec->endFreq - dec->startFreq) dec->symbolSamples
/ );
(dec->bitCount * 2 - 1);
*/
decoder_rebuild_tones(dec); decoder_rebuild_tones(dec);
} }
void decoder_rebuild_tones( void decoder_rebuild_tones(Decoder *dec) {
Decoder *dec) dec->toneCount = 1 << dec->bitsPerSymbol;
{
int index = 0;
for (int i = 0; i < dec->bitCount; i++) { printf("Decoder tones: %d\n", dec->toneCount);
goertzel_init(
&dec->tones[index++],
dec->startFreq + // clock tones
dec->dataSpacing * (i * 2), goertzel_init(
SAMPLE_RATE, &dec->tones[0],
dec->clock0,
dec->sampleRate,
dec->symbolSamples
);
goertzel_init(
&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 dec->symbolSamples
); );
goertzel_init( goertzel_init(
&dec->tones[index++], &dec->tones[i + 2],
dec->startFreq + freq,
dec->dataSpacing * (i * 2 + 1), dec->sampleRate,
SAMPLE_RATE,
dec->symbolSamples dec->symbolSamples
); );
printf(
"Tone %d = %dHz\n",
i,
freq
);
} }
goertzel_init(
&dec->tones[index++], memset(
dec->clock0, dec->energies,
SAMPLE_RATE, 0,
dec->symbolSamples 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->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) { static int detect_clock(Decoder *dec) {
float e0 = tone_energy(dec, 16); // Use instantaneous per-symbol energies (dec->energies) for fast detection
float e1 = tone_energy(dec, 17); float e0 = dec->energies[0];
float e1 = dec->energies[1];
float max = fmaxf(e0, e1); // Simple silence check
float min = fminf(e0, e1); float peak = fmaxf(e0, e1);
if (peak < 1e-7f)
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)
return -1; 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( static void clock_search(
Decoder *dec, Decoder *dec,
int clock) int clock) {
{ if (clock < 0) {
if(clock < 0)
{
dec->clockHistory = 0; dec->clockHistory = 0;
dec->clockCount = 0; dec->clockCount = 0;
return; return;
@@ -352,21 +185,19 @@ static void clock_search(
dec->clockHistory = dec->clockHistory =
(dec->clockHistory << 1) | (dec->clockHistory << 1) |
(clock & 1); (clock & 1);
if(dec->clockCount < 8) if (dec->clockCount < 8)
dec->clockCount++; dec->clockCount++;
if(dec->clockCount >= 8) if (dec->clockCount >= 8) {
{
uint8_t h = dec->clockHistory & 0xff; uint8_t h = dec->clockHistory & 0xff;
if(h == 0x55 || h == 0xAA) if (h == 0x55 || h == 0xAA) {
{
printf("CLOCK LOCK pattern=%02X\n", h); printf("CLOCK LOCK pattern=%02X\n", h);
dec->goodClocks = 8; dec->goodClocks = 8;
dec->clockHistory = 0; dec->clockHistory = 0;
@@ -378,112 +209,293 @@ static void clock_search(
dec->goodClocks = 0; 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) { static void update_all_energies(Decoder *dec) {
float e0 = tone_energy(dec, index); const int tones = dec->toneCount + 2;
float e1 = tone_energy(dec, index + 1);
float max = fmaxf(e0, e1);
dec->energies[index++] = e0; uint64_t oldest = dec->totalSamples - dec->sampleHistoryCount;
dec->energies[index++] = e1; if (dec->totalSamples < dec->symbolSamples)
dec->maxEnergy = fmaxf(dec->maxEnergy * 0.85f, max); return; // not enough samples yet
if (e1 > e0) uint64_t start = dec->totalSamples - dec->symbolSamples;
byte |= 1u << bit; 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; for (uint32_t s = 0; s < dec->symbolSamples; ++s) {
return byte; 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( void decoder_process(
Decoder *dec, Decoder *dec,
float *samples, float *samples,
uint32_t count) { uint32_t count) {
const int tones = dec->bitCount * 2 + 2; const int tones = dec->toneCount + 2;
for (uint32_t i = 0; i < count; i++) { for (uint32_t i = 0; i < count; i++) {
decoder_history_push(dec, samples[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++) for (int t = 0; t < tones; t++)
goertzel_add(&dec->tones[t], samples[i]); goertzel_add(&dec->tones[t], samples[i]);
}
if (dec->rxState == RX_ALIGN) { dec->sampleCount += 1;
if (dec->phaseWait > 0)
dec->phaseWait--;
if (dec->phaseWait == 0) { if (dec->sampleCount != dec->symbolSamples)
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)
continue; continue;
dec->sampleCount = 0; dec->sampleCount = 0;
switch (dec->rxState) { switch (dec->rxState) {
case RX_SEARCH: { case RX_SEARCH: {
// Update all energies for real-time visual feedback
update_all_energies(dec);
int clock = detect_clock(dec); int clock = detect_clock(dec);
clock_search(dec, clock); clock_search(dec, clock);
if (dec->goodClocks >= 8) { if (dec->goodClocks >= 8) {
dec->searchOffset++; printf("CLOCK LOCKED\n");
if (dec->searchOffset >= 4) {
dec->searchOffset = 0; dec->rxState = RX_SYNC;
if (try_phase_search(dec)) { dec->locked = true;
dec->phaseSearchReady = false;
dec->sampleCount = 0;
dec->clockHistory = 0;
dec->clockCount = 0;
dec->goodClocks = 0;
} else {
// Phase search failed, reset and try again
dec->goodClocks = 0;
dec->clockHistory = 0;
dec->clockCount = 0;
}
}
}
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->clockHistory = 0;
dec->clockCount = 0; dec->clockCount = 0;
dec->goodClocks = 0; dec->goodClocks = 0;
dec->searchOffset = 0;
decoder_reset_symbol_filters(dec);
break;
} }
uint8_t b = decode_symbol(dec); // DON'T reset filters - keep energy visible
if (dec->bufferSize < sizeof(dec->buffer))
dec->buffer[dec->bufferSize++] = b;
printf("DATA %02X\n", b);
break; 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 {
if (++dec->lostClockCount > 8) {
printf("CLOCK LOST\n");
decoder_reset_rx(dec);
break;
}
}
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;
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,18 +506,9 @@ void input_callback(
int len) { int len) {
Decoder *dec = userdata; Decoder *dec = userdata;
// stream contains audio captured from sound card input
float *samples = (float *) stream; float *samples = (float *) stream;
int count = len / sizeof(float); 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( decoder_process(
dec, dec,
@@ -526,7 +529,7 @@ void initAudioDecoder(SDL_Renderer *renderer) {
inSpec.freq = SAMPLE_RATE; inSpec.freq = SAMPLE_RATE;
inSpec.format = AUDIO_F32SYS; inSpec.format = AUDIO_F32SYS;
inSpec.channels = 1; inSpec.channels = 1;
inSpec.samples = 512; inSpec.samples = 960; // Match symbolSamples (48000/50)
inSpec.callback = input_callback; inSpec.callback = input_callback;
inSpec.userdata = &decoder; inSpec.userdata = &decoder;
@@ -538,7 +541,14 @@ void initAudioDecoder(SDL_Renderer *renderer) {
printf("Failed to open audio decoder: %s\n", SDL_GetError()); printf("Failed to open audio decoder: %s\n", SDL_GetError());
SDL_Quit(); SDL_Quit();
} }
printf("Input device spec: freq=%d channels=%d samples=%d\n", printf("Input device spec: freq=%d channels=%d samples=%d format=%d\n",
realSpec.freq, realSpec.channels, realSpec.samples); 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); SDL_PauseAudioDevice(inputDev, 0);
} }
+31 -9
View File
@@ -5,25 +5,36 @@
#ifndef AUDIOCODER_DECODER_H #ifndef AUDIOCODER_DECODER_H
#define AUDIOCODER_DECODER_H #define AUDIOCODER_DECODER_H
#define MAX_BITS 16
#define PREAMBLE_SYMBOLS 128
#define DECODER_SAMPLE_HISTORY 262144 #define DECODER_SAMPLE_HISTORY 262144
#define MAX_FSK_TONES 32
#include <SDL_audio.h> #include <SDL_audio.h>
#include <SDL_render.h> #include <SDL_render.h>
#include <stdint.h> #include <stdint.h>
#include "goertzel.h" #include "goertzel.h"
typedef enum { typedef enum
{
RX_SEARCH, RX_SEARCH,
RX_ALIGN,
RX_SYNC, RX_SYNC,
RX_LOCKED RX_MAGIC,
RX_DATA
} RxState; } RxState;
typedef enum
{
PKT_WAIT_PREAMBLE,
PKT_WAIT_MAGIC,
PKT_WAIT_LENGTH,
PKT_WAIT_DATA,
PKT_WAIT_CRC1,
PKT_WAIT_CRC2
} PacketState;
typedef struct { typedef struct {
uint8_t bitCount; uint8_t bitsPerSymbol;
uint8_t toneCount;
uint16_t startFreq; uint16_t startFreq;
uint16_t endFreq; uint16_t endFreq;
@@ -32,9 +43,16 @@ typedef struct {
uint16_t clock0; uint16_t clock0;
uint16_t clock1; uint16_t clock1;
uint8_t magicBuffer[16];
uint32_t magicIndex;
uint32_t preambleCount;
uint8_t lostClockCount;
uint8_t goodClockCount;
uint32_t symbolSamples; uint32_t symbolSamples;
uint32_t sampleRate;
uint32_t sampleCount; uint32_t sampleCount;
@@ -43,7 +61,9 @@ typedef struct {
uint32_t sampleHistoryCount; uint32_t sampleHistoryCount;
uint64_t totalSamples; 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 currentByte;
uint8_t bitIndex; uint8_t bitIndex;
@@ -55,7 +75,6 @@ typedef struct {
bool lastClock; bool lastClock;
float energies[MAX_BITS * 2 + 2];
float maxEnergy; float maxEnergy;
float clockEnergy0; float clockEnergy0;
float clockEnergy1; float clockEnergy1;
@@ -65,6 +84,8 @@ typedef struct {
int bestSyncPhase; int bestSyncPhase;
RxState rxState; RxState rxState;
PacketState packetState;
bool expectedClock; bool expectedClock;
float lastClockEnergy; float lastClockEnergy;
@@ -116,5 +137,6 @@ extern volatile uint32_t internalBusWrite;
extern volatile uint32_t internalBusRead; extern volatile uint32_t internalBusRead;
void initAudioDecoder(SDL_Renderer *renderer); void initAudioDecoder(SDL_Renderer *renderer);
void decoder_reset_rx(Decoder *dec);
#endif //AUDIOCODER_DECODER_H #endif //AUDIOCODER_DECODER_H
+174 -122
View File
@@ -9,72 +9,93 @@
#include <SDL_stdinc.h> #include <SDL_stdinc.h>
#include "goertzel.h" #include "goertzel.h"
#include "modem_settings.h"
#include "packet.h"
SDL_AudioDeviceID dev; SDL_AudioDeviceID dev;
AudioData encodedAudioData; AudioData encodedAudioData;
Encoder encoder; 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 syncSymbols[] =
const uint8_t syncBytes[4] =
{ {
'B', 2,0,
'R', 2,2,
'N', 6,2,
'Q' 1,2
}; };
static void encoder_set_bit( static void encoder_set_symbol(
AudioData *audio, AudioData *audio,
Encoder *enc, Encoder *enc,
unsigned bitIndex, unsigned symbol)
int bit)
{ {
/* /*
float spacing = symbol:
(float)(enc->endFreq - enc->startFreq) / 0 ... number of FSK tones-1
(enc->bitCount * 2 - 1); */
*/
uint16_t freq = uint16_t freq =
enc->startFreq + enc->startFreq +
enc->dataSpacing * (bitIndex * 2 + bit); symbol * enc->dataSpacing;
freq = goertzel_bin_freq( freq = goertzel_bin_freq(
freq, freq,
SAMPLE_RATE, enc->sampleRate,
enc->symbolSamples enc->symbolSamples
); );
SynthVoice *v = SynthVoice *v =
&audio->synthVoices[bitIndex + 1]; &audio->synthVoices[DATA_VOICE];
v->frequency = freq; v->frequency = freq;
v->phaseStep = v->phaseStep =
((uint64_t)freq << 32) / SAMPLE_RATE; ((uint64_t)freq << 32) /
enc->sampleRate;
v->volume = 255; v->volume = 255;
} }
static int encoder_get_bit(Encoder *enc) { static int encoder_get_symbol(
if (enc->byteIndex >= enc->bufferSize) Encoder *enc)
return -1; {
unsigned symbol=0;
int bit = (enc->buffer[enc->byteIndex] >> enc->bitIndex) & 1;
enc->bitIndex++; for(unsigned i=0;
i<enc->bitsPerSymbol;
i++)
{
if (enc->bitIndex == 8) { if(enc->packetIndex >= enc->packetSize)
enc->bitIndex = 0; return -1;
enc->byteIndex++;
int bit =
(enc->packet[enc->packetIndex]
>>enc->bitIndex)&1;
symbol |= bit<<i;
enc->bitIndex++;
if(enc->bitIndex==8)
{
enc->bitIndex=0;
enc->packetIndex++;
}
} }
return bit;
return symbol;
} }
static void encoder_clock( static void encoder_clock(
@@ -94,109 +115,91 @@ static void encoder_clock(
clock->frequency = freq; clock->frequency = freq;
clock->phaseStep = clock->phaseStep =
((uint64_t)freq << 32) / ((uint64_t)freq << 32) /
SAMPLE_RATE; (enc->sampleRate ? enc->sampleRate : SAMPLE_RATE);
clock->volume = 255; clock->volume = 255;
// Debug: print clock toggle
//printf("ENCODER CLOCK: toggled to %u Hz (state=%d)\n", freq, enc->clockState);
} }
void encoder_next_symbol( void encoder_next_symbol(
Encoder *enc, Encoder *enc,
AudioData *audio) AudioData *audio)
{ {
encoder_clock(enc, audio);
encoder_clock(enc,audio);
switch(enc->state) switch(enc->state)
{ {
case TX_PREAMBLE: case TX_PREAMBLE:
{
unsigned symbol =
enc->preambleSymbols %
enc->toneCount;
/* transmit all-zero symbol */
for(unsigned i=0;i<enc->bitCount;i++) encoder_set_symbol(
encoder_set_bit(audio,enc,i,0); audio,
enc,
symbol);
enc->preambleSymbols++; enc->preambleSymbols++;
if(enc->preambleSymbols >= PREAMBLE_SYMBOLS)
if(enc->preambleSymbols>=PREAMBLE_SYMBOLS)
{ {
enc->syncIndex = 0; enc->packetIndex=0;
enc->bitIndex = 0; enc->bitIndex=0;
enc->state = TX_SYNC;
}
break; enc->state=TX_PACKET;
case TX_SYNC:
{
uint8_t byte =
syncBytes[enc->syncIndex];
for(unsigned i=0;i<enc->bitCount;i++)
{
encoder_set_bit(
audio,
enc,
i,
(byte >> i) & 1);
}
enc->syncIndex++;
if(enc->syncIndex ==
sizeof(syncBytes))
{
enc->state = TX_DATA;
enc->byteIndex = 0;
enc->bitIndex = 0;
} }
break; break;
} }
case TX_DATA:
case TX_PACKET:
{ {
int finished = 0; int symbol =
encoder_get_symbol(enc);
for(unsigned i=0;i<enc->bitCount;i++)
if(symbol<0)
{ {
int bit = enc->state=TX_END;
encoder_get_bit(enc); break;
if(bit < 0)
{
bit = 0;
finished++;
}
encoder_set_bit(
audio,
enc,
i,
bit);
} }
if(finished ==
enc->bitCount) encoder_set_symbol(
{ audio,
enc->state = TX_END; enc,
} symbol);
break; break;
} }
case TX_END: case TX_END:
stopEncoder(enc); stopEncoder(enc);
break; break;
default: default:
break; break;
} }
enc->samplesRemaining =
enc->samplesRemaining=
enc->symbolSamples; enc->symbolSamples;
} }
void startEncoder(Encoder *enc) void startEncoder(Encoder *enc)
{ {
encoder_build_packet(enc, enc->buffer, enc->bufferSize);
enc->state = TX_PREAMBLE; enc->state = TX_PREAMBLE;
enc->transmitting = true; enc->transmitting = true;
@@ -214,7 +217,7 @@ void stopEncoder(Encoder *enc)
enc->transmitting=false; enc->transmitting=false;
enc->state=TX_IDLE; 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; encodedAudioData.synthVoices[i].volume=0;
} }
@@ -234,15 +237,16 @@ void audio_callback(void *userdata, Uint8 *stream, int len) {
int samples = len / sizeof(float); int samples = len / sizeof(float);
for (int i = 0; i < samples; i++) { for (int i = 0; i < samples; i++) {
if (enc->samplesRemaining == 0) if (enc->samplesRemaining == 0) {
encoder_next_symbol(enc, audio); encoder_next_symbol(enc, audio);
}
enc->samplesRemaining--; enc->samplesRemaining--;
float mix = 0.0f; float mix = 0.0f;
int active = 0; int active = 0;
for (unsigned v = 0; v < enc->bitCount + 1; v++) { for(unsigned v=0; v<2; v++) {
SynthVoice *voice = &audio->synthVoices[v]; SynthVoice *voice = &audio->synthVoices[v];
if (voice->volume == 0) 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; float output = active ? mix / (active + 1) : 0.0f;
out[i] = output; 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)); memset(&encoder,0,sizeof(Encoder));
encoder.bufferSize = 0; encoder.bufferSize = 0;
encoder.bitCount = 8;
/*
8-FSK:
3 bits per symbol
8 frequencies
*/
encoder.bitsPerSymbol = 3;
encoder.toneCount = 8;
encoder.byteIndex = 0; encoder.byteIndex = 0;
encoder.startFreq = 1000;
encoder.endFreq = 5000;
encoder.dataSpacing = 250;
encoder.symbolSamples = SAMPLE_RATE / 50;
encoder.clock0 = goertzel_bin_freq( encoder.startFreq =
6000, DEFAULT_START_FREQ;
SAMPLE_RATE,
encoder.symbolSamples 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.clock1 =
goertzel_bin_freq(
DEFAULT_CLOCK1_FREQ,
SAMPLE_RATE,
encoder.symbolSamples);
encoder.clock1 = goertzel_bin_freq(
6300,
SAMPLE_RATE,
encoder.symbolSamples
);
encoder.state = TX_IDLE; encoder.state = TX_IDLE;
encoder.transmitting = false; encoder.transmitting = false;
} }
void initAudioEncoder() { void initAudioEncoder() {
@@ -307,16 +340,35 @@ void initAudioEncoder() {
spec.freq = SAMPLE_RATE; spec.freq = SAMPLE_RATE;
spec.format = AUDIO_F32SYS; spec.format = AUDIO_F32SYS;
spec.channels = 1; spec.channels = 1;
spec.samples = 4096; spec.samples = 960; // requested buffer
spec.callback = audio_callback; spec.callback = audio_callback;
encodedAudioData.encoder = &encoder; encodedAudioData.encoder = &encoder;
spec.userdata = &encodedAudioData; spec.userdata = &encodedAudioData;
dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0); SDL_AudioSpec realSpec = {0};
printf("Encoder device opened: dev=%u\n", dev); 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) { if (dev == 0) {
printf("Failed to open audio encoder: %s\n", SDL_GetError()); printf("Failed to open audio encoder: %s\n", SDL_GetError());
SDL_Quit(); 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); SDL_PauseAudioDevice(dev, 0);
} }
+11 -14
View File
@@ -6,8 +6,8 @@
#define AUDIOCODER_ENCODER_H #define AUDIOCODER_ENCODER_H
#define SAMPLE_RATE 48000 #define SAMPLE_RATE 48000
#define MAX_BITS 16 #define CLOCK_VOICE 0
#define CLOCK_VOICE MAX_BITS #define DATA_VOICE 1
#define MAX_PACKET 223 #define MAX_PACKET 223
#include <SDL_audio.h> #include <SDL_audio.h>
@@ -23,21 +23,23 @@ typedef struct SynthVoice {
typedef enum { typedef enum {
TX_IDLE, TX_IDLE,
TX_PREAMBLE, TX_PREAMBLE,
TX_SYNC, TX_PACKET,
TX_DATA,
TX_END TX_END
} TxState; } TxState;
typedef struct { typedef struct {
unsigned int bitCount; unsigned int bitsPerSymbol;
unsigned int toneCount;
unsigned int byteIndex; unsigned int byteIndex;
unsigned int bitIndex; unsigned int bitIndex;
uint8_t buffer[1024 * 1024 * 8]; uint8_t buffer[1024 * 1024 * 8];
unsigned int bufferSize; unsigned int bufferSize;
uint8_t packet[MAX_PACKET];
uint32_t packetSize;
uint32_t packetIndex;
uint16_t startFreq; uint16_t startFreq;
@@ -53,6 +55,7 @@ typedef struct {
uint32_t symbolSamples; uint32_t symbolSamples;
uint32_t samplesRemaining; uint32_t samplesRemaining;
uint32_t sampleRate; // actual device sample rate
TxState state; TxState state;
@@ -66,7 +69,7 @@ typedef struct {
typedef struct AudioData { typedef struct AudioData {
SynthVoice synthVoices[MAX_BITS + 1]; SynthVoice synthVoices[2];
Encoder *encoder; Encoder *encoder;
} AudioData; } AudioData;
@@ -74,18 +77,12 @@ extern Encoder encoder;
extern AudioData encodedAudioData; extern AudioData encodedAudioData;
extern SDL_AudioDeviceID dev; 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 startEncoder(Encoder *enc);
void stopEncoder(Encoder *enc); void stopEncoder(Encoder *enc);
void initAudioEncoder(); void initAudioEncoder();
extern const uint8_t syncBytes[4]; extern const uint8_t syncSymbols[8];
#endif //AUDIOCODER_ENCODER_H #endif //AUDIOCODER_ENCODER_H
+8 -47
View File
@@ -6,70 +6,31 @@
#include <math.h> #include <math.h>
void goertzel_init( void goertzel_init(Goertzel *g, float freq, float sampleRate, int samples)
Goertzel *g,
float freq,
float sampleRate,
int samples)
{ {
float omega = 2.0f * M_PI * freq / sampleRate;
float omega = g->coeff = 2.0f*cosf(omega);
2.0f * M_PI * freq / sampleRate;
g->coeff =
2.0f*cosf(omega);
g->s1=0; g->s1=0;
g->s2=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->s2=g->s1;
g->s1=s; 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->s1=0;
g->s2=0; g->s2=0;
return power; return power;
} }
int goertzel_bin_freq( int goertzel_bin_freq(int freq, int sampleRate, int samples)
int freq,
int sampleRate,
int samples)
{ {
int bin = (freq * samples + sampleRate / 2) / sampleRate; int bin = (freq * samples + sampleRate / 2) / sampleRate;
return bin * sampleRate / samples; return bin * sampleRate / samples;
} }
+31 -22
View File
@@ -1,14 +1,11 @@
#include <math.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "font.h" #include "font.h"
#include "cpustatusui.h"
#include "decoder.h" #include "decoder.h"
#include "goertzel.h" #include "goertzel.h"
#include "texteditor.h" #include "texteditor.h"
#include "hexdump.h"
//The window we'll be rendering to //The window we'll be rendering to
SDL_Window *window = NULL; SDL_Window *window = NULL;
@@ -38,8 +35,7 @@ static const char *tx_state_name(TxState state) {
switch (state) { switch (state) {
case TX_IDLE: return "IDLE"; case TX_IDLE: return "IDLE";
case TX_PREAMBLE: return "PREAMBLE"; case TX_PREAMBLE: return "PREAMBLE";
case TX_SYNC: return "SYNC"; case TX_PACKET: return "PACKET";
case TX_DATA: return "DATA";
case TX_END: return "END"; case TX_END: return "END";
} }
return "?"; return "?";
@@ -48,9 +44,9 @@ static const char *tx_state_name(TxState state) {
static const char *rx_state_name(RxState state) { static const char *rx_state_name(RxState state) {
switch (state) { switch (state) {
case RX_SEARCH: return "SEARCH"; case RX_SEARCH: return "SEARCH";
case RX_ALIGN: return "ALIGN";
case RX_SYNC: return "SYNC"; case RX_SYNC: return "SYNC";
case RX_LOCKED: return "LOCKED"; case RX_MAGIC: return "MAGIC";
case RX_DATA: return "DATA";
} }
return "?"; return "?";
} }
@@ -65,8 +61,8 @@ static const char *setting_name(int setting) {
case 5: return "CLOCK1"; case 5: return "CLOCK1";
case 6: return "OUT DEVICE"; case 6: return "OUT DEVICE";
case 7: return "IN DEVICE"; case 7: return "IN DEVICE";
default: return "UNKNOWN";
} }
return "?";
} }
static void apply_modem_settings(void) { static void apply_modem_settings(void) {
@@ -79,12 +75,12 @@ static void apply_modem_settings(void) {
encoder.clock0, encoder.clock0,
SAMPLE_RATE, SAMPLE_RATE,
encoder.symbolSamples); encoder.symbolSamples);
encoder.clock1 = goertzel_bin_freq( encoder.clock1 = goertzel_bin_freq(
encoder.clock1, encoder.clock1,
SAMPLE_RATE, SAMPLE_RATE,
encoder.symbolSamples); encoder.symbolSamples);
decoder.bitCount = encoder.bitCount;
decoder.startFreq = encoder.startFreq; decoder.startFreq = encoder.startFreq;
decoder.endFreq = encoder.endFreq; decoder.endFreq = encoder.endFreq;
decoder.dataSpacing = encoder.dataSpacing; decoder.dataSpacing = encoder.dataSpacing;
@@ -135,6 +131,7 @@ static void adjust_modem_setting(int delta) {
static void render_modem_text(SDL_Renderer *renderer) { static void render_modem_text(SDL_Renderer *renderer) {
char line[256]; char line[256];
// TX info on the left
snprintf( snprintf(
line, line,
sizeof(line), sizeof(line),
@@ -147,6 +144,7 @@ static void render_modem_text(SDL_Renderer *renderer) {
encoder.syncIndex); encoder.syncIndex);
renderText(renderer, smallerFont, line, 10, 8); renderText(renderer, smallerFont, line, 10, 8);
// RX info on the right
snprintf( snprintf(
line, line,
sizeof(line), sizeof(line),
@@ -156,18 +154,19 @@ static void render_modem_text(SDL_Renderer *renderer) {
decoder.lastDecodedByte, decoder.lastDecodedByte,
decoder.bestSyncScore, decoder.bestSyncScore,
decoder.bestSyncPhase); decoder.bestSyncPhase);
renderText(renderer, smallerFont, line, 10, 22); renderText(renderer, smallerFont, line, 640, 8);
// Clock info below RX
snprintf( snprintf(
line, line,
sizeof(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.clockEnergy0,
decoder.clockEnergy1, decoder.clockEnergy1,
decoder.clockRatio, decoder.clockRatio,
decoder.clockHistory, decoder.clockHistory,
decoder.clockCount); decoder.clockCount);
renderText(renderer, smallerFont, line, 10, 36); renderText(renderer, smallerFont, line, 640, 22);
// Settings display with highlighting // Settings display with highlighting
const char *settingStr = setting_name(selectedModemSetting); 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) { 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 x = 500;
const int y = 560; const int y = 650;
const int maxHeight = 72; const int maxHeight = 192;
const int barWidth = 8; const int barWidth = 8;
const int gap = 4; 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_Rect frame = {x - 4, y - maxHeight - 4, tones * (barWidth + gap) + 4, maxHeight + 24};
SDL_RenderDrawRect(renderer, &frame); SDL_RenderDrawRect(renderer, &frame);
// Draw clocks first (indices 0 and 1)
for (int i = 0; i < tones; i++) { 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; float ratio = 0.0f;
if (decoder.maxEnergy > 0.0f) if (decoder.maxEnergy > 0.0f)
ratio = decoder.energies[i] / decoder.maxEnergy; ratio = decoder.energies[idx] / decoder.maxEnergy;
if (ratio > 1.0f) if (ratio > 1.0f)
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.x = x + i * (barWidth + gap);
barRect.y = y - barRect.h; barRect.y = y - barRect.h;
if (i >= decoder.bitCount * 2) if (idx < 2)
SDL_SetRenderDrawColor(renderer, 255, 192, 64, 255); SDL_SetRenderDrawColor(renderer, 255, 192, 64, 255); // clock
else if (i & 1) else if ((idx - 2) & 1)
SDL_SetRenderDrawColor(renderer, 80, 180, 255, 255); SDL_SetRenderDrawColor(renderer, 80, 180, 255, 255); // 1-tone
else else
SDL_SetRenderDrawColor(renderer, 80, 255, 140, 255); SDL_SetRenderDrawColor(renderer, 80, 255, 140, 255); // 0-tone
SDL_RenderFillRect(renderer, &barRect); SDL_RenderFillRect(renderer, &barRect);
} }
char label[128]; 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); renderText(renderer, smallerFont, label, x, y + 6);
} }
@@ -320,6 +325,9 @@ int init() {
updateDump(&transmitEditor, renderer); updateDump(&transmitEditor, renderer);
// Auto-start encoder for testing
//startEncoder(&encoder);
SDL_Rect viewport = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; SDL_Rect viewport = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
SDL_RenderSetViewport(renderer, &viewport); SDL_RenderSetViewport(renderer, &viewport);
@@ -477,6 +485,7 @@ int processEvent(SDL_Event e) {
break; break;
case SDLK_F5: case SDLK_F5:
updateDump(&transmitEditor, renderer); updateDump(&transmitEditor, renderer);
updateDump(&receivedEditor, renderer);
break; break;
case SDLK_F7: case SDLK_F7:
startEncoder(&encoder); 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
+100 -110
View File
@@ -1,136 +1,126 @@
//
// Created by bruno on 13. 7. 2026.
//
#include "packet.h" #include "packet.h"
#include "crc32.h"
#include <stdlib.h> #include <stdint.h>
#include <string.h>
#include "cpustatusui.h"
#include "decoder.h"
#include "protocol.h"
#define MAGIC 0xCA55 void encoder_build_packet(
Encoder *enc,
uint8_t *data,
#define PACKET_SIZE 223 uint8_t length)
{
uint32_t p=0;
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
};
memcpy( memcpy(
out + pos, &enc->packet[p],
sync, PACKET_MAGIC,
sizeof(sync)); MAGIC_SIZE);
pos += sizeof(sync); p+=MAGIC_SIZE;
uint32_t offset = 0; enc->packet[p++]=length;
for (uint32_t p = 0; p < packets; p++) { memcpy(
uint16_t magic = MAGIC; &enc->packet[p],
data,
length);
uint16_t packet = p; p+=length;
uint16_t len = uint16_t crc =
size - offset > PACKET_SIZE ? PACKET_SIZE : size - offset; crc16(
&enc->packet[0],
p);
memcpy(out + pos, &magic, 2); enc->packet[p++]=crc&0xff;
pos += 2; enc->packet[p++]=crc>>8;
memcpy(out + pos, &packet, 2); enc->packetSize=p;
pos += 2; }
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); for(uint32_t i=0;i<len;i++)
pos += 2; {
crc ^= data[i];
memcpy(out + pos, for(int b=0;b<8;b++)
file + offset, {
len); if(crc & 1)
crc =
pos += len; (crc >> 1) ^ 0xA001;
else
crc >>= 1;
uint32_t crc = }
calcCRC32(
file + offset,
len);
memcpy(out + pos, &crc, 4);
pos += 4;
offset += len;
} }
*outSize = pos; return crc;
}
return out;
}
+20 -12
View File
@@ -1,17 +1,25 @@
// #ifndef PACKET_H
// Created by bruno on 13. 7. 2026. #define PACKET_H
//
#ifndef AUDIOCODER_PACKET_H
#define AUDIOCODER_PACKET_H
#include <stdint.h> #include <stdint.h>
#include "decoder.h"
#include "encoder.h"
uint8_t *build_stream( #define PREAMBLE_SYMBOLS 128
uint8_t *file, #define PREAMBLE_CHECK_SYMBOLS 16
uint32_t size,
uint32_t *outSize
);
#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