thing
This commit is contained in:
Generated
+1
-1
@@ -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>
|
||||||
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/=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
@@ -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)
|
||||||
@@ -12,24 +12,18 @@
|
|||||||
#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) {
|
||||||
@@ -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 rel = (uint32_t) (sampleIndex - oldest);
|
||||||
(uint32_t)(sampleIndex - oldest);
|
*sample = dec->sampleHistory[(start + rel) % DECODER_SAMPLE_HISTORY];
|
||||||
|
|
||||||
*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);
|
||||||
|
|
||||||
|
|
||||||
|
// clock tones
|
||||||
goertzel_init(
|
goertzel_init(
|
||||||
&dec->tones[index++],
|
&dec->tones[0],
|
||||||
dec->startFreq +
|
dec->clock0,
|
||||||
dec->dataSpacing * (i * 2),
|
dec->sampleRate,
|
||||||
SAMPLE_RATE,
|
|
||||||
dec->symbolSamples
|
dec->symbolSamples
|
||||||
);
|
);
|
||||||
|
|
||||||
goertzel_init(
|
goertzel_init(
|
||||||
&dec->tones[index++],
|
&dec->tones[1],
|
||||||
dec->startFreq +
|
dec->clock1,
|
||||||
dec->dataSpacing * (i * 2 + 1),
|
dec->sampleRate,
|
||||||
SAMPLE_RATE,
|
|
||||||
dec->symbolSamples
|
dec->symbolSamples
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// data tones
|
||||||
|
for (int i = 0; i < dec->toneCount; i++) {
|
||||||
|
uint16_t freq =
|
||||||
|
dec->startFreq +
|
||||||
|
i * dec->dataSpacing;
|
||||||
|
|
||||||
|
|
||||||
|
// MUST match encoder
|
||||||
|
freq = goertzel_bin_freq(
|
||||||
|
freq,
|
||||||
|
dec->sampleRate,
|
||||||
|
dec->symbolSamples
|
||||||
|
);
|
||||||
|
|
||||||
|
goertzel_init(
|
||||||
|
&dec->tones[i + 2],
|
||||||
|
freq,
|
||||||
|
dec->sampleRate,
|
||||||
|
dec->symbolSamples
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
printf(
|
||||||
|
"Tone %d = %dHz\n",
|
||||||
|
i,
|
||||||
|
freq
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
goertzel_init(
|
|
||||||
&dec->tones[index++],
|
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;
|
||||||
@@ -356,17 +189,15 @@ static void clock_search(
|
|||||||
(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->clockHistory = 0;
|
||||||
dec->clockCount = 0;
|
dec->clockCount = 0;
|
||||||
|
|
||||||
dec->goodClocks = 0;
|
dec->goodClocks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DON'T reset filters - keep energy visible
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RX_SYNC: {
|
||||||
|
update_all_energies(dec);
|
||||||
|
|
||||||
|
|
||||||
|
int symbol = decode_symbol(dec);
|
||||||
|
|
||||||
|
if (clock_valid(dec)) {
|
||||||
|
dec->lostClockCount = 0;
|
||||||
|
printf("Symbol %d energy=%f\n", symbol, dec->energies[symbol + 2]);
|
||||||
} else {
|
} else {
|
||||||
// Phase search failed, reset and try again
|
if (++dec->lostClockCount > 8) {
|
||||||
dec->goodClocks = 0;
|
printf("CLOCK LOST\n");
|
||||||
dec->clockHistory = 0;
|
|
||||||
dec->clockCount = 0;
|
decoder_reset_rx(dec);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case RX_LOCKED: {
|
|
||||||
int clock = detect_clock(dec);
|
|
||||||
if (clock < 0) {
|
|
||||||
printf("clock lost\n");
|
|
||||||
dec->rxState = RX_SEARCH;
|
|
||||||
dec->locked = false;
|
|
||||||
dec->sampleCount = 0;
|
|
||||||
dec->clockHistory = 0;
|
|
||||||
dec->clockCount = 0;
|
|
||||||
dec->goodClocks = 0;
|
|
||||||
dec->searchOffset = 0;
|
|
||||||
decoder_reset_symbol_filters(dec);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t b = decode_symbol(dec);
|
|
||||||
|
|
||||||
if (dec->bufferSize < sizeof(dec->buffer))
|
if (symbol == (dec->preambleCount & 7)) {
|
||||||
dec->buffer[dec->bufferSize++] = b;
|
if (dec->preambleCount < 255)
|
||||||
|
dec->preambleCount++;
|
||||||
|
|
||||||
|
if (dec->preambleCount >= 16) {
|
||||||
|
printf("PREAMBLE OK\n");
|
||||||
|
|
||||||
|
dec->rxState = RX_MAGIC;
|
||||||
|
|
||||||
|
dec->magicIndex = 0;
|
||||||
|
dec->currentByte = 0;
|
||||||
|
dec->bitIndex = 0;
|
||||||
|
|
||||||
printf("DATA %02X\n", b);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (dec->preambleCount > 4)
|
||||||
|
dec->preambleCount -= 4;
|
||||||
|
else
|
||||||
|
dec->preambleCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RX_MAGIC:
|
||||||
|
case RX_DATA: {
|
||||||
|
update_all_energies(dec);
|
||||||
|
|
||||||
|
if (!clock_valid(dec)) {
|
||||||
|
if (++dec->lostClockCount > 8) {
|
||||||
|
printf("CLOCK LOST\n");
|
||||||
|
decoder_reset_rx(dec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dec->lostClockCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int symbol = decode_symbol(dec);
|
||||||
|
|
||||||
|
decoder_push_symbol(dec, symbol);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,19 +506,10 @@ void input_callback(
|
|||||||
int len) {
|
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,
|
||||||
samples,
|
samples,
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
{
|
||||||
|
unsigned symbol=0;
|
||||||
|
|
||||||
|
|
||||||
|
for(unsigned i=0;
|
||||||
|
i<enc->bitsPerSymbol;
|
||||||
|
i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(enc->packetIndex >= enc->packetSize)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
int bit = (enc->buffer[enc->byteIndex] >> enc->bitIndex) & 1;
|
|
||||||
|
int bit =
|
||||||
|
(enc->packet[enc->packetIndex]
|
||||||
|
>>enc->bitIndex)&1;
|
||||||
|
|
||||||
|
|
||||||
|
symbol |= bit<<i;
|
||||||
|
|
||||||
|
|
||||||
enc->bitIndex++;
|
enc->bitIndex++;
|
||||||
|
|
||||||
if (enc->bitIndex == 8) {
|
|
||||||
enc->bitIndex = 0;
|
if(enc->bitIndex==8)
|
||||||
enc->byteIndex++;
|
{
|
||||||
|
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;
|
|
||||||
|
enc->state=TX_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case TX_SYNC:
|
|
||||||
{
|
|
||||||
uint8_t byte =
|
|
||||||
syncBytes[enc->syncIndex];
|
|
||||||
|
|
||||||
for(unsigned i=0;i<enc->bitCount;i++)
|
case TX_PACKET:
|
||||||
{
|
{
|
||||||
encoder_set_bit(
|
int symbol =
|
||||||
|
encoder_get_symbol(enc);
|
||||||
|
|
||||||
|
|
||||||
|
if(symbol<0)
|
||||||
|
{
|
||||||
|
enc->state=TX_END;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
encoder_set_symbol(
|
||||||
audio,
|
audio,
|
||||||
enc,
|
enc,
|
||||||
i,
|
symbol);
|
||||||
(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:
|
|
||||||
{
|
|
||||||
int finished = 0;
|
|
||||||
|
|
||||||
for(unsigned i=0;i<enc->bitCount;i++)
|
|
||||||
{
|
|
||||||
int bit =
|
|
||||||
encoder_get_bit(enc);
|
|
||||||
|
|
||||||
if(bit < 0)
|
|
||||||
{
|
|
||||||
bit = 0;
|
|
||||||
finished++;
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder_set_bit(
|
|
||||||
audio,
|
|
||||||
enc,
|
|
||||||
i,
|
|
||||||
bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(finished ==
|
|
||||||
enc->bitCount)
|
|
||||||
{
|
|
||||||
enc->state = TX_END;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case TX_END:
|
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.clock1 = goertzel_bin_freq(
|
encoder.endFreq =
|
||||||
6300,
|
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,
|
SAMPLE_RATE,
|
||||||
encoder.symbolSamples
|
encoder.symbolSamples);
|
||||||
);
|
|
||||||
|
|
||||||
|
encoder.clock1 =
|
||||||
|
goertzel_bin_freq(
|
||||||
|
DEFAULT_CLOCK1_FREQ,
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 "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;
|
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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