thing
This commit is contained in:
Generated
+1
-1
@@ -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>
|
||||
Generated
+19
-19
@@ -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
@@ -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)
|
||||
@@ -12,30 +12,24 @@
|
||||
#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) {
|
||||
dec->sampleHistory[dec->sampleHistoryWrite] = sample;
|
||||
dec->sampleHistoryWrite =
|
||||
(dec->sampleHistoryWrite + 1) % DECODER_SAMPLE_HISTORY;
|
||||
(dec->sampleHistoryWrite + 1) % DECODER_SAMPLE_HISTORY;
|
||||
|
||||
if (dec->sampleHistoryCount < DECODER_SAMPLE_HISTORY)
|
||||
dec->sampleHistoryCount++;
|
||||
@@ -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];
|
||||
(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];
|
||||
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++) {
|
||||
goertzel_init(
|
||||
&dec->tones[index++],
|
||||
dec->startFreq +
|
||||
dec->dataSpacing * (i * 2),
|
||||
SAMPLE_RATE,
|
||||
printf("Decoder tones: %d\n", dec->toneCount);
|
||||
|
||||
|
||||
// clock tones
|
||||
goertzel_init(
|
||||
&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
|
||||
);
|
||||
|
||||
goertzel_init(
|
||||
&dec->tones[index++],
|
||||
dec->startFreq +
|
||||
dec->dataSpacing * (i * 2 + 1),
|
||||
SAMPLE_RATE,
|
||||
&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)
|
||||
{
|
||||
Decoder *dec,
|
||||
int clock) {
|
||||
if (clock < 0) {
|
||||
dec->clockHistory = 0;
|
||||
dec->clockCount = 0;
|
||||
return;
|
||||
@@ -352,21 +185,19 @@ static void clock_search(
|
||||
|
||||
|
||||
dec->clockHistory =
|
||||
(dec->clockHistory << 1) |
|
||||
(clock & 1);
|
||||
(dec->clockHistory << 1) |
|
||||
(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) {
|
||||
for (int t = 0; t < tones; t++)
|
||||
goertzel_add(&dec->tones[t], samples[i]);
|
||||
}
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
printf("CLOCK LOCKED\n");
|
||||
|
||||
dec->rxState = RX_SYNC;
|
||||
dec->locked = true;
|
||||
|
||||
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;
|
||||
|
||||
printf("DATA %02X\n", b);
|
||||
// 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 {
|
||||
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) {
|
||||
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,
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
AudioData *audio,
|
||||
Encoder *enc,
|
||||
unsigned bitIndex,
|
||||
int bit)
|
||||
static void encoder_set_symbol(
|
||||
AudioData *audio,
|
||||
Encoder *enc,
|
||||
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->symbolSamples
|
||||
);
|
||||
freq,
|
||||
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)
|
||||
return -1;
|
||||
static int encoder_get_symbol(
|
||||
Encoder *enc)
|
||||
{
|
||||
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) {
|
||||
enc->bitIndex = 0;
|
||||
enc->byteIndex++;
|
||||
if(enc->packetIndex >= enc->packetSize)
|
||||
return -1;
|
||||
|
||||
|
||||
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(
|
||||
@@ -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 *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;
|
||||
|
||||
break;
|
||||
|
||||
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;
|
||||
enc->state=TX_PACKET;
|
||||
}
|
||||
|
||||
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 =
|
||||
encoder_get_bit(enc);
|
||||
|
||||
if(bit < 0)
|
||||
{
|
||||
bit = 0;
|
||||
finished++;
|
||||
}
|
||||
|
||||
encoder_set_bit(
|
||||
audio,
|
||||
enc,
|
||||
i,
|
||||
bit);
|
||||
enc->state=TX_END;
|
||||
break;
|
||||
}
|
||||
|
||||
if(finished ==
|
||||
enc->bitCount)
|
||||
{
|
||||
enc->state = TX_END;
|
||||
}
|
||||
|
||||
encoder_set_symbol(
|
||||
audio,
|
||||
enc,
|
||||
symbol);
|
||||
|
||||
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.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.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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user