a mega cool windows xp app

feat: add radio bit

dunkirk.sh 6a468a41 93d890be

verified
.cache/clangd/index/main.cpp.47C66B394CD74271.idx

This is a binary file and will not be displayed.

+1 -1
CMakeLists.txt
···
add_compile_options(-D_GLIBCXX_HAS_GTHREADS=0)
add_executable(HelloWorldApp WIN32 main.cpp)
-
target_link_libraries(HelloWorldApp user32 gdi32)
+
target_link_libraries(HelloWorldApp user32 gdi32 winmm)
# Include current directory for headers
target_include_directories(HelloWorldApp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+62
CRUSH.md
···
+
# Shortwave Development Guide
+
+
## Project Overview
+
- **Language**: C-style C++ for Win32 API compatibility
+
- **Target Platform**: Windows XP
+
- **Primary Goal**: Simple Win32 Application
+
+
## Build & Development Commands
+
- **Build**: `nix build`
+
- Compiles Win32 application
+
- **Dev Setup**: `setup-dev`
+
- Generates `compile_commands.json`
+
- **Deploy**: `deploy-to-xp`
+
- Copies executable and DLLs to XP VM
+
- **Debugging**: Use Visual Studio or WinDbg
+
- **Testing**: Manual testing on Windows XP
+
+
## Code Style Guidelines
+
+
### Formatting
+
- Use tabs for indentation
+
- Opening braces on same line
+
- Max line length: 80 characters
+
- Avoid trailing whitespace
+
+
### Naming Conventions
+
- Functions: `PascalCase` (e.g., `QRCode_Init`)
+
- Constants/Macros: `UPPER_SNAKE_CASE` (e.g., `ID_ABOUT`)
+
- Global Variables: `g_` prefix (e.g., `g_qrCode`)
+
- Avoid abbreviations
+
+
### Types & Memory
+
- Prefer standard C types: `int`, `char*`
+
- Use Win32 types: `HWND`, `LPARAM`
+
- Avoid STL
+
- Static allocation preferred
+
- No dynamic memory allocation
+
- No exceptions
+
+
### Error Handling
+
- Always check Win32 API return values
+
- Validate pointers before use
+
- Use `NULL` checks
+
- Log errors to file/console
+
- Graceful failure modes
+
+
### Imports & Headers
+
1. `<windows.h>`
+
2. Standard C headers
+
3. Project-specific headers
+
- Use include guards
+
- Minimize header dependencies
+
+
### Documentation
+
- Comment complex Win32 logic
+
- Document function parameters/returns
+
+
## Best Practices
+
- Prioritize Win32 API compatibility
+
- Minimize external dependencies
+
- Focus on performance and low resource usage
+
- Test thoroughly on target Windows XP environment
+203 -189
flake.nix
···
systems.url = "github:nix-systems/default";
};
-
outputs = inputs@{ flake-parts, systems, ... }:
+
outputs =
+
inputs@{ flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ ];
systems = import systems;
-
perSystem = { self', pkgs, ... }: {
-
packages = {
-
hello-world-app = pkgs.stdenv.mkDerivation {
-
name = "hello-world-app";
-
version = "1.0.0";
-
src = ./.;
-
-
nativeBuildInputs = with pkgs; [
-
cmake
-
pkgsCross.mingw32.buildPackages.gcc
-
];
-
-
buildInputs = with pkgs.pkgsCross.mingw32; [
-
windows.mingw_w64
-
];
-
-
configurePhase = ''
-
export CC=${pkgs.pkgsCross.mingw32.buildPackages.gcc}/bin/i686-w64-mingw32-gcc
-
export CXX=${pkgs.pkgsCross.mingw32.buildPackages.gcc}/bin/i686-w64-mingw32-g++
-
export CMAKE_SYSTEM_NAME=Windows
-
export CMAKE_C_COMPILER=$CC
-
export CMAKE_CXX_COMPILER=$CXX
-
'';
-
-
buildPhase = ''
-
export LDFLAGS="-static -static-libgcc -static-libstdc++"
-
cmake -DCMAKE_BUILD_TYPE=Release \
-
-DCMAKE_SYSTEM_NAME=Windows \
-
-DCMAKE_C_COMPILER=$CC \
-
-DCMAKE_CXX_COMPILER=$CXX \
-
-DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" .
-
make VERBOSE=1
-
'';
-
-
installPhase = ''
-
mkdir -p $out/bin
-
cp HelloWorld.exe $out/bin/
-
'';
-
};
-
-
deploy-to-xp = pkgs.writeShellScriptBin "deploy-to-xp" ''
-
XP_DIR="$HOME/Documents/xp-drive"
-
mkdir -p "$XP_DIR"
-
-
# Handle Windows file locking by using a temporary name first
-
TEMP_NAME="HelloWorld_new.exe"
-
OLD_NAME="HelloWorld_old.exe"
-
FINAL_NAME="HelloWorld.exe"
-
-
echo "Deploying to $XP_DIR..."
-
-
# Copy to temporary name first
-
if cp ${self'.packages.hello-world-app}/bin/HelloWorld.exe "$XP_DIR/$TEMP_NAME"; then
-
echo "✓ Copied new version as $TEMP_NAME"
-
-
# If the original exists, try to rename it
-
if [ -f "$XP_DIR/$FINAL_NAME" ]; then
-
if mv "$XP_DIR/$FINAL_NAME" "$XP_DIR/$OLD_NAME" 2>/dev/null; then
-
echo "✓ Backed up old version as $OLD_NAME"
+
perSystem =
+
{ self', pkgs, ... }:
+
{
+
packages = {
+
shortwave = pkgs.stdenv.mkDerivation {
+
name = "shortwave";
+
version = "1.0.0";
+
src = ./.;
+
+
nativeBuildInputs = with pkgs; [
+
cmake
+
pkgsCross.mingw32.buildPackages.gcc
+
];
+
+
buildInputs = with pkgs.pkgsCross.mingw32; [
+
windows.mingw_w64
+
];
+
+
configurePhase = ''
+
export CC=${pkgs.pkgsCross.mingw32.buildPackages.gcc}/bin/i686-w64-mingw32-gcc
+
export CXX=${pkgs.pkgsCross.mingw32.buildPackages.gcc}/bin/i686-w64-mingw32-g++
+
export CMAKE_SYSTEM_NAME=Windows
+
export CMAKE_C_COMPILER=$CC
+
export CMAKE_CXX_COMPILER=$CXX
+
'';
+
+
buildPhase = ''
+
export LDFLAGS="-static -static-libgcc -static-libstdc++"
+
cmake -DCMAKE_BUILD_TYPE=Release \
+
-DCMAKE_SYSTEM_NAME=Windows \
+
-DCMAKE_C_COMPILER=$CC \
+
-DCMAKE_CXX_COMPILER=$CXX \
+
-DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" .
+
make VERBOSE=1
+
'';
+
+
installPhase = ''
+
mkdir -p $out/bin
+
cp HelloWorld.exe $out/bin/
+
'';
+
};
+
+
deploy-to-xp = pkgs.writeShellScriptBin "deploy-to-xp" ''
+
XP_DIR="$HOME/Documents/xp-drive"
+
mkdir -p "$XP_DIR"
+
+
# Handle Windows file locking by using a temporary name first
+
TEMP_NAME="HelloWorld_new.exe"
+
OLD_NAME="HelloWorld_old.exe"
+
FINAL_NAME="HelloWorld.exe"
+
+
echo "Deploying to $XP_DIR..."
+
+
# Copy to temporary name first
+
if cp ${self'.packages.shortwave}/bin/HelloWorld.exe "$XP_DIR/$TEMP_NAME"; then
+
echo "Copied new version as $TEMP_NAME"
+
+
# If the original exists, try to rename it
+
if [ -f "$XP_DIR/$FINAL_NAME" ]; then
+
if mv "$XP_DIR/$FINAL_NAME" "$XP_DIR/$OLD_NAME" 2>/dev/null; then
+
echo "Backed up old version as $OLD_NAME"
+
else
+
echo "Warning: Could not backup old version (file may be in use)"
+
echo "Close the application on XP and try again, or manually rename files"
+
echo "New version is available as: $TEMP_NAME"
+
exit 1
+
fi
+
fi
+
+
# Move temp to final name
+
if mv "$XP_DIR/$TEMP_NAME" "$XP_DIR/$FINAL_NAME"; then
+
echo "Deployed HelloWorld.exe successfully"
+
+
# Clean up old backup if it exists
+
if [ -f "$XP_DIR/$OLD_NAME" ]; then
+
rm -f "$XP_DIR/$OLD_NAME" 2>/dev/null || echo "(Old backup file remains)"
+
fi
else
-
echo "⚠ Warning: Could not backup old version (file may be in use)"
-
echo " Close the application on XP and try again, or manually rename files"
-
echo " New version is available as: $TEMP_NAME"
+
echo "Failed to finalize deployment"
exit 1
fi
-
fi
-
-
# Move temp to final name
-
if mv "$XP_DIR/$TEMP_NAME" "$XP_DIR/$FINAL_NAME"; then
-
echo "✓ Deployed HelloWorld.exe successfully"
-
-
# Clean up old backup if it exists
-
if [ -f "$XP_DIR/$OLD_NAME" ]; then
-
rm -f "$XP_DIR/$OLD_NAME" 2>/dev/null || echo " (Old backup file remains)"
-
fi
else
-
echo "✗ Failed to finalize deployment"
+
echo "Failed to copy new version"
exit 1
fi
-
else
-
echo "✗ Failed to copy new version"
-
exit 1
-
fi
-
-
echo ""
-
echo "Deployment complete! 🎉"
-
echo "You can now run the updated application on XP"
-
'';
-
-
setup-dev = pkgs.writeShellScriptBin "setup-dev" ''
-
echo "Setting up development environment for Zed..."
-
-
# Get the proper MinGW headers - use the known path from our build
-
GCC_BASE="/nix/store/l2gk3vvpdf33jf3gnfljyyx3dgwks8zp-i686-w64-mingw32-stage-final-gcc-debug-10.3.0/i686-w64-mingw32"
-
SYS_INCLUDE="$GCC_BASE/sys-include"
-
MINGW_MAIN_INCLUDE="/nix/store/hhbkp872dkayzd2qxfhkdc4rgn393g52-mingw-w64-i686-w64-mingw32-9.0.0-dev/include"
-
MCFGTHREAD_INCLUDE="/nix/store/21c6w351iwpblnfz2m9v3ssvxcmqsz7h-mcfgthreads-i686-w64-mingw32-git-dev/include"
-
CPP_INCLUDE="$GCC_BASE/include/c++/10.3.0"
-
CPP_TARGET_INCLUDE="$CPP_INCLUDE/i686-w64-mingw32"
-
-
# Verify paths exist
-
if [ ! -f "$SYS_INCLUDE/stdlib.h" ]; then
-
echo "Error: Could not find C standard library at $SYS_INCLUDE"
-
exit 1
-
fi
-
-
if [ ! -f "$MINGW_MAIN_INCLUDE/windows.h" ]; then
-
echo "Error: Could not find MinGW headers at $MINGW_MAIN_INCLUDE"
-
exit 1
-
fi
-
-
if [ ! -f "$CPP_INCLUDE/vector" ]; then
-
echo "Error: Could not find C++ standard library at $CPP_INCLUDE"
-
exit 1
-
fi
-
-
if [ ! -f "$MCFGTHREAD_INCLUDE/mcfgthread/gthread.h" ]; then
-
echo "Error: Could not find mcfgthread headers at $MCFGTHREAD_INCLUDE"
-
exit 1
-
fi
-
-
# Create simplified .clangd config to avoid intrinsics issues
-
cat > .clangd << EOF
-
CompileFlags:
-
Add:
-
- -target
-
- i686-w64-mingw32
-
- -DWINVER=0x0501
-
- -D_WIN32_WINNT=0x0501
-
- -DWIN32_LEAN_AND_MEAN
-
- -D_WIN32
-
- -DWIN32
-
- -std=c++17
-
- -fno-builtin
-
- -D__NO_INLINE__
-
- -isystem
-
- $SYS_INCLUDE
-
- -isystem
-
- $MINGW_MAIN_INCLUDE
-
- -isystem
-
- $MCFGTHREAD_INCLUDE
-
- -isystem
-
- $CPP_INCLUDE
-
- -isystem
-
- $CPP_TARGET_INCLUDE
-
Remove:
-
- -I*/gcc/*/include
-
EOF
-
-
# Create simplified compile_commands.json
-
cat > compile_commands.json << EOF
-
[
-
{
-
"directory": "$(pwd)",
-
"command": "clang++ -target i686-w64-mingw32 -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -DWIN32_LEAN_AND_MEAN -D_WIN32 -DWIN32 -fno-builtin -isystem \"$MINGW_MAIN_INCLUDE\" -std=c++17 -c main.cpp",
-
"file": "main.cpp"
-
}
-
]
-
EOF
-
-
echo "Generated simplified .clangd config and compile_commands.json"
-
echo "Using C standard library: $SYS_INCLUDE"
-
echo "Using MinGW headers: $MINGW_MAIN_INCLUDE"
-
echo "Using mcfgthread headers: $MCFGTHREAD_INCLUDE"
-
echo "Using C++ headers: $CPP_INCLUDE"
-
echo ""
-
echo "This includes complete C/C++ standard libraries and Win32 APIs"
-
echo "Restart Zed for the changes to take effect"
-
'';
-
-
default = self'.packages.hello-world-app;
-
};
-
-
devShells = {
-
default = pkgs.mkShell {
-
buildInputs = with pkgs; [
-
cmake
-
pkgsCross.mingw32.buildPackages.gcc
-
self'.packages.deploy-to-xp
-
self'.packages.setup-dev
-
];
-
-
shellHook = ''
-
echo "Win32 development environment loaded (older toolchain)"
-
echo "Available commands:"
-
echo " nix build - Build the application"
-
echo " deploy-to-xp - Deploy to XP VM folder"
-
echo " setup-dev - Generate compile_commands.json for Zed editor"
-
echo ""
-
echo "For Zed editor support:"
-
echo " 1. Run 'setup-dev' to generate compile_commands.json"
-
echo " 2. Open project in Zed for IntelliSense support"
'';
+
+
setup-dev = pkgs.writeShellScriptBin "setup-dev" ''
+
echo "Setting up development environment for Zed..."
+
+
# Get dynamic paths from nix packages
+
GCC_BASE="${pkgs.pkgsCross.mingw32.buildPackages.gcc}/i686-w64-mingw32"
+
SYS_INCLUDE="$GCC_BASE/sys-include"
+
MINGW_MAIN_INCLUDE="${pkgs.pkgsCross.mingw32.windows.mingw_w64}/include"
+
CPP_INCLUDE="${pkgs.lib.getDev pkgs.pkgsCross.mingw32.buildPackages.gcc}/include/c++/10.3.0"
+
CPP_TARGET_INCLUDE="$CPP_INCLUDE/i686-w64-mingw32"
+
+
# Create .clangd config with correct paths
+
cat > .clangd << EOF
+
CompileFlags:
+
Add:
+
- -target
+
- i686-w64-mingw32
+
- -DWINVER=0x0501
+
- -D_WIN32_WINNT=0x0501
+
- -DWIN32_LEAN_AND_MEAN
+
- -D_WIN32
+
- -DWIN32
+
- -std=c++17
+
- -fno-builtin
+
- -isystem
+
- $SYS_INCLUDE
+
- -isystem
+
- $MINGW_MAIN_INCLUDE
+
- -isystem
+
- $CPP_INCLUDE
+
- -isystem
+
- $CPP_TARGET_INCLUDE
+
Remove:
+
- -I*/gcc/*/include
+
EOF
+
+
# Create compile_commands.json
+
cat > compile_commands.json << EOF
+
[
+
{
+
"directory": "$(pwd)",
+
"command": "i686-w64-mingw32-g++ -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -DWIN32_LEAN_AND_MEAN -D_WIN32 -DWIN32 -std=c++17 -isystem \"$SYS_INCLUDE\" -isystem \"$MINGW_MAIN_INCLUDE\" -isystem \"$CPP_INCLUDE\" -isystem \"$CPP_TARGET_INCLUDE\" -c main.cpp",
+
"file": "main.cpp"
+
}
+
]
+
EOF
+
+
echo "Generated .clangd config and compile_commands.json with include paths"
+
echo "Development environment ready for Zed editor"
+
echo "Include paths:"
+
echo " C standard library: $SYS_INCLUDE"
+
echo " MinGW headers: $MINGW_MAIN_INCLUDE"
+
echo " C++ headers: $CPP_INCLUDE"
+
'';
+
+
default = self'.packages.shortwave;
+
};
+
+
devShells = {
+
default = pkgs.mkShell {
+
buildInputs = with pkgs; [
+
cmake
+
pkgsCross.mingw32.buildPackages.gcc
+
self'.packages.deploy-to-xp
+
self'.packages.setup-dev
+
];
+
+
shellHook = ''
+
echo "Win32 development environment loaded"
+
echo "Available commands:"
+
echo " nix build - Build the application"
+
echo " deploy-to-xp - Deploy to XP VM folder"
+
echo ""
+
echo "Setting up development environment..."
+
+
# Get dynamic paths from nix packages
+
GCC_BASE="${pkgs.pkgsCross.mingw32.buildPackages.gcc}/i686-w64-mingw32"
+
SYS_INCLUDE="$GCC_BASE/sys-include"
+
MINGW_MAIN_INCLUDE="${pkgs.pkgsCross.mingw32.windows.mingw_w64}/include"
+
CPP_INCLUDE="${pkgs.lib.getDev pkgs.pkgsCross.mingw32.buildPackages.gcc}/include/c++/10.3.0"
+
CPP_TARGET_INCLUDE="$CPP_INCLUDE/i686-w64-mingw32"
+
+
# Auto-generate .clangd config with correct paths
+
cat > .clangd << EOF
+
CompileFlags:
+
Add:
+
- -target
+
- i686-w64-mingw32
+
- -DWINVER=0x0501
+
- -D_WIN32_WINNT=0x0501
+
- -DWIN32_LEAN_AND_MEAN
+
- -D_WIN32
+
- -DWIN32
+
- -std=c++17
+
- -fno-builtin
+
- -isystem
+
- $SYS_INCLUDE
+
- -isystem
+
- $MINGW_MAIN_INCLUDE
+
- -isystem
+
- $CPP_INCLUDE
+
- -isystem
+
- $CPP_TARGET_INCLUDE
+
Remove:
+
- -I*/gcc/*/include
+
EOF
+
+
cat > compile_commands.json << EOF
+
[
+
{
+
"directory": "$(pwd)",
+
"command": "i686-w64-mingw32-g++ -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -DWIN32_LEAN_AND_MEAN -D_WIN32 -DWIN32 -std=c++17 -isystem \"$SYS_INCLUDE\" -isystem \"$MINGW_MAIN_INCLUDE\" -isystem \"$CPP_INCLUDE\" -isystem \"$CPP_TARGET_INCLUDE\" -c main.cpp",
+
"file": "main.cpp"
+
}
+
]
+
EOF
+
+
echo "✓ Generated .clangd config and compile_commands.json with include paths"
+
echo "✓ Development environment ready for Zed editor"
+
'';
+
};
};
};
-
};
};
-
}
+
}
+712 -177
main.cpp
···
#include <windows.h>
-
#include "qr.h"
+
#include <math.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <mmsystem.h>
+
+
#pragma comment(lib, "winmm.lib")
#define ID_ABOUT 1001
#define ID_EXIT 1002
-
#define ID_GENERATE_QR 1003
+
+
// Radio control IDs
+
#define ID_TUNING_DIAL 2001
+
#define ID_VOLUME_KNOB 2002
+
#define ID_POWER_BUTTON 2003
+
+
// Radio station data
+
typedef struct {
+
float frequency;
+
char name[64];
+
char description[128];
+
} RadioStation;
+
+
RadioStation g_stations[] = {
+
{14.230f, "BBC World Service", "International news and current affairs"},
+
{15.770f, "Radio Moscow", "Russian international broadcast"},
+
{17.895f, "Voice of America", "American international news"},
+
{21.500f, "Radio Australia", "Australian international service"},
+
{25.820f, "Radio Canada", "Canadian international broadcast"},
+
{28.400f, "Amateur Radio", "Ham radio operators"},
+
{31.100f, "Time Signal", "Atomic clock time broadcast"}
+
};
+
+
#define NUM_STATIONS (sizeof(g_stations) / sizeof(RadioStation))
+
#define SAMPLE_RATE 44100
+
#define BITS_PER_SAMPLE 16
+
#define CHANNELS 1
+
#define BUFFER_SIZE 4410 // 0.1 seconds of audio
+
#define NUM_BUFFERS 4
+
+
// Audio state
+
typedef struct {
+
HWAVEOUT hWaveOut;
+
WAVEHDR waveHeaders[NUM_BUFFERS];
+
short audioBuffers[NUM_BUFFERS][BUFFER_SIZE];
+
int currentBuffer;
+
int isPlaying;
+
float staticVolume;
+
float radioVolume;
+
} AudioState;
+
+
// Radio state
+
typedef struct {
+
float frequency;
+
float volume;
+
int power;
+
int signalStrength;
+
int isDraggingDial;
+
int isDraggingVolume;
+
} RadioState;
+
+
RadioState g_radio = {14.230f, 0.5f, 0, 0, 0, 0};
+
AudioState g_audio = {0};
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
void DrawRadioInterface(HDC hdc, RECT* rect);
+
void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency);
+
void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency);
+
void DrawSignalMeter(HDC hdc, int x, int y, int strength);
+
void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume);
+
void DrawPowerButton(HDC hdc, int x, int y, int radius, int power);
+
int IsPointInCircle(int px, int py, int cx, int cy, int radius);
+
float GetAngleFromPoint(int px, int py, int cx, int cy);
+
void UpdateFrequencyFromMouse(int mouseX, int mouseY);
+
void UpdateVolumeFromMouse(int mouseX, int mouseY);
-
// Global QR code instance - static allocation, no new/delete
-
QRCode g_qrCode;
-
BOOL g_hasQrCode = FALSE;
-
char g_qrText[256] = "Hello World!";
+
// Audio functions
+
int InitializeAudio();
+
void CleanupAudio();
+
void GenerateStaticBuffer(short* buffer, int size);
+
void FillAudioBuffer(short* buffer, int size);
+
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
+
void StartAudio();
+
void StopAudio();
+
RadioStation* FindNearestStation(float frequency);
+
float GetStationSignalStrength(RadioStation* station, float currentFreq);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
-
const char* CLASS_NAME = "HelloWorldWindow";
-
-
WNDCLASS wc = {};
-
wc.lpfnWndProc = WindowProc;
-
wc.hInstance = hInstance;
-
wc.lpszClassName = CLASS_NAME;
-
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
-
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
-
-
RegisterClass(&wc);
-
-
HWND hwnd = CreateWindow(
-
CLASS_NAME,
-
"Hello World App",
-
WS_OVERLAPPEDWINDOW,
-
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
-
NULL,
-
NULL,
-
hInstance,
-
NULL
-
);
-
-
if (hwnd == NULL) {
-
return 0;
-
}
-
-
// Create menu
-
HMENU hMenu = CreateMenu();
-
-
// Tools menu
-
HMENU hToolsMenu = CreatePopupMenu();
-
AppendMenu(hToolsMenu, MF_STRING, ID_GENERATE_QR, "&Generate QR Pattern");
-
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hToolsMenu, "&Tools");
-
-
// Help menu
-
HMENU hHelpMenu = CreatePopupMenu();
-
AppendMenu(hHelpMenu, MF_STRING, ID_ABOUT, "&About");
-
AppendMenu(hHelpMenu, MF_SEPARATOR, 0, NULL);
-
AppendMenu(hHelpMenu, MF_STRING, ID_EXIT, "E&xit");
-
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hHelpMenu, "&Help");
-
-
SetMenu(hwnd, hMenu);
-
-
ShowWindow(hwnd, nCmdShow);
-
UpdateWindow(hwnd);
-
-
MSG msg = {};
-
while (GetMessage(&msg, NULL, 0, 0)) {
-
TranslateMessage(&msg);
-
DispatchMessage(&msg);
-
}
-
-
return 0;
+
const char* CLASS_NAME = "ShortwaveRadio";
+
+
WNDCLASS wc = {};
+
wc.lpfnWndProc = WindowProc;
+
wc.hInstance = hInstance;
+
wc.lpszClassName = CLASS_NAME;
+
wc.hbrBackground = CreateSolidBrush(RGB(101, 67, 33)); // Wood grain brown
+
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+
+
RegisterClass(&wc);
+
+
HWND hwnd = CreateWindow(
+
CLASS_NAME,
+
"Shortwave Radio Tuner",
+
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, // Fixed size
+
CW_USEDEFAULT, CW_USEDEFAULT, 600, 450,
+
NULL,
+
NULL,
+
hInstance,
+
NULL
+
);
+
+
if (hwnd == NULL) {
+
return 0;
+
}
+
+
// Initialize audio system
+
if (InitializeAudio() != 0) {
+
MessageBox(hwnd, "Failed to initialize audio system", "Error", MB_OK | MB_ICONERROR);
+
return 0;
+
}
+
+
// Audio starts when power button is pressed
+
+
// Create menu
+
HMENU hMenu = CreateMenu();
+
+
// Radio menu
+
HMENU hRadioMenu = CreatePopupMenu();
+
AppendMenu(hRadioMenu, MF_STRING, ID_ABOUT, "&About");
+
AppendMenu(hRadioMenu, MF_SEPARATOR, 0, NULL);
+
AppendMenu(hRadioMenu, MF_STRING, ID_EXIT, "E&xit");
+
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hRadioMenu, "&Radio");
+
+
SetMenu(hwnd, hMenu);
+
+
ShowWindow(hwnd, nCmdShow);
+
UpdateWindow(hwnd);
+
+
MSG msg = {};
+
while (GetMessage(&msg, NULL, 0, 0)) {
+
TranslateMessage(&msg);
+
DispatchMessage(&msg);
+
}
+
+
// Cleanup audio
+
StopAudio();
+
CleanupAudio();
+
+
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
-
switch (uMsg) {
-
case WM_SIZE:
-
// Trigger repaint when window is resized
-
InvalidateRect(hwnd, NULL, TRUE);
-
return 0;
-
-
case WM_DESTROY:
-
// No cleanup needed for static allocation
-
PostQuitMessage(0);
-
return 0;
-
-
case WM_PAINT: {
-
PAINTSTRUCT ps;
-
HDC hdc = BeginPaint(hwnd, &ps);
-
-
RECT rect;
-
GetClientRect(hwnd, &rect);
-
-
if (g_hasQrCode) {
-
// Draw QR code - calculate size based on window
-
int qrSize = QRCode_GetSize();
-
int moduleSize = 8;
-
int qrPixelSize = qrSize * moduleSize;
-
-
// Center horizontally and vertically with some padding
-
int startX = (rect.right - qrPixelSize) / 2;
-
int startY = (rect.bottom - qrPixelSize - 80) / 2; // Leave space for text
-
-
QRCode_DrawToHDC(&g_qrCode, hdc, startX, startY, moduleSize);
-
-
// Draw text below QR code
-
SetTextAlign(hdc, TA_CENTER);
-
SetBkMode(hdc, TRANSPARENT);
-
int textLen = 0;
-
while (g_qrText[textLen]) textLen++; // Calculate length
-
TextOut(hdc, rect.right / 2, startY + qrPixelSize + 20,
-
g_qrText, textLen);
-
-
// Add disclaimer
-
const char* disclaimer = "(Visual demo - not scannable)";
-
int disclaimerLen = 0;
-
while (disclaimer[disclaimerLen]) disclaimerLen++;
-
TextOut(hdc, rect.right / 2, startY + qrPixelSize + 40,
-
disclaimer, disclaimerLen);
-
} else {
-
// Default view - center in current window size
-
SetTextAlign(hdc, TA_CENTER);
-
SetBkMode(hdc, TRANSPARENT);
-
int centerY = rect.bottom / 2;
-
TextOut(hdc, rect.right / 2, centerY - 10, "Hello World!", 12);
-
TextOut(hdc, rect.right / 2, centerY + 10,
-
"Use Tools > Generate QR Pattern", 32);
-
}
-
-
EndPaint(hwnd, &ps);
-
return 0;
-
}
-
-
case WM_COMMAND:
-
switch (LOWORD(wParam)) {
-
case ID_ABOUT: {
-
const char* aboutText = "Hello World App\n\n"
-
"Version: 1.0.0\n"
-
"Built by: Kieran Klukas\n\n"
-
"A simple Win32 application\n"
-
"compatible with Windows XP\n\n"
-
"Features:\n"
-
"- QR Pattern Generation (visual demo)\n"
-
"- XP Compatible Design\n"
-
"- Pure Win32 API";
-
MessageBox(hwnd, aboutText, "About Hello World App",
-
MB_OK | MB_ICONINFORMATION);
-
break;
-
}
-
case ID_GENERATE_QR: {
-
// Simple input dialog using InputBox simulation
-
if (MessageBox(hwnd, "Generate QR code pattern for current text?\n\n(Note: This creates a visual QR-like pattern for demo purposes,\nnot a scannable QR code)\n\nClick OK to use default text,\nor Cancel to cycle through presets.",
-
"Generate QR Pattern", MB_OKCANCEL | MB_ICONQUESTION) == IDCANCEL) {
-
-
// For now, use a simple preset - in a real app you'd want a proper input dialog
-
const char* presets[] = {
-
"Hello World!",
-
"https://github.com/taciturnaxolotl/shortwave",
-
"Made with love by Kieran Klukas",
-
"Windows XP Forever!",
-
"QR codes are cool!"
-
};
-
-
static int presetIndex = 0;
-
const char* selectedText = presets[presetIndex % 5];
-
presetIndex++;
-
-
// Copy selected text to global buffer
-
int i = 0;
-
while (selectedText[i] && i < 255) {
-
g_qrText[i] = selectedText[i];
-
i++;
-
}
-
g_qrText[i] = '\0';
-
}
-
-
// Generate new QR code - no new/delete, just reinitialize
-
QRCode_Init(&g_qrCode, g_qrText);
-
g_hasQrCode = TRUE;
-
-
// Refresh the window
-
InvalidateRect(hwnd, NULL, TRUE);
-
break;
-
}
-
case ID_EXIT:
-
PostQuitMessage(0);
-
break;
-
}
-
return 0;
-
}
-
-
return DefWindowProc(hwnd, uMsg, wParam, lParam);
-
}
+
switch (uMsg) {
+
case WM_SIZE:
+
InvalidateRect(hwnd, NULL, TRUE);
+
return 0;
+
+
case WM_DESTROY:
+
PostQuitMessage(0);
+
return 0;
+
+
case WM_PAINT: {
+
PAINTSTRUCT ps;
+
HDC hdc = BeginPaint(hwnd, &ps);
+
+
RECT rect;
+
GetClientRect(hwnd, &rect);
+
+
DrawRadioInterface(hdc, &rect);
+
+
EndPaint(hwnd, &ps);
+
return 0;
+
}
+
+
case WM_LBUTTONDOWN: {
+
int mouseX = LOWORD(lParam);
+
int mouseY = HIWORD(lParam);
+
+
// Check if clicking on tuning dial
+
if (IsPointInCircle(mouseX, mouseY, 150, 200, 60)) {
+
g_radio.isDraggingDial = 1;
+
SetCapture(hwnd);
+
UpdateFrequencyFromMouse(mouseX, mouseY);
+
InvalidateRect(hwnd, NULL, TRUE);
+
}
+
// Check if clicking on volume knob
+
else if (IsPointInCircle(mouseX, mouseY, 350, 200, 30)) {
+
g_radio.isDraggingVolume = 1;
+
SetCapture(hwnd);
+
UpdateVolumeFromMouse(mouseX, mouseY);
+
InvalidateRect(hwnd, NULL, TRUE);
+
}
+
// Check if clicking on power button
+
else if (IsPointInCircle(mouseX, mouseY, 500, 120, 25)) {
+
g_radio.power = !g_radio.power;
+
if (g_radio.power) {
+
StartAudio();
+
} else {
+
StopAudio();
+
}
+
InvalidateRect(hwnd, NULL, TRUE);
+
}
+
return 0;
+
}
+
+
case WM_LBUTTONUP: {
+
if (g_radio.isDraggingDial || g_radio.isDraggingVolume) {
+
g_radio.isDraggingDial = 0;
+
g_radio.isDraggingVolume = 0;
+
ReleaseCapture();
+
}
+
return 0;
+
}
+
+
case WM_MOUSEMOVE: {
+
if (g_radio.isDraggingDial) {
+
int mouseX = LOWORD(lParam);
+
int mouseY = HIWORD(lParam);
+
UpdateFrequencyFromMouse(mouseX, mouseY);
+
InvalidateRect(hwnd, NULL, TRUE);
+
}
+
else if (g_radio.isDraggingVolume) {
+
int mouseX = LOWORD(lParam);
+
int mouseY = HIWORD(lParam);
+
UpdateVolumeFromMouse(mouseX, mouseY);
+
InvalidateRect(hwnd, NULL, TRUE);
+
}
+
return 0;
+
}
+
+
case WM_COMMAND:
+
switch (LOWORD(wParam)) {
+
case ID_ABOUT: {
+
const char* aboutText = "Shortwave Radio Tuner\n\n"
+
"Version: 1.0.0\n"
+
"Built for Rewind V2 Hackathon\n\n"
+
"A vintage shortwave radio simulator\n"
+
"compatible with Windows XP\n\n"
+
"Features:\n"
+
"- Realistic tuning interface\n"
+
"- Internet radio streaming\n"
+
"- Authentic static noise";
+
MessageBox(hwnd, aboutText, "About Shortwave Radio",
+
MB_OK | MB_ICONINFORMATION);
+
break;
+
}
+
case ID_EXIT:
+
PostQuitMessage(0);
+
break;
+
}
+
return 0;
+
}
+
+
return DefWindowProc(hwnd, uMsg, wParam, lParam);
+
}
+
+
void DrawRadioInterface(HDC hdc, RECT* rect) {
+
// Create vintage radio background
+
HBRUSH woodBrush = CreateSolidBrush(RGB(101, 67, 33));
+
FillRect(hdc, rect, woodBrush);
+
DeleteObject(woodBrush);
+
+
// Draw radio panel (darker inset)
+
RECT panel = {50, 50, rect->right - 50, rect->bottom - 50};
+
HBRUSH panelBrush = CreateSolidBrush(RGB(80, 50, 25));
+
FillRect(hdc, &panel, panelBrush);
+
DeleteObject(panelBrush);
+
+
// Draw panel border (raised effect)
+
HPEN lightPen = CreatePen(PS_SOLID, 2, RGB(140, 100, 60));
+
HPEN darkPen = CreatePen(PS_SOLID, 2, RGB(40, 25, 15));
+
+
SelectObject(hdc, lightPen);
+
MoveToEx(hdc, panel.left, panel.bottom, NULL);
+
LineTo(hdc, panel.left, panel.top);
+
LineTo(hdc, panel.right, panel.top);
+
+
SelectObject(hdc, darkPen);
+
LineTo(hdc, panel.right, panel.bottom);
+
LineTo(hdc, panel.left, panel.bottom);
+
+
DeleteObject(lightPen);
+
DeleteObject(darkPen);
+
+
// Draw frequency display
+
DrawFrequencyDisplay(hdc, 200, 80, g_radio.frequency);
+
+
// Draw tuning dial
+
DrawTuningDial(hdc, 150, 200, 60, g_radio.frequency);
+
+
// Draw volume knob
+
DrawVolumeKnob(hdc, 350, 200, 30, g_radio.volume);
+
+
// Draw signal meter
+
DrawSignalMeter(hdc, 450, 150, g_radio.signalStrength);
+
+
// Draw power button
+
DrawPowerButton(hdc, 500, 120, 25, g_radio.power);
+
+
// Draw station info if tuned to a station
+
RadioStation* currentStation = FindNearestStation(g_radio.frequency);
+
if (currentStation && g_radio.signalStrength > 30) {
+
RECT stationRect = {80, 320, 520, 360};
+
HBRUSH stationBrush = CreateSolidBrush(RGB(0, 0, 0));
+
FillRect(hdc, &stationRect, stationBrush);
+
DeleteObject(stationBrush);
+
+
HPEN stationPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
+
SelectObject(hdc, stationPen);
+
Rectangle(hdc, stationRect.left, stationRect.top, stationRect.right, stationRect.bottom);
+
DeleteObject(stationPen);
+
+
SetTextColor(hdc, RGB(0, 255, 0));
+
HFONT stationFont = CreateFont(12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
+
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
+
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
+
DEFAULT_PITCH | FF_SWISS, "Arial");
+
SelectObject(hdc, stationFont);
+
+
char stationText[256];
+
sprintf(stationText, "%.3f MHz - %s: %s",
+
currentStation->frequency, currentStation->name, currentStation->description);
+
+
SetTextAlign(hdc, TA_LEFT);
+
TextOut(hdc, stationRect.left + 5, stationRect.top + 5, stationText, strlen(stationText));
+
+
DeleteObject(stationFont);
+
}
+
+
// Draw labels
+
SetBkMode(hdc, TRANSPARENT);
+
SetTextColor(hdc, RGB(255, 255, 255));
+
HFONT font = CreateFont(14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
+
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
+
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
+
DEFAULT_PITCH | FF_SWISS, "Arial");
+
SelectObject(hdc, font);
+
+
TextOut(hdc, 180, 300, "TUNING", 6);
+
TextOut(hdc, 330, 260, "VOLUME", 6);
+
TextOut(hdc, 430, 200, "SIGNAL", 6);
+
TextOut(hdc, 485, 160, "POWER", 5);
+
+
DeleteObject(font);
+
}
+
+
void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency) {
+
// Draw display background (black LCD style)
+
RECT display = {x - 80, y - 20, x + 80, y + 20};
+
HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
+
FillRect(hdc, &display, blackBrush);
+
DeleteObject(blackBrush);
+
+
// Draw display border
+
HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
+
SelectObject(hdc, borderPen);
+
Rectangle(hdc, display.left, display.top, display.right, display.bottom);
+
DeleteObject(borderPen);
+
+
// Draw frequency text
+
char freqText[32];
+
sprintf(freqText, "%.3f MHz", frequency);
+
+
SetBkMode(hdc, TRANSPARENT);
+
SetTextColor(hdc, RGB(0, 255, 0)); // Green LCD color
+
HFONT lcdFont = CreateFont(16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
+
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
+
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
+
FIXED_PITCH | FF_MODERN, "Courier New");
+
SelectObject(hdc, lcdFont);
+
+
SetTextAlign(hdc, TA_CENTER);
+
TextOut(hdc, x, y - 8, freqText, strlen(freqText));
+
+
DeleteObject(lcdFont);
+
}
+
+
void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency) {
+
// Draw dial background
+
HBRUSH dialBrush = CreateSolidBrush(RGB(160, 120, 80));
+
SelectObject(hdc, dialBrush);
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
+
DeleteObject(dialBrush);
+
+
// Draw dial border
+
HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(60, 40, 20));
+
SelectObject(hdc, borderPen);
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
+
DeleteObject(borderPen);
+
+
// Draw frequency markings
+
SetTextColor(hdc, RGB(0, 0, 0));
+
HFONT smallFont = CreateFont(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
+
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
+
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
+
DEFAULT_PITCH | FF_SWISS, "Arial");
+
SelectObject(hdc, smallFont);
+
SetTextAlign(hdc, TA_CENTER);
+
+
for (int i = 0; i < 12; i++) {
+
float angle = (float)i * 3.14159f / 6.0f;
+
int markX = x + (int)((radius - 15) * cos(angle));
+
int markY = y + (int)((radius - 15) * sin(angle));
+
+
char mark[8];
+
sprintf(mark, "%d", 10 + i * 2);
+
TextOut(hdc, markX, markY - 5, mark, strlen(mark));
+
}
+
+
// Draw pointer based on frequency
+
float angle = (frequency - 10.0f) / 24.0f * 3.14159f;
+
int pointerX = x + (int)((radius - 10) * cos(angle));
+
int pointerY = y + (int)((radius - 10) * sin(angle));
+
+
HPEN pointerPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
+
SelectObject(hdc, pointerPen);
+
MoveToEx(hdc, x, y, NULL);
+
LineTo(hdc, pointerX, pointerY);
+
DeleteObject(pointerPen);
+
+
DeleteObject(smallFont);
+
}
+
+
void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume) {
+
// Draw knob background
+
HBRUSH knobBrush = CreateSolidBrush(RGB(140, 100, 60));
+
SelectObject(hdc, knobBrush);
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
+
DeleteObject(knobBrush);
+
+
// Draw knob border
+
HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(60, 40, 20));
+
SelectObject(hdc, borderPen);
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
+
DeleteObject(borderPen);
+
+
// Draw volume indicator
+
float angle = volume * 3.14159f * 1.5f - 3.14159f * 0.75f;
+
int indicatorX = x + (int)((radius - 5) * cos(angle));
+
int indicatorY = y + (int)((radius - 5) * sin(angle));
+
+
HPEN indicatorPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
+
SelectObject(hdc, indicatorPen);
+
MoveToEx(hdc, x, y, NULL);
+
LineTo(hdc, indicatorX, indicatorY);
+
DeleteObject(indicatorPen);
+
}
+
+
void DrawSignalMeter(HDC hdc, int x, int y, int strength) {
+
// Draw meter background
+
RECT meter = {x, y, x + 80, y + 20};
+
HBRUSH meterBrush = CreateSolidBrush(RGB(0, 0, 0));
+
FillRect(hdc, &meter, meterBrush);
+
DeleteObject(meterBrush);
+
+
// Draw meter border
+
HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
+
SelectObject(hdc, borderPen);
+
Rectangle(hdc, meter.left, meter.top, meter.right, meter.bottom);
+
DeleteObject(borderPen);
+
+
// Draw signal bars
+
int barWidth = 8;
+
int numBars = strength / 10;
+
for (int i = 0; i < numBars && i < 10; i++) {
+
RECT bar = {x + 2 + i * barWidth, y + 2,
+
x + 2 + (i + 1) * barWidth - 1, y + 18};
+
+
COLORREF barColor;
+
if (i < 3) barColor = RGB(0, 255, 0); // Green
+
else if (i < 7) barColor = RGB(255, 255, 0); // Yellow
+
else barColor = RGB(255, 0, 0); // Red
+
+
HBRUSH barBrush = CreateSolidBrush(barColor);
+
FillRect(hdc, &bar, barBrush);
+
DeleteObject(barBrush);
+
}
+
}
+
+
void DrawPowerButton(HDC hdc, int x, int y, int radius, int power) {
+
// Draw button background
+
COLORREF buttonColor = power ? RGB(255, 0, 0) : RGB(100, 100, 100);
+
HBRUSH buttonBrush = CreateSolidBrush(buttonColor);
+
SelectObject(hdc, buttonBrush);
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
+
DeleteObject(buttonBrush);
+
+
// Draw button border
+
HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(60, 60, 60));
+
SelectObject(hdc, borderPen);
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
+
DeleteObject(borderPen);
+
+
// Draw power symbol
+
if (power) {
+
HPEN symbolPen = CreatePen(PS_SOLID, 3, RGB(255, 255, 255));
+
SelectObject(hdc, symbolPen);
+
+
// Draw power symbol (circle with line)
+
Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6);
+
MoveToEx(hdc, x, y - 10, NULL);
+
LineTo(hdc, x, y - 2);
+
+
DeleteObject(symbolPen);
+
}
+
}
+
+
int IsPointInCircle(int px, int py, int cx, int cy, int radius) {
+
int dx = px - cx;
+
int dy = py - cy;
+
return (dx * dx + dy * dy) <= (radius * radius);
+
}
+
+
float GetAngleFromPoint(int px, int py, int cx, int cy) {
+
return atan2((float)(py - cy), (float)(px - cx));
+
}
+
+
void UpdateFrequencyFromMouse(int mouseX, int mouseY) {
+
float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200);
+
+
// Convert angle to frequency (10-34 MHz range)
+
// Normalize angle from -PI to PI to 0-1 range
+
float normalizedAngle = (angle + 3.14159f) / (2.0f * 3.14159f);
+
+
// Map to frequency range
+
g_radio.frequency = 10.0f + normalizedAngle * 24.0f;
+
+
// Clamp frequency
+
if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
+
if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
+
+
// Calculate signal strength based on nearest station
+
RadioStation* nearestStation = FindNearestStation(g_radio.frequency);
+
if (nearestStation) {
+
g_radio.signalStrength = (int)(GetStationSignalStrength(nearestStation, g_radio.frequency) * 100.0f);
+
} else {
+
g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
+
}
+
+
if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
+
if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
+
}
+
+
void UpdateVolumeFromMouse(int mouseX, int mouseY) {
+
float angle = GetAngleFromPoint(mouseX, mouseY, 350, 200);
+
+
// Convert angle to volume (0-1 range)
+
// Map from -135 degrees to +135 degrees
+
float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
+
+
g_radio.volume = normalizedAngle;
+
+
// Clamp volume
+
if (g_radio.volume < 0.0f) g_radio.volume = 0.0f;
+
if (g_radio.volume > 1.0f) g_radio.volume = 1.0f;
+
}
+
+
int InitializeAudio() {
+
WAVEFORMATEX waveFormat;
+
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
+
waveFormat.nChannels = CHANNELS;
+
waveFormat.nSamplesPerSec = SAMPLE_RATE;
+
waveFormat.wBitsPerSample = BITS_PER_SAMPLE;
+
waveFormat.nBlockAlign = (waveFormat.nChannels * waveFormat.wBitsPerSample) / 8;
+
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
+
waveFormat.cbSize = 0;
+
+
MMRESULT result = waveOutOpen(&g_audio.hWaveOut, WAVE_MAPPER, &waveFormat,
+
(DWORD_PTR)WaveOutProc, 0, CALLBACK_FUNCTION);
+
+
if (result != MMSYSERR_NOERROR) {
+
return -1;
+
}
+
+
// Initialize audio buffers
+
for (int i = 0; i < NUM_BUFFERS; i++) {
+
memset(&g_audio.waveHeaders[i], 0, sizeof(WAVEHDR));
+
g_audio.waveHeaders[i].lpData = (LPSTR)g_audio.audioBuffers[i];
+
g_audio.waveHeaders[i].dwBufferLength = BUFFER_SIZE * sizeof(short);
+
g_audio.waveHeaders[i].dwFlags = 0;
+
+
result = waveOutPrepareHeader(g_audio.hWaveOut, &g_audio.waveHeaders[i], sizeof(WAVEHDR));
+
if (result != MMSYSERR_NOERROR) {
+
return -1;
+
}
+
}
+
+
g_audio.currentBuffer = 0;
+
g_audio.isPlaying = 0;
+
g_audio.staticVolume = 0.3f;
+
g_audio.radioVolume = 0.0f;
+
+
return 0;
+
}
+
+
void CleanupAudio() {
+
if (g_audio.hWaveOut) {
+
waveOutReset(g_audio.hWaveOut);
+
+
for (int i = 0; i < NUM_BUFFERS; i++) {
+
waveOutUnprepareHeader(g_audio.hWaveOut, &g_audio.waveHeaders[i], sizeof(WAVEHDR));
+
}
+
+
waveOutClose(g_audio.hWaveOut);
+
g_audio.hWaveOut = NULL;
+
}
+
}
+
+
void GenerateStaticBuffer(short* buffer, int size) {
+
for (int i = 0; i < size; i++) {
+
// Generate white noise
+
float noise = ((float)rand() / RAND_MAX) * 2.0f - 1.0f;
+
+
// Apply volume and convert to 16-bit
+
float volume = g_audio.staticVolume * g_radio.volume;
+
buffer[i] = (short)(noise * volume * 32767.0f);
+
}
+
}
+
+
void FillAudioBuffer(short* buffer, int size) {
+
// Only generate audio if radio is powered on
+
if (!g_radio.power) {
+
memset(buffer, 0, size * sizeof(short));
+
return;
+
}
+
+
// Generate static
+
GenerateStaticBuffer(buffer, size);
+
+
// Find nearest station and mix in signal
+
RadioStation* station = FindNearestStation(g_radio.frequency);
+
if (station && g_radio.signalStrength > 20) {
+
float signalStrength = GetStationSignalStrength(station, g_radio.frequency);
+
float radioVolume = signalStrength * g_radio.volume * 0.7f;
+
+
for (int i = 0; i < size; i++) {
+
// Generate different tones for different stations
+
float time = (float)i / SAMPLE_RATE;
+
float tone1 = sin(2.0f * 3.14159f * (station->frequency * 50.0f) * time);
+
float tone2 = sin(2.0f * 3.14159f * (station->frequency * 75.0f) * time) * 0.5f;
+
float tone = (tone1 + tone2) * 0.5f;
+
+
// Add some modulation to simulate voice/music
+
float modulation = sin(2.0f * 3.14159f * 3.0f * time) * 0.3f + 0.7f;
+
tone *= modulation;
+
+
// Mix tone with existing static
+
float mixed = (float)buffer[i] / 32767.0f;
+
mixed = mixed * (1.0f - radioVolume) + tone * radioVolume;
+
+
buffer[i] = (short)(mixed * 32767.0f);
+
}
+
}
+
}
+
+
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
+
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
+
if (uMsg == WOM_DONE && g_audio.isPlaying) {
+
WAVEHDR* waveHeader = (WAVEHDR*)dwParam1;
+
+
// Refill the buffer
+
FillAudioBuffer((short*)waveHeader->lpData, BUFFER_SIZE);
+
+
// Queue the buffer for playback
+
waveOutWrite(g_audio.hWaveOut, waveHeader, sizeof(WAVEHDR));
+
}
+
}
+
+
void StartAudio() {
+
if (!g_audio.isPlaying) {
+
g_audio.isPlaying = 1;
+
+
// Fill and queue all buffers
+
for (int i = 0; i < NUM_BUFFERS; i++) {
+
FillAudioBuffer(g_audio.audioBuffers[i], BUFFER_SIZE);
+
waveOutWrite(g_audio.hWaveOut, &g_audio.waveHeaders[i], sizeof(WAVEHDR));
+
}
+
}
+
}
+
+
void StopAudio() {
+
if (g_audio.isPlaying) {
+
g_audio.isPlaying = 0;
+
waveOutReset(g_audio.hWaveOut);
+
}
+
}
+
+
RadioStation* FindNearestStation(float frequency) {
+
RadioStation* nearest = NULL;
+
float minDistance = 999.0f;
+
+
for (int i = 0; i < NUM_STATIONS; i++) {
+
float distance = fabs(g_stations[i].frequency - frequency);
+
if (distance < minDistance) {
+
minDistance = distance;
+
nearest = &g_stations[i];
+
}
+
}
+
+
// Only return station if we're close enough (within 0.5 MHz)
+
if (minDistance <= 0.5f) {
+
return nearest;
+
}
+
+
return NULL;
+
}
+
+
float GetStationSignalStrength(RadioStation* station, float currentFreq) {
+
if (!station) return 0.0f;
+
+
float distance = fabs(station->frequency - currentFreq);
+
+
// Signal strength drops off with distance from exact frequency
+
if (distance < 0.05f) {
+
return 0.9f; // Very strong signal
+
} else if (distance < 0.1f) {
+
return 0.7f; // Strong signal
+
} else if (distance < 0.2f) {
+
return 0.5f; // Medium signal
+
} else if (distance < 0.5f) {
+
return 0.2f; // Weak signal
+
}
+
+
return 0.0f; // No signal
+
}
-176
qr.h
···
-
#pragma once
-
#include <windows.h>
-
-
#define QR_SIZE 21
-
#define MAX_TEXT_LEN 256
-
-
// Pure C struct for QR code - no C++ classes
-
typedef struct {
-
BOOL modules[QR_SIZE][QR_SIZE];
-
char text[MAX_TEXT_LEN];
-
} QRCode;
-
-
// Function prototypes
-
void QRCode_Init(QRCode* qr, const char* inputText);
-
void QRCode_GeneratePattern(QRCode* qr);
-
void QRCode_AddFinderPattern(QRCode* qr, int x, int y);
-
BOOL QRCode_IsReserved(int x, int y);
-
void QRCode_DrawToHDC(QRCode* qr, HDC hdc, int startX, int startY, int moduleSize);
-
int QRCode_GetSize(void);
-
const char* QRCode_GetText(QRCode* qr);
-
-
// Implementation
-
void QRCode_Init(QRCode* qr, const char* inputText) {
-
int x, y, i;
-
-
// Initialize modules array
-
for (y = 0; y < QR_SIZE; y++) {
-
for (x = 0; x < QR_SIZE; x++) {
-
qr->modules[y][x] = FALSE;
-
}
-
}
-
-
// Copy text (safe copy)
-
i = 0;
-
while (inputText[i] && i < MAX_TEXT_LEN - 1) {
-
qr->text[i] = inputText[i];
-
i++;
-
}
-
qr->text[i] = '\0';
-
-
// Generate pattern
-
QRCode_GeneratePattern(qr);
-
}
-
-
void QRCode_GeneratePattern(QRCode* qr) {
-
int i, x, y;
-
unsigned int hash = 0;
-
unsigned char textBytes[MAX_TEXT_LEN];
-
int textLen = 0;
-
-
// Add finder patterns (corners)
-
QRCode_AddFinderPattern(qr, 0, 0);
-
QRCode_AddFinderPattern(qr, QR_SIZE - 7, 0);
-
QRCode_AddFinderPattern(qr, 0, QR_SIZE - 7);
-
-
// Add timing patterns
-
for (i = 8; i < QR_SIZE - 8; i++) {
-
qr->modules[6][i] = (i % 2 == 0) ? TRUE : FALSE;
-
qr->modules[i][6] = (i % 2 == 0) ? TRUE : FALSE;
-
}
-
-
// Convert text to bytes and calculate length
-
while (qr->text[textLen] && textLen < MAX_TEXT_LEN - 1) {
-
textBytes[textLen] = (unsigned char)qr->text[textLen];
-
textLen++;
-
}
-
-
// Add format information (fake but realistic looking)
-
// These would normally encode error correction level and mask pattern
-
qr->modules[8][0] = TRUE;
-
qr->modules[8][1] = FALSE;
-
qr->modules[8][2] = TRUE;
-
qr->modules[8][3] = TRUE;
-
qr->modules[8][4] = FALSE;
-
qr->modules[8][5] = TRUE;
-
-
// Add data in a more realistic zigzag pattern
-
int bitIndex = 0;
-
BOOL upward = TRUE;
-
-
for (x = QR_SIZE - 1; x > 0; x -= 2) {
-
if (x == 6) x--; // Skip timing column
-
-
for (i = 0; i < QR_SIZE; i++) {
-
y = upward ? (QR_SIZE - 1 - i) : i;
-
-
// Fill two columns (right to left)
-
for (int col = 0; col < 2; col++) {
-
int currentX = x - col;
-
if (currentX >= 0 && !QRCode_IsReserved(currentX, y)) {
-
// Use text data in a more structured way
-
BOOL bit = FALSE;
-
if (bitIndex < textLen * 8) {
-
int byteIndex = bitIndex / 8;
-
int bitPos = 7 - (bitIndex % 8);
-
bit = (textBytes[byteIndex] >> bitPos) & 1;
-
bitIndex++;
-
} else {
-
// Padding pattern
-
bit = ((currentX + y) % 3 == 0) ? TRUE : FALSE;
-
}
-
qr->modules[y][currentX] = bit;
-
}
-
}
-
}
-
upward = !upward;
-
}
-
}
-
-
void QRCode_AddFinderPattern(QRCode* qr, int x, int y) {
-
int dx, dy;
-
BOOL dark;
-
-
for (dy = 0; dy < 7; dy++) {
-
for (dx = 0; dx < 7; dx++) {
-
if (x + dx < QR_SIZE && y + dy < QR_SIZE) {
-
dark = (dx == 0 || dx == 6 || dy == 0 || dy == 6 ||
-
(dx >= 2 && dx <= 4 && dy >= 2 && dy <= 4)) ? TRUE : FALSE;
-
qr->modules[y + dy][x + dx] = dark;
-
}
-
}
-
}
-
}
-
-
BOOL QRCode_IsReserved(int x, int y) {
-
// Check if position is part of finder patterns
-
if ((x < 9 && y < 9) ||
-
(x >= QR_SIZE - 8 && y < 9) ||
-
(x < 9 && y >= QR_SIZE - 8)) {
-
return TRUE;
-
}
-
-
// Check timing patterns
-
if (x == 6 || y == 6) {
-
return TRUE;
-
}
-
-
// Check format information areas
-
if ((x < 9 && y == 8) || (x == 8 && y < 9)) {
-
return TRUE;
-
}
-
if ((x >= QR_SIZE - 8 && y == 8) || (x == 8 && y >= QR_SIZE - 7)) {
-
return TRUE;
-
}
-
-
return FALSE;
-
}
-
-
void QRCode_DrawToHDC(QRCode* qr, HDC hdc, int startX, int startY, int moduleSize) {
-
HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
-
HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
-
int x, y;
-
RECT rect;
-
-
for (y = 0; y < QR_SIZE; y++) {
-
for (x = 0; x < QR_SIZE; x++) {
-
rect.left = startX + x * moduleSize;
-
rect.top = startY + y * moduleSize;
-
rect.right = startX + (x + 1) * moduleSize;
-
rect.bottom = startY + (y + 1) * moduleSize;
-
-
FillRect(hdc, &rect, qr->modules[y][x] ? blackBrush : whiteBrush);
-
}
-
}
-
-
DeleteObject(blackBrush);
-
DeleteObject(whiteBrush);
-
}
-
-
int QRCode_GetSize(void) {
-
return QR_SIZE;
-
}
-
-
const char* QRCode_GetText(QRCode* qr) {
-
return qr->text;
-
}