#include #include #include #include #include "font.h" #include "decoder.h" #include "goertzel.h" #include "texteditor.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_PACKET: return "PACKET"; case TX_END: return "END"; } return "?"; } static const char *rx_state_name(RxState state) { switch (state) { case RX_SEARCH: return "SEARCH"; case RX_SYNC: return "SYNC"; case RX_MAGIC: return "MAGIC"; case RX_DATA: return "DATA"; } 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"; default: return "UNKNOWN"; } } 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.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]; // TX info on the left 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); // RX info on the right 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, 640, 8); // Clock info below RX snprintf( line, sizeof(line), "CLK E0 %08.5f E1 %08.5f R %02.1f HIST %02X/%u", decoder.clockEnergy0, decoder.clockEnergy1, decoder.clockRatio, decoder.clockHistory, decoder.clockCount); renderText(renderer, smallerFont, line, 640, 22); // 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 dataTones = MAX_FSK_TONES; const int tones = dataTones + 2; // clocks are indices 0,1 const int x = 500; const int y = 650; const int maxHeight = 192; 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); // Draw clocks first (indices 0 and 1) for (int i = 0; i < tones; i++) { int idx; if (i < 2) idx = i; // clock indices 0,1 else idx = 2 + (i - 2); // data indices start at 2 float ratio = 0.0f; if (decoder.maxEnergy > 0.0f) ratio = decoder.energies[idx] / 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 (idx < 2) SDL_SetRenderDrawColor(renderer, 255, 192, 64, 255); // clock else if ((idx - 2) & 1) SDL_SetRenderDrawColor(renderer, 80, 180, 255, 255); // 1-tone else SDL_SetRenderDrawColor(renderer, 80, 255, 140, 255); // 0-tone SDL_RenderFillRect(renderer, &barRect); } char label[128]; snprintf(label, sizeof(label), "TONE ENERGY: CLOCK0, CLOCK1, BIT0..BIT15"); 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); // Auto-start encoder for testing //startEncoder(&encoder); 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); updateDump(&receivedEditor, 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; }