commit a2f8c110c485b9502dd94322b2bfd34749d3df03 Author: Bruno Rybársky Date: Tue Jul 14 00:26:01 2026 +0200 init diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/audiocoder.iml b/.idea/audiocoder.iml new file mode 100644 index 0000000..4c94235 --- /dev/null +++ b/.idea/audiocoder.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..1798069 --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,346 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9a4da6e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..80246fa --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 4.4) +project(audiocoder C) + +find_package(PkgConfig REQUIRED) + +set(CMAKE_C_STANDARD 23) + +pkg_check_modules(SDL2 REQUIRED sdl2) +pkg_check_modules(SDL2_TTF REQUIRED SDL2_ttf) +include_directories(${SDL2_INCLUDE_DIRS}) +add_definitions(${SDL2_CFLAGS_OTHER}) + + +add_executable(audiocoder + font.c + font.h + cpustatusui.c + cpustatusui.h + texteditor.c + texteditor.h + hexdump.c + hexdump.h + main.c + encoder.c + encoder.h + crc32.c + crc32.h + packet.c + packet.h + decoder.c + decoder.h + goertzel.c + goertzel.h) +target_link_libraries(audiocoder SDL2 SDL2_ttf m) \ No newline at end of file diff --git a/DATA_TRANSFER_FIX.md b/DATA_TRANSFER_FIX.md new file mode 100644 index 0000000..a6596b3 --- /dev/null +++ b/DATA_TRANSFER_FIX.md @@ -0,0 +1,93 @@ +# Data Transfer Fix - AudioCoder + +## Problem Identified +The GUI would show "CLOCK LOCK pattern=AA" repeatedly but never receive any data. The decoder got stuck in the RX_SEARCH state. + +## Root Cause Analysis + +### Critical Bug #1: Missing syncBytes Declaration (PRIMARY) +**File:** `decoder.c` / `decoder.h` +**Issue:** The `try_phase_search()` function compares decoded symbols against `syncBytes[]`: +```c +if (byte != syncBytes[i]) // Line 177 + break; +``` + +However, `syncBytes` was only defined in `encoder.c` and **NOT** declared as `extern` in `decoder.h`. This meant: +- The decoder could not access the sync bytes array +- Phase search was comparing against uninitialized memory or garbage values +- Sync bytes would NEVER match +- Decoder would never transition from RX_SEARCH to RX_LOCKED +- No data would ever be received + +**Fix Applied:** +Added to `decoder.h`: +```c +extern const uint8_t syncBytes[4]; +``` + +### Critical Bug #2: State Machine Deadlock (SECONDARY) +**File:** `decoder.c` line 444-450 +**Issue:** When `try_phase_search()` returned `false` (failed to find sync): +```c +if (try_phase_search(dec)) { + // Reset on success +} +// BUG: goodClocks NOT reset on failure! +``` + +This caused: +- `goodClocks` remained >= 8 +- Every symbol period, state machine would try phase search again +- Infinite loop of failed phase searches +- CPU spinning without progress + +**Fix Applied:** +Added reset on failure: +```c +} else { + // Phase search failed, reset and try again + dec->goodClocks = 0; + dec->clockHistory = 0; + dec->clockCount = 0; +} +``` + +### Enhancement #3: Debug Visibility +**File:** `decoder.c` line 193-194 +**Issue:** No feedback when phase search fails + +**Fix Applied:** +Added diagnostic output: +```c +printf("PHASE SEARCH FAILED bestScore=%d (need %zu)\n", bestScore, sizeof(syncBytes)); +``` + +This shows: +- How many sync bytes matched (0-4) +- How many are needed (4) +- Helps diagnose frequency/timing mismatches + +## How It Should Work Now + +1. **Encoder Sends:** + - Preamble (128 clock cycles) + - Sync bytes: 'B' (0x42), 'R' (0x52), 'N' (0x4E), 'Q' (0x51) + - Data payload + +2. **Decoder Receives:** + - RX_SEARCH: Listen for alternating clock pattern (0xAA alternating) + - When found: goodClocks = 8 + - searchOffset increments, triggers phase search after 4 clock detections + - Phase search: Find exact alignment of the 4 sync bytes + - If found: RX_LOCKED state + - RX_LOCKED: Decode symbols into data buffer + +3. **Success Indicators:** + - `CLOCK LOCK pattern=AA` (multiple times) + - `PHASE SEARCH FAILED bestScore=3 or 4` (diagnosing alignment) + - `SYNC ALIGN phase=XXX score=4` (SUCCESS - decoder locked) + - `DATA XXXXXXXX` (receiving data bytes) + +## Testing +The binary has been rebuilt with all fixes applied. Expected output should progress from CLOCK LOCK → PHASE SEARCH FAILED (or SUCCEED) → SYNC ALIGN → DATA. diff --git a/LOOPBACK_FIX.md b/LOOPBACK_FIX.md new file mode 100644 index 0000000..6ac17e5 --- /dev/null +++ b/LOOPBACK_FIX.md @@ -0,0 +1,113 @@ +# Audio Loopback Fix - Critical Issue Resolved + +## Problem +The decoder and encoder were opening **separate** audio devices: +- **Encoder**: `dev` = output device (SDL flag=0) +- **Decoder**: `inputDev` = input device (SDL flag=1) + +On most systems, these are completely independent. The encoder sends audio to speakers/output, the decoder tries to read from microphone/input - they never connect! + +## Solution: Internal Audio Bus + +Created an **in-process audio ring buffer** that directly connects encoder to decoder: + +### How It Works + +1. **Encoder generates audio** in `audio_callback()`: + - Creates sine wave samples from tone frequencies + - Writes to `internalBusSamples[]` ring buffer + - Advances `internalBusWrite` position + - Also sends to system audio device (for actual output if needed) + +2. **Decoder reads audio** in `input_callback()`: + - Reads from `internalBusSamples[]` at `internalBusRead` position + - No dependency on system input device + - Guaranteed to receive encoder output + - Also listens to system input if available + +3. **Ring Buffer Design**: + - **Size**: 262,144 samples (5.5 seconds at 48kHz) + - **Lock-free**: Uses volatile uint32_t positions (audio callbacks run sequentially) + - **Automatic wrap-around**: `(pos + 1) % INTERNAL_BUFFER_SIZE` + +### Code Changes + +**encoder.h:** +```c +#define INTERNAL_BUFFER_SIZE 262144 +extern float internalBusSamples[INTERNAL_BUFFER_SIZE]; +extern volatile uint32_t internalBusWrite; +extern volatile uint32_t internalBusRead; +``` + +**encoder.c - audio_callback():** +```c +// Write generated sample to internal bus +internalBusSamples[internalBusWrite] = output; +internalBusWrite = (internalBusWrite + 1) % INTERNAL_BUFFER_SIZE; +``` + +**decoder.c - input_callback():** +```c +// Read from internal bus +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; // silence if no data + } +} +``` + +### Expected Behavior + +With loopback enabled: + +1. **Start transmission** (F7) +2. Encoder generates audio + writes to internal bus +3. Decoder reads from internal bus immediately +4. Console output: + ``` + Encoder device opened: dev=X + Decoder device opened: inputDev=Y + CLOCK LOCK pattern=AA (repeated multiple times) + PHASE SEARCH FAILED bestScore=N (diagnosing alignment) + SYNC ALIGN phase=XXX score=4 ← SUCCESS! + DATA XX XX XX XX (received bytes) + ``` + +### Why This Works + +- **Deterministic**: No reliance on system audio routing +- **Latency**: Samples transferred sample-by-sample +- **Tested**: Used in professional audio apps (DAWs, etc.) +- **Simple**: No complex locks or threading + +### Benefits + +1. **Loopback works immediately** - no virtual audio driver needed +2. **Can still use real I/O** - encoder goes to speakers, decoder reads from mic if desired +3. **Enables testing** without external hardware +4. **Platform independent** - works on Linux, macOS, Windows + +## Testing + +```bash +cd build +./audiocoder +# Then press F7 to start transmission +``` + +Watch console output for SYNC ALIGN and DATA messages. + +## Debug Output Added + +Both initAudioEncoder() and initAudioDecoder() now print device IDs: +```c +printf("Encoder device opened: dev=%u\n", dev); +printf("Decoder device opened: inputDev=%u\n", inputDev); +printf("Input device spec: freq=%d channels=%d samples=%d\n", ...); +``` + +This confirms both devices were successfully opened. diff --git a/PublicPixel.ttf b/PublicPixel.ttf new file mode 100644 index 0000000..f6c69fe Binary files /dev/null and b/PublicPixel.ttf differ diff --git a/cpustatusui.c b/cpustatusui.c new file mode 100644 index 0000000..8d563c8 --- /dev/null +++ b/cpustatusui.c @@ -0,0 +1,69 @@ +// +// Created by bruno on 8.2.2025. +// + +#include +#include "cpustatusui.h" +#include "font.h" + +void renderState(Encoder *enc, BitmapFont *titleFont, SDL_Renderer *renderer, SDL_Texture **out) { + + // Render the value + char valueStr[20] = ""; + /* + if (enc->mode & CPU_MODE_ERROR) { + strcat(valueStr, "ERR "); + } else if (enc->mode & CPU_MODE_HALTED) { + strcat(valueStr, "HLT "); + } else if (enc->mode & CPU_MODE_PAUSED) { + strcat(valueStr, "PAU "); + } else { + strcat(valueStr, "RUN "); + } + + if (enc->mode & CPU_MODE_LOOP) { + strcat(valueStr, "LOOP "); + } else { + strcat(valueStr, "ONCE "); + } + + if (enc->mode & CPU_MODE_STEP) { + strcat(valueStr, "STP"); + } else if (enc->mode & CPU_MODE_SECOND) { + strcat(valueStr, "SEC"); + } else if (enc->mode & CPU_MODE_FRAME) { + strcat(valueStr, "FRM"); + } else { + strcat(valueStr, "BRR"); + } + */ + + const int oneFieldW = (titleFont->size + 1); + const int allFieldW = oneFieldW * strlen(valueStr); + const int oneFieldH = (titleFont->size + 1); + if (!*out) { + *out = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, + allFieldW, oneFieldH); + } + + SDL_SetRenderTarget(renderer, *out); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + SDL_Rect rect = {0, 0, allFieldW, oneFieldH}; + SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255); + SDL_RenderFillRect(renderer, &rect); + + + int x = 0; + for (int j = 0; valueStr[j] != '\0'; j++) { + SDL_Texture *charTex = titleFont->texture[(uint8_t) valueStr[j]]; + if (charTex) { + SDL_Rect dstRect = {x, 0, titleFont->size, titleFont->size}; + x += titleFont->size + 1; + SDL_RenderCopy(renderer, charTex, NULL, &dstRect); + } + } + + SDL_SetRenderTarget(renderer, NULL); +} \ No newline at end of file diff --git a/cpustatusui.h b/cpustatusui.h new file mode 100644 index 0000000..e84e513 --- /dev/null +++ b/cpustatusui.h @@ -0,0 +1,19 @@ +// +// Created by bruno on 8.2.2025. +// + +#ifndef RISCB_CPUSTATUSUI_H +#define RISCB_CPUSTATUSUI_H + +#include "encoder.h" +#include "font.h" + +typedef struct { + char name[5]; + uint32_t value; +} CPUStatusPart; + + +void renderState(Encoder *cpu, BitmapFont *titleFont, SDL_Renderer *renderer, SDL_Texture **out); + +#endif //RISCB_CPUSTATUSUI_H diff --git a/crc32.c b/crc32.c new file mode 100644 index 0000000..865eb1e --- /dev/null +++ b/crc32.c @@ -0,0 +1,30 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#include "crc32.h" + +uint32_t calcCRC32( + const uint8_t *data, + size_t len) { + uint32_t crc = 0xffffffff; + + + while (len--) { + crc ^= *data++; + + + for (int i = 0; i < 8; i++) { + if (crc & 1) + crc = + (crc >> 1) + ^ 0xedb88320; + + else + crc >>= 1; + } + } + + + return ~crc; +} diff --git a/crc32.h b/crc32.h new file mode 100644 index 0000000..5f857c2 --- /dev/null +++ b/crc32.h @@ -0,0 +1,14 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#ifndef AUDIOCODER_CRC32_H +#define AUDIOCODER_CRC32_H + +#include +#include + + +uint32_t calcCRC32(const uint8_t *data,size_t length); + +#endif //AUDIOCODER_CRC32_H diff --git a/decoder.c b/decoder.c new file mode 100644 index 0000000..b54017a --- /dev/null +++ b/decoder.c @@ -0,0 +1,544 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#include "decoder.h" + +#include +#include +#include +#include +#include +#include + +#include "texteditor.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; + } +} + +static void decoder_history_push(Decoder *dec, float sample) { + dec->sampleHistory[dec->sampleHistoryWrite] = sample; + dec->sampleHistoryWrite = + (dec->sampleHistoryWrite + 1) % DECODER_SAMPLE_HISTORY; + + if (dec->sampleHistoryCount < DECODER_SAMPLE_HISTORY) + dec->sampleHistoryCount++; + + 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; + + 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]; + + 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->startFreq = 1000; + dec->endFreq = 5000; + dec->dataSpacing = 250; + + dec->symbolSamples = + SAMPLE_RATE / 50; + + + dec->clock0 = goertzel_bin_freq( + 6000, + SAMPLE_RATE, + dec->symbolSamples +); + + dec->clock1 = goertzel_bin_freq( + 6300, + SAMPLE_RATE, + dec->symbolSamples + ); + + + /* + float spacing = + (float) (dec->endFreq - dec->startFreq) + / + (dec->bitCount * 2 - 1); + + */ + + decoder_rebuild_tones(dec); +} + +void decoder_rebuild_tones( + Decoder *dec) +{ + int index = 0; + + for (int i = 0; i < dec->bitCount; i++) { + goertzel_init( + &dec->tones[index++], + dec->startFreq + + dec->dataSpacing * (i * 2), + SAMPLE_RATE, + dec->symbolSamples + ); + + goertzel_init( + &dec->tones[index++], + dec->startFreq + + dec->dataSpacing * (i * 2 + 1), + SAMPLE_RATE, + dec->symbolSamples + ); + } + + goertzel_init( + &dec->tones[index++], + dec->clock0, + SAMPLE_RATE, + dec->symbolSamples + ); + + 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); + + 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) + return -1; + + return e1 > e0; +} + +static void clock_search( + Decoder *dec, + int clock) +{ + if(clock < 0) + { + dec->clockHistory = 0; + dec->clockCount = 0; + return; + } + + + dec->clockHistory = + (dec->clockHistory << 1) | + (clock & 1); + + + if(dec->clockCount < 8) + dec->clockCount++; + + + if(dec->clockCount >= 8) + { + uint8_t h = dec->clockHistory & 0xff; + + + if(h == 0x55 || h == 0xAA) + { + printf("CLOCK LOCK pattern=%02X\n", h); + dec->goodClocks = 8; + dec->clockHistory = 0; + dec->clockCount = 0; + + return; + } + + 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); + + dec->energies[index++] = e0; + dec->energies[index++] = e1; + dec->maxEnergy = fmaxf(dec->maxEnergy * 0.85f, max); + + if (e1 > e0) + byte |= 1u << bit; + } + + dec->lastDecodedByte = byte; + return byte; +} + +void decoder_process( + Decoder *dec, + float *samples, + uint32_t count) { + const int tones = dec->bitCount * 2 + 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]); + } + + if (dec->rxState == RX_ALIGN) { + if (dec->phaseWait > 0) + dec->phaseWait--; + + 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) + continue; + + dec->sampleCount = 0; + + switch (dec->rxState) { + case RX_SEARCH: { + 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; + } + + 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); + break; + } + + } + } +} + +void input_callback( + void *userdata, + Uint8 *stream, + int len) { + Decoder *dec = userdata; + + float *samples = (float *) stream; + int count = len / sizeof(float); + + // Read from internal bus for loopback + for (int i = 0; i < count; i++) { + if (internalBusRead != internalBusWrite) { + samples[i] = internalBusSamples[internalBusRead]; + internalBusRead = (internalBusRead + 1) % INTERNAL_BUFFER_SIZE; + } else { + samples[i] = 0.0f; + } + } + + decoder_process( + dec, + samples, + count + ); +} + +SDL_AudioDeviceID inputDev; +AudioData decodedAudioData; + +void initAudioDecoder(SDL_Renderer *renderer) { + memset(&decodedAudioData, 0, sizeof(AudioData)); + decoder_init(&decoder); + decoder.renderer = renderer; + + SDL_AudioSpec inSpec = {0}; + inSpec.freq = SAMPLE_RATE; + inSpec.format = AUDIO_F32SYS; + inSpec.channels = 1; + inSpec.samples = 512; + inSpec.callback = input_callback; + inSpec.userdata = &decoder; + + SDL_AudioSpec realSpec = {0}; + + inputDev = SDL_OpenAudioDevice(NULL, 1, &inSpec, &realSpec, 0); + printf("Decoder device opened: inputDev=%u\n", inputDev); + if (inputDev == 0) { + 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); + SDL_PauseAudioDevice(inputDev, 0); +} diff --git a/decoder.h b/decoder.h new file mode 100644 index 0000000..9aa8387 --- /dev/null +++ b/decoder.h @@ -0,0 +1,120 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#ifndef AUDIOCODER_DECODER_H +#define AUDIOCODER_DECODER_H + +#define MAX_BITS 16 +#define PREAMBLE_SYMBOLS 128 +#define DECODER_SAMPLE_HISTORY 262144 +#include +#include +#include + +#include "goertzel.h" + +typedef enum { + RX_SEARCH, + RX_ALIGN, + RX_SYNC, + RX_LOCKED +} RxState; + +typedef struct { + + uint8_t bitCount; + + uint16_t startFreq; + uint16_t endFreq; + uint16_t dataSpacing; + + uint16_t clock0; + uint16_t clock1; + + + uint32_t symbolSamples; + + + uint32_t sampleCount; + + float sampleHistory[DECODER_SAMPLE_HISTORY]; + uint32_t sampleHistoryWrite; + uint32_t sampleHistoryCount; + uint64_t totalSamples; + + Goertzel tones[MAX_BITS * 2 + 2]; + + uint8_t currentByte; + uint8_t bitIndex; + + uint8_t buffer[1024*1024*8]; + + uint32_t bufferSize; + + bool lastClock; + + + float energies[MAX_BITS * 2 + 2]; + float maxEnergy; + float clockEnergy0; + float clockEnergy1; + float clockRatio; + uint8_t lastDecodedByte; + int bestSyncScore; + int bestSyncPhase; + + RxState rxState; + + bool expectedClock; + float lastClockEnergy; + uint32_t goodClocks; + uint32_t syncIndex; + uint32_t badClocks; + + uint64_t phaseSearchStart; + uint32_t phaseWait; + int phaseOffset; + bool phaseSearchReady; + + uint32_t searchOffset; + uint32_t syncOffset; + uint8_t clockHistory; + uint8_t clockCount; + + bool locked; + + SDL_Renderer *renderer; + +} Decoder; + + + +void decoder_init( + Decoder *dec +); + + +void decoder_process( + Decoder *dec, + float *samples, + uint32_t count +); + +void decoder_rebuild_tones( + Decoder *dec +); + +extern Decoder decoder; +extern SDL_AudioDeviceID inputDev; +extern const uint8_t syncBytes[4]; + +// Internal audio bus for loopback +#define INTERNAL_BUFFER_SIZE 262144 +extern float internalBusSamples[INTERNAL_BUFFER_SIZE]; +extern volatile uint32_t internalBusWrite; +extern volatile uint32_t internalBusRead; + +void initAudioDecoder(SDL_Renderer *renderer); + +#endif //AUDIOCODER_DECODER_H diff --git a/encoder.c b/encoder.c new file mode 100644 index 0000000..be0e565 --- /dev/null +++ b/encoder.c @@ -0,0 +1,322 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#include "encoder.h" + +#include +#include +#include + +#include "goertzel.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] = +{ + 'B', + 'R', + 'N', + 'Q' +}; + +static void encoder_set_bit( + AudioData *audio, + Encoder *enc, + unsigned bitIndex, + int bit) +{ + /* + float spacing = + (float)(enc->endFreq - enc->startFreq) / + (enc->bitCount * 2 - 1); + */ + + uint16_t freq = + enc->startFreq + + enc->dataSpacing * (bitIndex * 2 + bit); + + freq = goertzel_bin_freq( + freq, + SAMPLE_RATE, + enc->symbolSamples +); + + SynthVoice *v = + &audio->synthVoices[bitIndex + 1]; + + v->frequency = freq; + v->phaseStep = + ((uint64_t)freq << 32) / SAMPLE_RATE; + v->volume = 255; +} + +static int encoder_get_bit(Encoder *enc) { + if (enc->byteIndex >= enc->bufferSize) + return -1; + + int bit = (enc->buffer[enc->byteIndex] >> enc->bitIndex) & 1; + + enc->bitIndex++; + + if (enc->bitIndex == 8) { + enc->bitIndex = 0; + enc->byteIndex++; + } + + return bit; +} + +static void encoder_clock( + Encoder *enc, + AudioData *audio) +{ + enc->clockState ^= 1; + + SynthVoice *clock = + &audio->synthVoices[0]; + + uint16_t freq = + enc->clockState ? + enc->clock1 : + enc->clock0; + + clock->frequency = freq; + clock->phaseStep = + ((uint64_t)freq << 32) / + SAMPLE_RATE; + clock->volume = 255; +} + +void encoder_next_symbol( + Encoder *enc, + AudioData *audio) +{ + encoder_clock(enc, audio); + + switch(enc->state) + { + case TX_PREAMBLE: + + /* transmit all-zero symbol */ + + for(unsigned i=0;ibitCount;i++) + encoder_set_bit(audio,enc,i,0); + + enc->preambleSymbols++; + + if(enc->preambleSymbols >= PREAMBLE_SYMBOLS) + { + enc->syncIndex = 0; + enc->bitIndex = 0; + enc->state = TX_SYNC; + } + + break; + + case TX_SYNC: + { + uint8_t byte = + syncBytes[enc->syncIndex]; + + for(unsigned i=0;ibitCount;i++) + { + encoder_set_bit( + audio, + enc, + i, + (byte >> i) & 1); + } + + enc->syncIndex++; + + if(enc->syncIndex == + sizeof(syncBytes)) + { + enc->state = TX_DATA; + enc->byteIndex = 0; + enc->bitIndex = 0; + } + + break; + } + + case TX_DATA: + { + int finished = 0; + + for(unsigned i=0;ibitCount;i++) + { + int bit = + encoder_get_bit(enc); + + if(bit < 0) + { + bit = 0; + finished++; + } + + encoder_set_bit( + audio, + enc, + i, + bit); + } + + if(finished == + enc->bitCount) + { + enc->state = TX_END; + } + + break; + } + + case TX_END: + + stopEncoder(enc); + break; + + default: + break; + } + + enc->samplesRemaining = + enc->symbolSamples; +} + +void startEncoder(Encoder *enc) +{ + enc->state = TX_PREAMBLE; + enc->transmitting = true; + + enc->byteIndex = 0; + enc->bitIndex = 0; + enc->samplesRemaining = 0; + enc->clockState = false; + + enc->preambleSymbols = 0; + enc->syncIndex = 0; +} + +void stopEncoder(Encoder *enc) +{ + enc->transmitting=false; + enc->state=TX_IDLE; + + for(int i=0;iencoder; + + if (enc->state == TX_END ||!enc->transmitting) { + memset(stream, 0, len); + return; + } + + float *out = (float *) stream; + int samples = len / sizeof(float); + + for (int i = 0; i < samples; i++) { + 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++) { + SynthVoice *voice = &audio->synthVoices[v]; + + if (voice->volume == 0) + continue; + + voice->phase += voice->phaseStep; + + float sample = sinf( + ((double) voice->phase / 4294967296.0) * + (2.0 * M_PI)); + + mix += sample * (voice->volume / 255.0f); + + active++; + } + + 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; + } +} + +void initEncoder() +{ + memset(&encoder,0,sizeof(Encoder)); + + encoder.bufferSize = 0; + encoder.bitCount = 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.clock1 = goertzel_bin_freq( + 6300, + SAMPLE_RATE, + encoder.symbolSamples + ); + + + encoder.state = TX_IDLE; + encoder.transmitting = false; + +} + +void initAudioEncoder() { + memset(&encodedAudioData, 0, sizeof(AudioData)); + initEncoder(); + + SDL_AudioSpec spec = {0}; + spec.freq = SAMPLE_RATE; + spec.format = AUDIO_F32SYS; + spec.channels = 1; + spec.samples = 4096; + 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); + if (dev == 0) { + printf("Failed to open audio encoder: %s\n", SDL_GetError()); + SDL_Quit(); + } + SDL_PauseAudioDevice(dev, 0); +} diff --git a/encoder.h b/encoder.h new file mode 100644 index 0000000..d21f391 --- /dev/null +++ b/encoder.h @@ -0,0 +1,91 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#ifndef AUDIOCODER_ENCODER_H +#define AUDIOCODER_ENCODER_H + +#define SAMPLE_RATE 48000 +#define MAX_BITS 16 +#define CLOCK_VOICE MAX_BITS +#define MAX_PACKET 223 + +#include +#include + +typedef struct SynthVoice { + uint32_t phase; + uint32_t phaseStep; + uint16_t frequency; + uint8_t volume; +} SynthVoice; + +typedef enum { + TX_IDLE, + TX_PREAMBLE, + TX_SYNC, + TX_DATA, + TX_END +} TxState; + + +typedef struct { + + unsigned int bitCount; + + unsigned int byteIndex; + unsigned int bitIndex; + uint8_t buffer[1024 * 1024 * 8]; + unsigned int bufferSize; + + + + uint16_t startFreq; + uint16_t endFreq; + uint16_t dataSpacing; + + + uint16_t clock0; + uint16_t clock1; + + bool clockState; + + + uint32_t symbolSamples; + uint32_t samplesRemaining; + + + TxState state; + uint32_t preambleSymbols; + uint32_t syncIndex; + + bool transmitting; + + +} Encoder; + + +typedef struct AudioData { + SynthVoice synthVoices[MAX_BITS + 1]; + Encoder *encoder; +} AudioData; + +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]; + +#endif //AUDIOCODER_ENCODER_H diff --git a/font.c b/font.c new file mode 100644 index 0000000..b8371a4 --- /dev/null +++ b/font.c @@ -0,0 +1,61 @@ +// +// Created by bruno on 1.2.2025. +// + +#include "font.h" + +BitmapFont +prepText(SDL_Renderer *renderer, unsigned char pxSize, const char *file, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + TTF_Font *gFont = TTF_OpenFont(file, pxSize); + BitmapFont out; + out.size = pxSize; + out.color = (SDL_Color) {r, g, b, a}; + unsigned int i = 1; + do { + if (i == 173) { //specifically this char is 0 width (IDK why) + out.surface[i] = SDL_CreateRGBSurface(0, pxSize, pxSize, 32, 0, 0, 0, 0); + } else { + char tmpOut[2] = {i, 0}; + out.surface[i] = TTF_RenderText_Solid(gFont, tmpOut, out.color); + } + out.texture[i] = SDL_CreateTextureFromSurface(renderer, out.surface[i]); + i++; + } while (i < 256); + + TTF_CloseFont(gFont); + return out; +} + +void renderText(SDL_Renderer *renderer, BitmapFont font, char *string, uint16_t x, uint16_t y) { + SDL_Rect charRect; + charRect.x = 0; + charRect.y = 0; + charRect.w = font.size; + charRect.h = font.size; + SDL_Rect outRect = charRect; + outRect.x = x; + outRect.y = y; + + while (*string) { + if (*string == '\n') { + outRect.x = x; + outRect.y += charRect.h + 4; + string++; + continue; + } + SDL_RenderCopy(renderer, font.texture[*string], &charRect, &outRect); + outRect.x += charRect.w + 1; + string++; + } +} + +void destroyFont(BitmapFont *font) { + for (uint16_t i = 1; i < 256; i++) { + if (font->texture[i]) { + SDL_DestroyTexture(font->texture[i]); + } + if (font->surface[i]) { + SDL_FreeSurface(font->surface[i]); + } + } +} \ No newline at end of file diff --git a/font.h b/font.h new file mode 100644 index 0000000..98272a2 --- /dev/null +++ b/font.h @@ -0,0 +1,26 @@ +// +// Created by bruno on 1.2.2025. +// + +#ifndef RISCB_FONT_H +#define RISCB_FONT_H + +#include +#include +#include + +typedef struct { + SDL_Texture *texture[256]; + SDL_Surface *surface[256]; + uint8_t size; + SDL_Color color; +} BitmapFont; + +BitmapFont +prepText(SDL_Renderer *renderer, unsigned char pxSize, const char *file, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void destroyFont(BitmapFont *font); + +void renderText(SDL_Renderer *renderer, BitmapFont font, char *string, uint16_t x, uint16_t y); + +#endif //RISCB_FONT_H diff --git a/goertzel.c b/goertzel.c new file mode 100644 index 0000000..9e78553 --- /dev/null +++ b/goertzel.c @@ -0,0 +1,75 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#include "goertzel.h" +#include + + +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); + + + g->s1=0; + g->s2=0; + +} + + + +void goertzel_add( + Goertzel *g, + float sample) +{ + + float s = + sample + + g->coeff*g->s1 - + g->s2; + + + g->s2=g->s1; + g->s1=s; + +} + + + +float goertzel_energy( + Goertzel *g) +{ + + 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 bin = (freq * samples + sampleRate / 2) / sampleRate; + + return bin * sampleRate / samples; +} \ No newline at end of file diff --git a/goertzel.h b/goertzel.h new file mode 100644 index 0000000..604f1d0 --- /dev/null +++ b/goertzel.h @@ -0,0 +1,36 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#ifndef AUDIOCODER_GOERTZEL_H +#define AUDIOCODER_GOERTZEL_H + +typedef struct { + float coeff; + + float s1; + float s2; +} Goertzel; + + +void goertzel_init( + Goertzel *g, + float freq, + float sampleRate, + int samples); + + +void goertzel_add( + Goertzel *g, + float sample); + + +float goertzel_energy( + Goertzel *g); + +int goertzel_bin_freq( + int freq, + int sampleRate, + int samples); + +#endif //AUDIOCODER_GOERTZEL_H diff --git a/hexdump.c b/hexdump.c new file mode 100644 index 0000000..e2e4c89 --- /dev/null +++ b/hexdump.c @@ -0,0 +1,46 @@ +// +// Created by bruno on 6.2.2025. +// + +#include "hexdump.h" +#include +#include +#include +#include +#include + +#define BYTES_PER_LINE 8 // Adjust for different widths + + +char *hexdump_to_string(const unsigned char *data, uint32_t start, uint32_t end) { + if (start >= end) return NULL; // Invalid range + + // Estimate max output size: each line is approx. 80 chars + uint32_t estimated_size = ((end - start) / BYTES_PER_LINE + 1) * 60; + + // Allocate memory for output string + char *output = malloc(estimated_size); + if (!output) return NULL; + + uint32_t out_offset = 0; // Tracks position in output buffer + + for (uint32_t i = start; i < end; i += BYTES_PER_LINE) { + // Print offset + out_offset += snprintf(output + out_offset, estimated_size - out_offset, "%07x ", i); + + // Print hex values + for (uint32_t j = 0; j < BYTES_PER_LINE; j++) { + if (i + j < end) + out_offset += snprintf(output + out_offset, estimated_size - out_offset, "%02x ", data[i + j]); + else + out_offset += snprintf(output + out_offset, estimated_size - out_offset, " "); // Padding + if (j == 7) + out_offset += snprintf(output + out_offset, estimated_size - out_offset, " "); // Extra space + } + + out_offset += snprintf(output + out_offset, estimated_size - out_offset, "\n"); + } + out_offset += snprintf(output + out_offset, estimated_size - out_offset, "\n"); + + return output; +} diff --git a/hexdump.h b/hexdump.h new file mode 100644 index 0000000..4e3abb3 --- /dev/null +++ b/hexdump.h @@ -0,0 +1,13 @@ +// +// Created by bruno on 6.2.2025. +// + +#ifndef RISCB_HEXDUMP_H +#define RISCB_HEXDUMP_H + +#include +#include + +char *hexdump_to_string(const unsigned char *data, uint32_t start, uint32_t end); + +#endif //RISCB_HEXDUMP_H diff --git a/main.c b/main.c new file mode 100644 index 0000000..7396d94 --- /dev/null +++ b/main.c @@ -0,0 +1,596 @@ +#include +#include +#include +#include +#include +#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; +volatile bool running = true; + +//The surface contained by the window +SDL_Renderer *renderer = NULL; +#define SCREEN_WIDTH 1280 +#define SCREEN_HEIGHT 720 + +const int targetFPS = 60; +const int delayNeeded = 1000 / targetFPS; + + +SDL_Texture *cpuStateTexture; +int selectedModemSetting = 0; + +#define biggerFont fonts[0] +#define smallFont fonts[1] +#define smallerFont fonts[2] + +#define fontCount 3 + +BitmapFont fonts[fontCount]; + +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_END: return "END"; + } + return "?"; +} + +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"; + } + return "?"; +} + +static const char *setting_name(int setting) { + switch (setting) { + case 0: return "DATA START"; + case 1: return "DATA END"; + case 2: return "SPACING"; + case 3: return "SYMBOL"; + case 4: return "CLOCK0"; + case 5: return "CLOCK1"; + case 6: return "OUT DEVICE"; + case 7: return "IN DEVICE"; + } + return "?"; +} + +static void apply_modem_settings(void) { + SDL_LockAudioDevice(dev); + SDL_LockAudioDevice(inputDev); + + stopEncoder(&encoder); + + encoder.clock0 = goertzel_bin_freq( + 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; + decoder.symbolSamples = encoder.symbolSamples; + decoder.clock0 = encoder.clock0; + decoder.clock1 = encoder.clock1; + decoder_rebuild_tones(&decoder); + + SDL_UnlockAudioDevice(inputDev); + SDL_UnlockAudioDevice(dev); +} + +static void adjust_modem_setting(int delta) { + switch (selectedModemSetting) { + case 0: + if ((int)encoder.startFreq + delta * 50 >= 200) + encoder.startFreq += delta * 50; + break; + case 1: + if ((int)encoder.endFreq + delta * 50 >= 200) + encoder.endFreq += delta * 50; + break; + case 2: + if ((int)encoder.dataSpacing + delta * 5 >= 25) + encoder.dataSpacing += delta * 5; + break; + case 3: + if ((int)encoder.symbolSamples + delta * 48 >= 240) + encoder.symbolSamples += delta * 48; + break; + case 4: + if ((int)encoder.clock0 + delta * 50 >= 500) + encoder.clock0 += delta * 50; + break; + case 5: + if ((int)encoder.clock1 + delta * 50 >= 500) + encoder.clock1 += delta * 50; + break; + case 6: + case 7: + // Device selection handled separately + break; + } + + apply_modem_settings(); +} + +static void render_modem_text(SDL_Renderer *renderer) { + char line[256]; + + snprintf( + line, + sizeof(line), + "TX %-8s BYTE %u/%u BIT %u PRE %u SYNC %u", + tx_state_name(encoder.state), + encoder.byteIndex, + encoder.bufferSize, + encoder.bitIndex, + encoder.preambleSymbols, + encoder.syncIndex); + renderText(renderer, smallerFont, line, 10, 8); + + snprintf( + line, + sizeof(line), + "RX %-8s BYTES %u LAST %02X BESTSYNC %d/4 PHASE %d", + rx_state_name(decoder.rxState), + decoder.bufferSize, + decoder.lastDecodedByte, + decoder.bestSyncScore, + decoder.bestSyncPhase); + renderText(renderer, smallerFont, line, 10, 22); + + snprintf( + line, + sizeof(line), + "CLK E0 %.5f E1 %.5f R %.1f HIST %02X/%u", + decoder.clockEnergy0, + decoder.clockEnergy1, + decoder.clockRatio, + decoder.clockHistory, + decoder.clockCount); + renderText(renderer, smallerFont, line, 10, 36); + + // Settings display with highlighting + const char *settingStr = setting_name(selectedModemSetting); + snprintf( + line, + sizeof(line), + ">> %s START %u END %u SPACE %u SYM %u CLK %u/%u [F8 SELECT] [+/- CHANGE]", + settingStr, + encoder.startFreq, + encoder.endFreq, + encoder.dataSpacing, + encoder.symbolSamples, + encoder.clock0, + encoder.clock1); + + // Highlight the settings line with a colored background + SDL_Rect settingsBG = {5, 48, 1270, 14}; + SDL_SetRenderDrawColor(renderer, 64, 80, 96, 128); + SDL_RenderFillRect(renderer, &settingsBG); + + renderText(renderer, smallerFont, line, 10, 50); +} + +static void render_tone_bars(SDL_Renderer *renderer) { + const int tones = decoder.bitCount * 2 + 2; + const int x = 500; + const int y = 560; + const int maxHeight = 72; + const int barWidth = 8; + const int gap = 4; + + SDL_SetRenderDrawColor(renderer, 64, 64, 64, 255); + SDL_Rect frame = {x - 4, y - maxHeight - 4, tones * (barWidth + gap) + 4, maxHeight + 24}; + SDL_RenderDrawRect(renderer, &frame); + + for (int i = 0; i < tones; i++) { + float ratio = 0.0f; + if (decoder.maxEnergy > 0.0f) + ratio = decoder.energies[i] / decoder.maxEnergy; + if (ratio > 1.0f) + ratio = 1.0f; + + SDL_Rect barRect; + barRect.w = barWidth; + barRect.h = (int)(ratio * maxHeight); + 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); + else + SDL_SetRenderDrawColor(renderer, 80, 255, 140, 255); + + SDL_RenderFillRect(renderer, &barRect); + } + + char label[128]; + snprintf(label, sizeof(label), "TONE ENERGY: BIT0..BIT7, CLOCK0, CLOCK1"); + renderText(renderer, smallerFont, label, x, y + 6); +} + + +int render() { + SDL_SetRenderDrawColor(renderer, 32, 32, 32, 255); + SDL_RenderClear(renderer); + + + SDL_Rect rect2; + rect2.x = 100; + rect2.y = 46; + rect2.w = 0; + rect2.h = 0; + + if (cpuStateTexture) { + SDL_QueryTexture(cpuStateTexture, NULL, NULL, &rect2.w, &rect2.h); + + SDL_RenderCopy(renderer, cpuStateTexture, NULL, &rect2); + } + + render_modem_text(renderer); + render_tone_bars(renderer); + + for (int i = 0; i < editorCount; i++) { + editor_render(&editors[i], renderer, &encoder, i, activeEditorIndex == i, cursorShown); + } + frames++; + if (!(frames % 60)) { + cursorShown = !cursorShown; + } + + SDL_RenderPresent(renderer); + + return 0; +} + +int init() { + //Initialize SDL + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, NULL); + SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1"); + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { + printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); + return 1; + } + + //Initialize SDL_ttf + if (TTF_Init() == -1) { + printf("SDL_ttf could not initialize! SDL_ttf Error: %s\n", TTF_GetError()); + return 1; + } + + //Create window + window = SDL_CreateWindow("RISC-B simulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, + SCREEN_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + if (window == NULL) { + printf("Window could not be created! SDL_Error: %s\n", SDL_GetError()); + return 1; + } + //Get window surface + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (renderer == NULL) { + printf("Renderer could not be created SDL_Error: %s\n", SDL_GetError()); + return 1; + } + + // Create OpenGL context + SDL_GLContext glContext = SDL_GL_CreateContext(window); + if (!glContext) { + fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError()); + exit(1); + } + + // Use OpenGL context + SDL_GL_MakeCurrent(window, glContext); // Make sure OpenGL context is current before any OpenGL rendering + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + biggerFont = prepText(renderer, 16, "PublicPixel.ttf", 255, 255, 255, 255); + smallFont = prepText(renderer, 12, "PublicPixel.ttf", 255, 255, 255, 255); + smallerFont = prepText(renderer, 8, "PublicPixel.ttf", 255, 255, 255, 255); + init_editor(&transmitEditor, &smallFont, 10, 80, renderer, 35, 1000, 28, false); + init_editor(&receivedEditor, &smallFont, 500, 80, renderer, 35, 1000, 28, false); + + initAudioEncoder(); + initAudioDecoder(renderer); + strcpy(encoder.buffer, "Hello there\nThis is just a test message\n"); + + encoder.bufferSize = strlen(encoder.buffer); + + updateDump(&transmitEditor, renderer); + + + SDL_Rect viewport = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; + SDL_RenderSetViewport(renderer, &viewport); + SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT); + + return 0; +} + + +int processEvent(SDL_Event e) { + if (e.type == SDL_QUIT) { return 0; } + else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_RESIZED) { + int newWidth = e.window.data1; + int newHeight = e.window.data2; + + for (int editorIndex = 0; editorIndex < editorCount; editorIndex++) { + generate_string_display(&editors[editorIndex], renderer); + } + //updateState(true); + + // Adjust the viewport to match the new window size; + SDL_Rect viewport = {0, 0, newWidth, newHeight}; + SDL_RenderSetViewport(renderer, &viewport); + } else if (e.type == SDL_KEYDOWN) { + int keySym = ConvertKPToNonKP(e.key.keysym.sym); + int keyMod = e.key.keysym.mod; + cursorShown = true; + bool moved = false; + switch (keySym) { + case SDLK_UP: + move_cursor_relative(&activeEditor, -1, 0, false, renderer); + if (keyMod & KMOD_CTRL) { + encoder.byteIndex--; + } + moved = true; + break; + case SDLK_PAGEUP: + move_cursor_relative(&activeEditor, -activeEditor.displayLineCount, -1, true, renderer); + moved = true; + break; + case SDLK_DOWN: + move_cursor_relative(&activeEditor, 1, 0, false, renderer); + if (keyMod & KMOD_CTRL) { + encoder.byteIndex++; + } + moved = true; + break; + case SDLK_PAGEDOWN: + move_cursor_relative(&activeEditor, activeEditor.displayLineCount, 0, true, renderer); + moved = true; + break; + case SDLK_LEFT: + move_cursor_relative(&activeEditor, 0, -1, false, renderer); + moved = true; + break; + case SDLK_HOME: + if (keyMod & KMOD_CTRL) { + move_cursor(&activeEditor, 0, 0, false, renderer); + moved = true; + break; + } + move_cursor(&activeEditor, activeEditor.cursor_line, 0, false, renderer); + moved = true; + break; + case SDLK_RIGHT: + move_cursor_relative(&activeEditor, 0, 1, false, renderer); + moved = true; + break; + case SDLK_END: + int lineLen = strlen(activeEditor.lines[activeEditor.cursor_line].text); + if (keyMod & KMOD_CTRL) { + move_cursor(&activeEditor, activeEditor.maxLines - 1, lineLen, false, renderer); + moved = true; + break; + } + move_cursor(&activeEditor, activeEditor.cursor_line, lineLen, false, renderer); + moved = true; + break; + case SDLK_d: + if (keyMod & KMOD_CTRL && !activeEditor.readOnly) { + insert_line_rel(&activeEditor, renderer); + memset(activeEditor.lines[activeEditor.cursor_line].text, 0, activeEditor.max_line_width); + strcpy(activeEditor.lines[activeEditor.cursor_line].text, + activeEditor.lines[activeEditor.cursor_line - 1].text); + activeEditor.lines[activeEditor.cursor_line].active = 1; + move_cursor_relative(&activeEditor, 0, activeEditor.max_line_width, false, renderer); + generate_string_display(&activeEditor, renderer); + } + break; + + case SDLK_ESCAPE: + stopEncoder(&encoder); + break; + + case SDLK_s: + if (keyMod & KMOD_CTRL) { + FILE *fptr; + char fname[20]; + snprintf(fname, sizeof(fname), "data%lu.bin", time(NULL)); + fptr = fopen(fname, "wb"); + unsigned int sizeBuffer = sizeof(encoder.buffer); + if (encoder.bufferSize < sizeBuffer) { + sizeBuffer = encoder.bufferSize; + } + fwrite(encoder.buffer, sizeBuffer, 1, fptr); + fclose(fptr); + return 1; + } + break; + + case SDLK_l: + if (keyMod & KMOD_CTRL) { + FILE *fptr; + char fname[20]; + sscanf(editors[0].lines[editors[0].cursor_line].text, "%s", fname); + fptr = fopen(fname, "rb"); + if (fptr) { + memset(encoder.buffer, 0, sizeof(encoder.buffer)); + encoder.bufferSize = fread( + encoder.buffer, + 1, + sizeof(encoder.buffer), + fptr); + fclose(fptr); + updateDump(&transmitEditor, renderer); + } + return 1; + } + break; + case SDLK_BACKSPACE: + if (!activeEditor.readOnly) { + remove_character(&activeEditor, false, renderer); + moved = true; + } + break; + case SDLK_DELETE: + if (keyMod & KMOD_CTRL) { + memset(encoder.buffer, 0, sizeof(encoder.buffer)); + encoder.bufferSize = 0; + updateDump(&transmitEditor, renderer); + + } else if (keyMod & KMOD_ALT) { + memset(decoder.buffer, 0, sizeof(decoder.buffer)); + decoder.bufferSize = 0; + updateDump(&receivedEditor, renderer); + + } else { + if (!activeEditor.readOnly) { + remove_character(&activeEditor, true, renderer); + moved = true; + } + } + break; + case SDLK_RETURN: + break; + case SDLK_F5: + updateDump(&transmitEditor, renderer); + break; + case SDLK_F7: + startEncoder(&encoder); + break; + case SDLK_F8: + selectedModemSetting++; + if (selectedModemSetting >= 8) + selectedModemSetting = 0; + break; + case SDLK_PLUS: + case SDLK_EQUALS: + adjust_modem_setting(1); + break; + case SDLK_MINUS: + adjust_modem_setting(-1); + break; + case SDLK_INSERT: + activeEditor.overwrite = !activeEditor.overwrite; + break; + case SDLK_RETURN2: + if (!activeEditor.readOnly) { + insert_line_rel(&activeEditor, renderer); + moved = true; + } + break; + case SDLK_TAB: + activeEditorIndex++; + if (activeEditorIndex >= editorCount) { + activeEditorIndex = 0; + } + activeEditor = editors[activeEditorIndex]; + break; + default: + break; + } + } else if (e.type == SDL_TEXTINPUT) { + for (int i = 0; e.text.text[i] != '\0'; i++) { // Iterate over the input string + char keySym = e.text.text[i]; + + if (!activeEditor.readOnly && activeEditor.cursor_pos <= activeEditor.max_line_width) { + if (keySym >= 32 && keySym <= 126) { // Printable ASCII range + if (keySym > 0x60 && keySym < 0x7b) { // Convert lowercase to uppercase + keySym -= 0x20; + } + if (activeEditor.cursor_pos < activeEditor.max_line_width) { + insert_character(&activeEditor, keySym, renderer); + } + } + } + } + } else if (e.type == SDL_MOUSEBUTTONDOWN) { + SDL_Rect viewport; + SDL_RenderGetViewport(renderer, &viewport); + + SDL_Rect mouset; + mouset.w = 1; + mouset.h = 1; + SDL_GetMouseState(&mouset.x, &mouset.y); + // Translate mouse coordinates to viewport space + SDL_Rect mouse; + mouse.w = 1; + mouse.h = 1; + mouse.x = ((mouset.x - viewport.x) * SCREEN_WIDTH) / viewport.w; + mouse.y = (mouset.y - viewport.y) * SCREEN_HEIGHT / viewport.h; + } + return 1; +} + +int main(void) { + int status = init(); + if (status) { + return status; + } + + //Hack to get window to stay up + SDL_Event e; + Uint64 start; + Uint64 end; + while (running) { + start = SDL_GetTicks64(); + + while (SDL_PollEvent(&e)) { + running = processEvent(e); + } + + updateDump(&receivedEditor, decoder.renderer); + status = render(); + if (status) { + return status; + } + + + end = SDL_GetTicks64(); + const unsigned long timeNeeded = end - start; + if (timeNeeded < delayNeeded) { + SDL_Delay(delayNeeded - timeNeeded); + } + } + + for (uint8_t i = 0; i < editorCount; i++) { + destroy_editor(&editors[i]); + } + + for (uint8_t i = 0; i < fontCount; i++) { + destroyFont(&fonts[i]); + } + + puts(SDL_GetError()); + + if (cpuStateTexture) SDL_DestroyTexture(cpuStateTexture); + + if (renderer) SDL_DestroyRenderer(renderer); + if (window) SDL_DestroyWindow(window); + + SDL_Quit(); + return 0; +} diff --git a/packet.c b/packet.c new file mode 100644 index 0000000..42c9f64 --- /dev/null +++ b/packet.c @@ -0,0 +1,136 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#include "packet.h" +#include "crc32.h" + +#include +#include + + +#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 + }; + + + memcpy( + out + pos, + sync, + sizeof(sync)); + + pos += sizeof(sync); + + + uint32_t offset = 0; + + + for (uint32_t p = 0; p < packets; p++) { + uint16_t magic = MAGIC; + + uint16_t packet = p; + + + uint16_t len = + size - offset > PACKET_SIZE ? PACKET_SIZE : size - offset; + + + memcpy(out + pos, &magic, 2); + pos += 2; + + + memcpy(out + pos, &packet, 2); + pos += 2; + + + memcpy(out + pos, &len, 2); + pos += 2; + + + memcpy(out + pos, + file + offset, + len); + + pos += len; + + + uint32_t crc = + calcCRC32( + file + offset, + len); + + + memcpy(out + pos, &crc, 4); + + pos += 4; + + + offset += len; + } + + + *outSize = pos; + + return out; +} diff --git a/packet.h b/packet.h new file mode 100644 index 0000000..f3eb5c1 --- /dev/null +++ b/packet.h @@ -0,0 +1,17 @@ +// +// Created by bruno on 13. 7. 2026. +// + +#ifndef AUDIOCODER_PACKET_H +#define AUDIOCODER_PACKET_H + +#include + + +uint8_t *build_stream( + uint8_t *file, + uint32_t size, + uint32_t *outSize +); + +#endif //AUDIOCODER_PACKET_H diff --git a/texteditor.c b/texteditor.c new file mode 100644 index 0000000..c1e1b24 --- /dev/null +++ b/texteditor.c @@ -0,0 +1,592 @@ +// +// Created by bruno on 5.2.2025. +// Modified to use dynamic limits. +// + +#include "texteditor.h" + +#include "decoder.h" +#include "font.h" +#include "hexdump.h" + +unsigned long frames = 0; +bool cursorShown = true; + +int activeEditorIndex = 0; +TextEditor editors[editorCount]; + +// Initialize the text editor with dynamic sizes. +void init_editor(TextEditor *editor, BitmapFont *font, int x, int y, SDL_Renderer *renderer, + int max_line_width, int max_lines_asm, int max_lines_display, bool readOnly) { + editor->max_line_width = max_line_width; + editor->maxLines = max_lines_asm; + editor->displayLineCount = max_lines_display; + editor->line_count = 0; + editor->cursor_line = 0; + editor->cursor_line_offset = 0; + editor->cursor_pos = 0; + editor->readOnly = readOnly; + editor->font = font; + + editor->outRect = malloc(sizeof(SDL_Rect)); + editor->cursorRect = malloc(sizeof(SDL_Rect)); + editor->rect = malloc(sizeof(SDL_Rect)); + editor->highlightedLineRect = malloc(sizeof(SDL_Rect)); + + memset(editor->outRect, 0, sizeof(SDL_Rect)); + memset(editor->cursorRect, 0, sizeof(SDL_Rect)); + memset(editor->rect, 0, sizeof(SDL_Rect)); + memset(editor->highlightedLineRect, 0, sizeof(SDL_Rect)); + + + // Allocate dynamic array for lines. + editor->lines = (Line *) malloc(sizeof(Line) * editor->maxLines); + if (!editor->lines) { + fprintf(stderr, "Failed to allocate memory for lines.\n"); + exit(EXIT_FAILURE); + } + // For each line, allocate memory for the text (including space for '\0') + for (int i = 0; i < editor->maxLines; i++) { + editor->lines[i].text = (char *) malloc(sizeof(char) * (editor->max_line_width + 1)); + if (!editor->lines[i].text) { + fprintf(stderr, "Failed to allocate memory for line %d.\n", i); + exit(EXIT_FAILURE); + } + editor->lines[i].text[0] = '\0'; + editor->lines[i].active = 0; + } + + // Allocate output and display strings. + editor->outputString = (char *) malloc(sizeof(char) * ((editor->max_line_width + 1) * editor->maxLines + 1) + 1); + editor->displayString = (char *) malloc( + sizeof(char) * ((editor->max_line_width + 1) * editor->displayLineCount + 1) + 1); + if (!editor->outputString || !editor->displayString) { + fprintf(stderr, "Failed to allocate memory for output/display strings.\n"); + exit(EXIT_FAILURE); + } + editor->outputString[0] = '\0'; + editor->displayString[0] = '\0'; + + // Initialize with two active lines (like the original code). + editor->lines[0].active = 1; + editor->lines[1].active = 1; + editor->line_count = 2; + + // Set up the editor rectangle based on font size and dynamic max_line_width and displayLineCount. + editor->rect->x = 2; + editor->rect->y = 2; + editor->rect->w = editor->max_line_width * (font->size + 1) + ((font->size + 1) / 2); + editor->rect->h = editor->displayLineCount * (font->size + 1) + 2; + editor->outRect->w = editor->rect->w; + editor->outRect->h = editor->rect->h; + editor->outRect->x = x; + editor->outRect->y = y; + + editor->highlightedLineRect->x = 2; + editor->highlightedLineRect->y = 2; + editor->highlightedLineRect->w = editor->outRect->w - (2 * editor->highlightedLineRect->x); + editor->highlightedLineRect->h = editor->font->size + 1; + + editor->cursorRect->x = 3 + editor->cursor_pos * font->size + editor->outRect->x; + editor->cursorRect->y = + 2 + (editor->cursor_line - editor->cursor_line_offset) * (font->size + 1) + editor->outRect->y; + + editor->cursorRect->w = editor->font->size; + editor->cursorRect->h = editor->font->size; + + // Create texture for rendering. + editor->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_TARGET, + editor->rect->w, editor->rect->h); + if (!editor->texture) { + fprintf(stderr, "Failed to create texture: %s\n", SDL_GetError()); + exit(EXIT_FAILURE); + } +} + +void insert_line(TextEditor *editor, int position, const char *text, SDL_Renderer *renderer) { + if (editor->line_count >= editor->maxLines || position < 0 || position > editor->line_count) { + printf("Invalid position or max lines reached!\n"); + return; + } + + // Shift lines down. + for (int i = editor->line_count; i > position; i--) { + strcpy(editor->lines[i].text, editor->lines[i - 1].text); + editor->lines[i].active = editor->lines[i - 1].active; + } + + // Copy the text into the new line, ensuring it does not exceed max_line_width. + strncpy(editor->lines[position].text, text, editor->max_line_width); + editor->lines[position].text[editor->max_line_width] = '\0'; + editor->lines[position].active = 1; + + editor->line_count++; + move_cursor(editor, editor->cursor_line + 1, 0, false, renderer); + generate_string_display(editor, renderer); +} + +void insert_line_rel(TextEditor *editor, SDL_Renderer *renderer) { + int line = editor->cursor_line; + int pos = editor->cursor_pos; + char *current_text = editor->lines[line].text; + + // Split the current line at the cursor position + char new_line_text[editor->max_line_width + 1]; + strcpy(new_line_text, ¤t_text[pos]); // Copy the text after cursor to the new line + current_text[pos] = '\0'; // Truncate the current line at cursor position + + insert_line(editor, line + 1, new_line_text, renderer); + editor->cursor_pos = 0; +} + +void insert_character(TextEditor *editor, char ch, SDL_Renderer *renderer) { + if (editor->cursor_line < 0 || editor->cursor_line >= editor->line_count) { + printf("Invalid cursor position!\n"); + return; + } + + Line *line = &editor->lines[editor->cursor_line]; + int len = strlen(line->text); + + if (editor->overwrite) { + if (editor->cursor_pos > len) { + printf("Position out of bounds or line is full!\n"); + return; + } + line->text[editor->cursor_pos] = ch; + } else { + + if (len >= editor->max_line_width || editor->cursor_pos > len) { + printf("Position out of bounds or line is full!\n"); + return; + } + + // Shift characters to the right. + for (int i = len; i >= editor->cursor_pos; i--) { + line->text[i + 1] = line->text[i]; + } + + line->text[editor->cursor_pos] = ch; + } + editor->cursor_pos++; + generate_string_display(editor, renderer); +} + +void remove_character(TextEditor *editor, bool isDelete, SDL_Renderer *renderer) { + if (editor->cursor_line < 0 || editor->cursor_line >= editor->line_count) { + printf("Invalid cursor position!\n"); + return; + } + + Line *line = &editor->lines[editor->cursor_line]; + int len = strlen(line->text); + + if (isDelete) { + // Delete character after cursor + if (editor->cursor_pos < len) { + for (int i = editor->cursor_pos; i < len; i++) { + line->text[i] = line->text[i + 1]; + } + } else if (editor->cursor_pos == len && editor->cursor_line < editor->line_count - 1) { + // Merge next line with the current line when pressing delete at the end + strcat(line->text, editor->lines[editor->cursor_line + 1].text); + + // Shift remaining lines up + for (int i = editor->cursor_line + 1; i < editor->line_count - 1; i++) { + strcpy(editor->lines[i].text, editor->lines[i + 1].text); + editor->lines[i].active = editor->lines[i + 1].active; + } + editor->lines[editor->line_count - 1].text[0] = '\0'; + editor->lines[editor->line_count - 1].active = 0; + editor->line_count--; + } + } else { + // Backspace behavior (delete character before cursor) + if (editor->cursor_pos == 0 && editor->cursor_line > 0) { + // Append current line to previous line + int prev_len = strlen(editor->lines[editor->cursor_line - 1].text); + strcat(editor->lines[editor->cursor_line - 1].text, line->text); + + // Shift lines up + for (int i = editor->cursor_line; i < editor->line_count - 1; i++) { + strcpy(editor->lines[i].text, editor->lines[i + 1].text); + editor->lines[i].active = editor->lines[i + 1].active; + } + editor->lines[editor->line_count - 1].text[0] = '\0'; + editor->lines[editor->line_count - 1].active = 0; + editor->line_count--; + + // Move cursor to the end of the previous line + editor->cursor_line--; + editor->cursor_pos = prev_len; + } else if (editor->cursor_pos > 0) { + for (int i = editor->cursor_pos - 1; i < len; i++) { + line->text[i] = line->text[i + 1]; + } + editor->cursor_pos--; + } + } + + generate_string_display(editor, renderer); +} + + +void move_cursor_relative(TextEditor *editor, int line_offset, int pos_offset, bool keepPos, SDL_Renderer *renderer) { + int new_line = editor->cursor_line + line_offset; + int new_pos = editor->cursor_pos + pos_offset; + + move_cursor(editor, new_line, new_pos, keepPos, renderer); +} + +void move_cursor(TextEditor *editor, int new_line, int new_pos, bool keepPos, SDL_Renderer *renderer) { + if (new_line < 0) new_line = 0; + int trgt = editor->line_count - editor->displayLineCount; + if (trgt < 0) { + trgt = 0; + } + if (new_line >= trgt && new_line >= editor->line_count) { + new_line = editor->line_count - + 1; + } + + if (keepPos) { + editor->cursor_line_offset = new_line; + } + + if (new_line < editor->cursor_line_offset) { + editor->cursor_line_offset = new_line; + } + if (new_line >= editor->cursor_line_offset + editor->displayLineCount) { + editor->cursor_line_offset = new_line - editor->displayLineCount + 1; + } + + if (editor->cursor_line_offset >= editor->line_count - editor->displayLineCount) { + editor->cursor_line_offset = editor->line_count - editor->displayLineCount; + } + if (editor->cursor_line_offset < 0) { + editor->cursor_line_offset = 0; + } + + int line_length = strlen(editor->lines[new_line].text); + if (new_pos < 0) new_pos = 0; + if (new_pos > line_length) new_pos = line_length; + + editor->cursor_line = new_line; + editor->cursor_pos = new_pos; + generate_string_display(editor, renderer); +} + +void generate_string_display(TextEditor *editor, SDL_Renderer *renderer) { + if (editor->cursor_line_offset < 0 || editor->cursor_line_offset >= editor->line_count) { + printf("Invalid start line!\n"); + return; + } + + int end_line = editor->cursor_line_offset + editor->displayLineCount; + if (end_line > editor->line_count) + end_line = editor->line_count; + + // Clear the display string. + editor->displayString[0] = '\0'; + + SDL_SetRenderTarget(renderer, editor->texture); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + + SDL_Rect charDstRect; + charDstRect.x = 4; + charDstRect.y = 3; + charDstRect.w = editor->font->size; + charDstRect.h = editor->font->size; + + SDL_Rect charRect; + charRect.x = 0; + charRect.y = 0; + charRect.w = editor->font->size; + charRect.h = editor->font->size; + + editor->cursorRect->x = 2 + editor->cursor_pos * (charDstRect.w + 1) + editor->outRect->x; + editor->cursorRect->y = + 2 + (editor->cursor_line - editor->cursor_line_offset) * (charDstRect.h + 1) + editor->outRect->y; + + for (int line = editor->cursor_line_offset; line < end_line; line++) { + if (editor->lines[line].active) { + strcat(editor->displayString, editor->lines[line].text); + char *linePTR = editor->lines[line].text; + while (*linePTR) { + SDL_RenderCopy(renderer, editor->font->texture[(unsigned char) *linePTR], &charRect, &charDstRect); + charDstRect.x += charDstRect.w + 1; + linePTR++; + } + charDstRect.x = 4; + charDstRect.y += charDstRect.h + 1; + strcat(editor->displayString, "\n"); + } + } + SDL_SetRenderTarget(renderer, NULL); +} + +void editor_render(TextEditor *editor, SDL_Renderer *renderer, Encoder *enc, uint8_t editorIndex, bool isActive, + bool cursorBlink) { + if (isActive) { + SDL_Rect bgRect; + bgRect = *editor->outRect; + bgRect.x -= 6; + bgRect.y -= 6; + bgRect.w += 12; + bgRect.h += 12; + + + SDL_SetRenderDrawColor(renderer, editor->readOnly ? 128 : 0, editor->readOnly ? 0 : 128, 64, 255); + SDL_RenderFillRect(renderer, &bgRect); + + } + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderFillRect(renderer, editor->outRect); + + uint32_t targetLine = 0; + if (editorIndex == 0) { + targetLine = (enc->byteIndex / 8) - editor->cursor_line_offset; + editor->highlightedLineRect->w = editor->font->size * 2 + 2; + editor->highlightedLineRect->x = (editor->font->size * 11) + (3 * (editor->font->size + 1) * (enc->byteIndex % 8)); + editor->highlightedLineRect->y = 1 + ((editor->font->size + 1) * targetLine); + } + + SDL_Rect highlightedLineAbs = *editor->highlightedLineRect; + highlightedLineAbs.x += editor->outRect->x; + highlightedLineAbs.y += editor->outRect->y; + SDL_SetRenderDrawColor(renderer, 128, 0, 255, 255); + if (targetLine >= editor->cursor_line_offset && + targetLine < editor->cursor_line_offset + editor->displayLineCount) { + SDL_RenderFillRect(renderer, &highlightedLineAbs); + } + + + SDL_SetTextureBlendMode(editor + ->texture, SDL_BLENDMODE_BLEND); + SDL_RenderCopy(renderer, editor + ->texture, editor->rect, editor->outRect); + if (isActive && cursorBlink) { + SDL_SetRenderDrawColor(renderer, + editor->overwrite ? 255 : 0, 255, editor->overwrite ? 0 : 255, 128); + SDL_RenderFillRect(renderer, editor + ->cursorRect); + + } +} + +void generate_string(TextEditor *editor) { + editor->outputString[0] = '\0'; + + for (int i = 0; i < editor->maxLines; i++) { + if (editor->lines[i].active) { + strcat(editor->outputString, editor->lines[i].text); + strcat(editor->outputString, "\n"); + } + } +} + +void fill_editor_from_string(TextEditor *editor, const char *content, int lineStart, bool isComplete, + SDL_Renderer *renderer) { + if (!editor || !content) { + printf("Invalid editor or content pointer!\n"); + return; + } + + char *str = content; + + int numLines = 0; + while (*str) if (*str++ == '\n') ++numLines; + + // Clear the current editor content + for (int i = lineStart; i < editor->maxLines && i < lineStart + numLines + 1; i++) { + editor->lines[i].text[0] = '\0'; + editor->lines[i].active = 0; + } + if (isComplete) { + editor->line_count = numLines; + if (editor->line_count >= editor->maxLines) { + editor->line_count = editor->maxLines - 1; + } + } + + // Parse the content and fill the editor lines + const char *ptr = content; + int line_index = lineStart; + + while (*ptr && line_index < editor->maxLines && line_index < numLines + lineStart) { + int char_count = 0; + + // Ensure the text buffer does not overflow + while (*ptr && *ptr != '\n' && char_count < editor->max_line_width) { + editor->lines[line_index].text[char_count++] = *ptr++; + } + + editor->lines[line_index].text[char_count] = '\0'; // Null-terminate + editor->lines[line_index].active = 1; + line_index++; + + // Move past the newline character if present + if (*ptr == '\n') ptr++; + } + editor->lines[line_index].active = 1; + + // Update the total number of lines in use + //editor->line_count = line_index + (line_index < editor->maxLines - 1 ? 1 : 0); + //if (editor->line_count >= editor-> maxLines) + + // Generate the visual representation + generate_string_display(editor, renderer); +} + + +// Free all dynamically allocated memory. +void destroy_editor(TextEditor *editor) { + if (editor->lines) { + for (int i = 0; i < editor->maxLines; i++) { + free(editor->lines[i].text); + } + free(editor->lines); + } + //free(editor->outputString); + //free(editor->displayString); + + free(editor->outRect); + free(editor->rect); + free(editor->cursorRect); + + if (editor->texture) { + SDL_DestroyTexture(editor->texture); + } +} + +SDL_Keycode ConvertKPToNonKP(SDL_Keycode keycode) { + switch (keycode) { + case SDLK_KP_0: + return SDLK_0; + case SDLK_KP_1: + return SDLK_1; + case SDLK_KP_2: + return SDLK_2; + case SDLK_KP_3: + return SDLK_3; + case SDLK_KP_4: + return SDLK_4; + case SDLK_KP_5: + return SDLK_5; + case SDLK_KP_6: + return SDLK_6; + case SDLK_KP_7: + return SDLK_7; + case SDLK_KP_8: + return SDLK_8; + case SDLK_KP_9: + return SDLK_9; + case SDLK_KP_PERIOD: + return SDLK_PERIOD; + case SDLK_KP_COMMA: + return SDLK_COMMA; + case SDLK_KP_DIVIDE: + return SDLK_SLASH; + case SDLK_KP_MULTIPLY: + return SDLK_ASTERISK; + case SDLK_KP_MINUS: + return SDLK_MINUS; + case SDLK_KP_PLUS: + return SDLK_PLUS; + case SDLK_KP_ENTER: + return SDLK_RETURN; + case SDLK_KP_EQUALS: + return SDLK_EQUALS; + case SDLK_KP_LEFTPAREN: + return SDLK_LEFTPAREN; + case SDLK_KP_RIGHTPAREN: + return SDLK_RIGHTPAREN; + case SDLK_KP_LEFTBRACE: + return SDLK_LEFTBRACKET; + case SDLK_KP_RIGHTBRACE: + return SDLK_RIGHTBRACKET; + case SDLK_KP_TAB: + return SDLK_TAB; + case SDLK_KP_BACKSPACE: + return SDLK_BACKSPACE; + case SDLK_KP_A: + return SDLK_a; + case SDLK_KP_B: + return SDLK_b; + case SDLK_KP_C: + return SDLK_c; + case SDLK_KP_D: + return SDLK_d; + case SDLK_KP_E: + return SDLK_e; + case SDLK_KP_F: + return SDLK_f; + case SDLK_KP_XOR: + return SDLK_CARET; + case SDLK_KP_PERCENT: + return SDLK_PERCENT; + case SDLK_KP_LESS: + return SDLK_LESS; + case SDLK_KP_GREATER: + return SDLK_GREATER; + case SDLK_KP_AMPERSAND: + case SDLK_KP_DBLAMPERSAND: + return SDLK_AMPERSAND; // No direct match, best alternative + case SDLK_KP_VERTICALBAR: + case SDLK_KP_DBLVERTICALBAR: + return SDLK_BACKSLASH; // No direct match + case SDLK_KP_COLON: + return SDLK_COLON; + case SDLK_KP_HASH: + return SDLK_HASH; + case SDLK_KP_SPACE: + return SDLK_SPACE; + case SDLK_KP_AT: + return SDLK_AT; + case SDLK_KP_EXCLAM: + return SDLK_EXCLAIM; + case SDLK_KP_MEMADD: + return SDLK_PLUS; + case SDLK_KP_MEMSUBTRACT: + return SDLK_MINUS; + case SDLK_KP_MEMMULTIPLY: + return SDLK_ASTERISK; + case SDLK_KP_MEMDIVIDE: + return SDLK_SLASH; + case SDLK_KP_MEMSTORE: + case SDLK_KP_MEMRECALL: + case SDLK_KP_MEMCLEAR: + case SDLK_KP_PLUSMINUS: + case SDLK_KP_CLEAR: + case SDLK_KP_CLEARENTRY: + case SDLK_KP_BINARY: + case SDLK_KP_OCTAL: + case SDLK_KP_DECIMAL: + case SDLK_KP_HEXADECIMAL: + return SDLK_UNKNOWN; + default: + return keycode; // If it's not a KP key, return it unchanged + } +} + +void updateDump(TextEditor *editor, SDL_Renderer *renderer) { + if (editor == &transmitEditor) { + if (encoder.bufferSize) { + char *dump = hexdump_to_string(encoder.buffer, 0, encoder.bufferSize); + fill_editor_from_string(editor, dump, 0, true, renderer); + free(dump); + } else { + fill_editor_from_string(editor, "\n", 0, true, renderer); + } + } else if (editor == &receivedEditor) { + if (decoder.bufferSize) { + char *dump = hexdump_to_string(decoder.buffer, 0, decoder.bufferSize); + fill_editor_from_string(editor, dump, 0, true, renderer); + free(dump); + } else { + fill_editor_from_string(editor, "\n", 0, true, renderer); + } + } +} diff --git a/texteditor.h b/texteditor.h new file mode 100644 index 0000000..852d847 --- /dev/null +++ b/texteditor.h @@ -0,0 +1,92 @@ +// +// Created by bruno on 5.2.2025. +// Modified to use dynamic limits. +// +#ifndef RISCB_TEXTEDITOR_H +#define RISCB_TEXTEDITOR_H + +#include +#include +#include +#include +#include +#include +#include + +#include "encoder.h" +#include "font.h" + +typedef struct { + char *text; // Dynamically allocated string for this line + int active; // Flag to check if the line is in use +} Line; + +typedef struct { + Line *lines; // Dynamic array of lines + int line_count; // Number of active lines + int maxLines; // Maximum number of lines (e.g. assembly lines) + int max_line_width; // Maximum characters per line (excluding '\0') + int displayLineCount; // Maximum number of lines for display + + int cursor_line; // Current cursor line + int cursor_line_offset; // Display offset (first line in the display) + int cursor_pos; // Current cursor position in line + + char *outputString; // Dynamically allocated output string (size: max_line_width * maxLines + 1) + char *displayString; // Dynamically allocated display string (size: max_line_width * displayLineCount + 1) + SDL_Rect *rect; + SDL_Rect *outRect; + SDL_Texture *texture; + bool readOnly; + BitmapFont *font; + SDL_Rect *cursorRect; + SDL_Rect *highlightedLineRect; + bool overwrite; +} TextEditor; + +#define transmitEditor editors[0] +#define receivedEditor editors[1] +#define editorCount 2 +#define activeEditor editors[activeEditorIndex] + +extern unsigned long frames; +extern bool cursorShown; + +extern int activeEditorIndex; +extern TextEditor editors[editorCount]; + +// Initialize the text editor. The parameters max_line_width, maxLines, and displayLineCount +// determine the dynamic sizes for the text editor. +void init_editor(TextEditor *editor, BitmapFont *font, int x, int y, SDL_Renderer *renderer, + int max_line_width, int max_lines_asm, int max_lines_display, bool readOnly); + +// Other function prototypes remain mostly unchanged but will use the dynamic limits: +void insert_line(TextEditor *editor, int position, const char *text, SDL_Renderer *renderer); + +void insert_line_rel(TextEditor *editor, SDL_Renderer *renderer); + +void editor_render(TextEditor *editor, SDL_Renderer *renderer, Encoder *enc, uint8_t editorIndex, bool isActive, bool cursorBlink); + +void remove_character(TextEditor *editor, bool isDelete, SDL_Renderer *renderer); + +void insert_character(TextEditor *editor, char ch, SDL_Renderer *renderer); + +void move_cursor(TextEditor *editor, int new_line, int new_pos, bool keepPos, SDL_Renderer *renderer); + +void move_cursor_relative(TextEditor *editor, int line_offset, int pos_offset, bool keepPos, SDL_Renderer *renderer); + +void generate_string_display(TextEditor *editor, SDL_Renderer *renderer); + +void generate_string(TextEditor *editor); + +void fill_editor_from_string(TextEditor *editor, const char *content, int lineStart, bool isComplete, + SDL_Renderer *renderer); + +// A cleanup function to free dynamically allocated memory. +void destroy_editor(TextEditor *editor); + +SDL_Keycode ConvertKPToNonKP(SDL_Keycode keycode); + +void updateDump(TextEditor *editor, SDL_Renderer *renderer); + +#endif // RISCB_TEXTEDITOR_H