···
5
+
#include <mmsystem.h>
7
+
#pragma comment(lib, "winmm.lib")
6
-
#define ID_GENERATE_QR 1003
12
+
// Radio control IDs
13
+
#define ID_TUNING_DIAL 2001
14
+
#define ID_VOLUME_KNOB 2002
15
+
#define ID_POWER_BUTTON 2003
17
+
// Radio station data
21
+
char description[128];
24
+
RadioStation g_stations[] = {
25
+
{14.230f, "BBC World Service", "International news and current affairs"},
26
+
{15.770f, "Radio Moscow", "Russian international broadcast"},
27
+
{17.895f, "Voice of America", "American international news"},
28
+
{21.500f, "Radio Australia", "Australian international service"},
29
+
{25.820f, "Radio Canada", "Canadian international broadcast"},
30
+
{28.400f, "Amateur Radio", "Ham radio operators"},
31
+
{31.100f, "Time Signal", "Atomic clock time broadcast"}
34
+
#define NUM_STATIONS (sizeof(g_stations) / sizeof(RadioStation))
35
+
#define SAMPLE_RATE 44100
36
+
#define BITS_PER_SAMPLE 16
38
+
#define BUFFER_SIZE 4410 // 0.1 seconds of audio
39
+
#define NUM_BUFFERS 4
44
+
WAVEHDR waveHeaders[NUM_BUFFERS];
45
+
short audioBuffers[NUM_BUFFERS][BUFFER_SIZE];
59
+
int isDraggingVolume;
62
+
RadioState g_radio = {14.230f, 0.5f, 0, 0, 0, 0};
63
+
AudioState g_audio = {0};
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
66
+
void DrawRadioInterface(HDC hdc, RECT* rect);
67
+
void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency);
68
+
void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency);
69
+
void DrawSignalMeter(HDC hdc, int x, int y, int strength);
70
+
void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume);
71
+
void DrawPowerButton(HDC hdc, int x, int y, int radius, int power);
72
+
int IsPointInCircle(int px, int py, int cx, int cy, int radius);
73
+
float GetAngleFromPoint(int px, int py, int cx, int cy);
74
+
void UpdateFrequencyFromMouse(int mouseX, int mouseY);
75
+
void UpdateVolumeFromMouse(int mouseX, int mouseY);
10
-
// Global QR code instance - static allocation, no new/delete
12
-
BOOL g_hasQrCode = FALSE;
13
-
char g_qrText[256] = "Hello World!";
78
+
int InitializeAudio();
79
+
void CleanupAudio();
80
+
void GenerateStaticBuffer(short* buffer, int size);
81
+
void FillAudioBuffer(short* buffer, int size);
82
+
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
85
+
RadioStation* FindNearestStation(float frequency);
86
+
float GetStationSignalStrength(RadioStation* station, float currentFreq);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
16
-
const char* CLASS_NAME = "HelloWorldWindow";
19
-
wc.lpfnWndProc = WindowProc;
20
-
wc.hInstance = hInstance;
21
-
wc.lpszClassName = CLASS_NAME;
22
-
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
23
-
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
27
-
HWND hwnd = CreateWindow(
30
-
WS_OVERLAPPEDWINDOW,
31
-
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
43
-
HMENU hMenu = CreateMenu();
46
-
HMENU hToolsMenu = CreatePopupMenu();
47
-
AppendMenu(hToolsMenu, MF_STRING, ID_GENERATE_QR, "&Generate QR Pattern");
48
-
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hToolsMenu, "&Tools");
51
-
HMENU hHelpMenu = CreatePopupMenu();
52
-
AppendMenu(hHelpMenu, MF_STRING, ID_ABOUT, "&About");
53
-
AppendMenu(hHelpMenu, MF_SEPARATOR, 0, NULL);
54
-
AppendMenu(hHelpMenu, MF_STRING, ID_EXIT, "E&xit");
55
-
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hHelpMenu, "&Help");
57
-
SetMenu(hwnd, hMenu);
59
-
ShowWindow(hwnd, nCmdShow);
63
-
while (GetMessage(&msg, NULL, 0, 0)) {
64
-
TranslateMessage(&msg);
65
-
DispatchMessage(&msg);
89
+
const char* CLASS_NAME = "ShortwaveRadio";
92
+
wc.lpfnWndProc = WindowProc;
93
+
wc.hInstance = hInstance;
94
+
wc.lpszClassName = CLASS_NAME;
95
+
wc.hbrBackground = CreateSolidBrush(RGB(101, 67, 33)); // Wood grain brown
96
+
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
100
+
HWND hwnd = CreateWindow(
102
+
"Shortwave Radio Tuner",
103
+
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, // Fixed size
104
+
CW_USEDEFAULT, CW_USEDEFAULT, 600, 450,
111
+
if (hwnd == NULL) {
115
+
// Initialize audio system
116
+
if (InitializeAudio() != 0) {
117
+
MessageBox(hwnd, "Failed to initialize audio system", "Error", MB_OK | MB_ICONERROR);
121
+
// Audio starts when power button is pressed
124
+
HMENU hMenu = CreateMenu();
127
+
HMENU hRadioMenu = CreatePopupMenu();
128
+
AppendMenu(hRadioMenu, MF_STRING, ID_ABOUT, "&About");
129
+
AppendMenu(hRadioMenu, MF_SEPARATOR, 0, NULL);
130
+
AppendMenu(hRadioMenu, MF_STRING, ID_EXIT, "E&xit");
131
+
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hRadioMenu, "&Radio");
133
+
SetMenu(hwnd, hMenu);
135
+
ShowWindow(hwnd, nCmdShow);
136
+
UpdateWindow(hwnd);
139
+
while (GetMessage(&msg, NULL, 0, 0)) {
140
+
TranslateMessage(&msg);
141
+
DispatchMessage(&msg);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
74
-
// Trigger repaint when window is resized
75
-
InvalidateRect(hwnd, NULL, TRUE);
79
-
// No cleanup needed for static allocation
85
-
HDC hdc = BeginPaint(hwnd, &ps);
88
-
GetClientRect(hwnd, &rect);
91
-
// Draw QR code - calculate size based on window
92
-
int qrSize = QRCode_GetSize();
94
-
int qrPixelSize = qrSize * moduleSize;
96
-
// Center horizontally and vertically with some padding
97
-
int startX = (rect.right - qrPixelSize) / 2;
98
-
int startY = (rect.bottom - qrPixelSize - 80) / 2; // Leave space for text
100
-
QRCode_DrawToHDC(&g_qrCode, hdc, startX, startY, moduleSize);
102
-
// Draw text below QR code
103
-
SetTextAlign(hdc, TA_CENTER);
104
-
SetBkMode(hdc, TRANSPARENT);
106
-
while (g_qrText[textLen]) textLen++; // Calculate length
107
-
TextOut(hdc, rect.right / 2, startY + qrPixelSize + 20,
108
-
g_qrText, textLen);
111
-
const char* disclaimer = "(Visual demo - not scannable)";
112
-
int disclaimerLen = 0;
113
-
while (disclaimer[disclaimerLen]) disclaimerLen++;
114
-
TextOut(hdc, rect.right / 2, startY + qrPixelSize + 40,
115
-
disclaimer, disclaimerLen);
117
-
// Default view - center in current window size
118
-
SetTextAlign(hdc, TA_CENTER);
119
-
SetBkMode(hdc, TRANSPARENT);
120
-
int centerY = rect.bottom / 2;
121
-
TextOut(hdc, rect.right / 2, centerY - 10, "Hello World!", 12);
122
-
TextOut(hdc, rect.right / 2, centerY + 10,
123
-
"Use Tools > Generate QR Pattern", 32);
126
-
EndPaint(hwnd, &ps);
131
-
switch (LOWORD(wParam)) {
133
-
const char* aboutText = "Hello World App\n\n"
135
-
"Built by: Kieran Klukas\n\n"
136
-
"A simple Win32 application\n"
137
-
"compatible with Windows XP\n\n"
139
-
"- QR Pattern Generation (visual demo)\n"
140
-
"- XP Compatible Design\n"
141
-
"- Pure Win32 API";
142
-
MessageBox(hwnd, aboutText, "About Hello World App",
143
-
MB_OK | MB_ICONINFORMATION);
146
-
case ID_GENERATE_QR: {
147
-
// Simple input dialog using InputBox simulation
148
-
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.",
149
-
"Generate QR Pattern", MB_OKCANCEL | MB_ICONQUESTION) == IDCANCEL) {
151
-
// For now, use a simple preset - in a real app you'd want a proper input dialog
152
-
const char* presets[] = {
154
-
"https://github.com/taciturnaxolotl/shortwave",
155
-
"Made with love by Kieran Klukas",
156
-
"Windows XP Forever!",
157
-
"QR codes are cool!"
160
-
static int presetIndex = 0;
161
-
const char* selectedText = presets[presetIndex % 5];
164
-
// Copy selected text to global buffer
166
-
while (selectedText[i] && i < 255) {
167
-
g_qrText[i] = selectedText[i];
170
-
g_qrText[i] = '\0';
173
-
// Generate new QR code - no new/delete, just reinitialize
174
-
QRCode_Init(&g_qrCode, g_qrText);
175
-
g_hasQrCode = TRUE;
177
-
// Refresh the window
178
-
InvalidateRect(hwnd, NULL, TRUE);
182
-
PostQuitMessage(0);
188
-
return DefWindowProc(hwnd, uMsg, wParam, lParam);
154
+
InvalidateRect(hwnd, NULL, TRUE);
158
+
PostQuitMessage(0);
163
+
HDC hdc = BeginPaint(hwnd, &ps);
166
+
GetClientRect(hwnd, &rect);
168
+
DrawRadioInterface(hdc, &rect);
170
+
EndPaint(hwnd, &ps);
174
+
case WM_LBUTTONDOWN: {
175
+
int mouseX = LOWORD(lParam);
176
+
int mouseY = HIWORD(lParam);
178
+
// Check if clicking on tuning dial
179
+
if (IsPointInCircle(mouseX, mouseY, 150, 200, 60)) {
180
+
g_radio.isDraggingDial = 1;
182
+
UpdateFrequencyFromMouse(mouseX, mouseY);
183
+
InvalidateRect(hwnd, NULL, TRUE);
185
+
// Check if clicking on volume knob
186
+
else if (IsPointInCircle(mouseX, mouseY, 350, 200, 30)) {
187
+
g_radio.isDraggingVolume = 1;
189
+
UpdateVolumeFromMouse(mouseX, mouseY);
190
+
InvalidateRect(hwnd, NULL, TRUE);
192
+
// Check if clicking on power button
193
+
else if (IsPointInCircle(mouseX, mouseY, 500, 120, 25)) {
194
+
g_radio.power = !g_radio.power;
195
+
if (g_radio.power) {
200
+
InvalidateRect(hwnd, NULL, TRUE);
205
+
case WM_LBUTTONUP: {
206
+
if (g_radio.isDraggingDial || g_radio.isDraggingVolume) {
207
+
g_radio.isDraggingDial = 0;
208
+
g_radio.isDraggingVolume = 0;
214
+
case WM_MOUSEMOVE: {
215
+
if (g_radio.isDraggingDial) {
216
+
int mouseX = LOWORD(lParam);
217
+
int mouseY = HIWORD(lParam);
218
+
UpdateFrequencyFromMouse(mouseX, mouseY);
219
+
InvalidateRect(hwnd, NULL, TRUE);
221
+
else if (g_radio.isDraggingVolume) {
222
+
int mouseX = LOWORD(lParam);
223
+
int mouseY = HIWORD(lParam);
224
+
UpdateVolumeFromMouse(mouseX, mouseY);
225
+
InvalidateRect(hwnd, NULL, TRUE);
231
+
switch (LOWORD(wParam)) {
233
+
const char* aboutText = "Shortwave Radio Tuner\n\n"
235
+
"Built for Rewind V2 Hackathon\n\n"
236
+
"A vintage shortwave radio simulator\n"
237
+
"compatible with Windows XP\n\n"
239
+
"- Realistic tuning interface\n"
240
+
"- Internet radio streaming\n"
241
+
"- Authentic static noise";
242
+
MessageBox(hwnd, aboutText, "About Shortwave Radio",
243
+
MB_OK | MB_ICONINFORMATION);
247
+
PostQuitMessage(0);
253
+
return DefWindowProc(hwnd, uMsg, wParam, lParam);
256
+
void DrawRadioInterface(HDC hdc, RECT* rect) {
257
+
// Create vintage radio background
258
+
HBRUSH woodBrush = CreateSolidBrush(RGB(101, 67, 33));
259
+
FillRect(hdc, rect, woodBrush);
260
+
DeleteObject(woodBrush);
262
+
// Draw radio panel (darker inset)
263
+
RECT panel = {50, 50, rect->right - 50, rect->bottom - 50};
264
+
HBRUSH panelBrush = CreateSolidBrush(RGB(80, 50, 25));
265
+
FillRect(hdc, &panel, panelBrush);
266
+
DeleteObject(panelBrush);
268
+
// Draw panel border (raised effect)
269
+
HPEN lightPen = CreatePen(PS_SOLID, 2, RGB(140, 100, 60));
270
+
HPEN darkPen = CreatePen(PS_SOLID, 2, RGB(40, 25, 15));
272
+
SelectObject(hdc, lightPen);
273
+
MoveToEx(hdc, panel.left, panel.bottom, NULL);
274
+
LineTo(hdc, panel.left, panel.top);
275
+
LineTo(hdc, panel.right, panel.top);
277
+
SelectObject(hdc, darkPen);
278
+
LineTo(hdc, panel.right, panel.bottom);
279
+
LineTo(hdc, panel.left, panel.bottom);
281
+
DeleteObject(lightPen);
282
+
DeleteObject(darkPen);
284
+
// Draw frequency display
285
+
DrawFrequencyDisplay(hdc, 200, 80, g_radio.frequency);
287
+
// Draw tuning dial
288
+
DrawTuningDial(hdc, 150, 200, 60, g_radio.frequency);
290
+
// Draw volume knob
291
+
DrawVolumeKnob(hdc, 350, 200, 30, g_radio.volume);
293
+
// Draw signal meter
294
+
DrawSignalMeter(hdc, 450, 150, g_radio.signalStrength);
296
+
// Draw power button
297
+
DrawPowerButton(hdc, 500, 120, 25, g_radio.power);
299
+
// Draw station info if tuned to a station
300
+
RadioStation* currentStation = FindNearestStation(g_radio.frequency);
301
+
if (currentStation && g_radio.signalStrength > 30) {
302
+
RECT stationRect = {80, 320, 520, 360};
303
+
HBRUSH stationBrush = CreateSolidBrush(RGB(0, 0, 0));
304
+
FillRect(hdc, &stationRect, stationBrush);
305
+
DeleteObject(stationBrush);
307
+
HPEN stationPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
308
+
SelectObject(hdc, stationPen);
309
+
Rectangle(hdc, stationRect.left, stationRect.top, stationRect.right, stationRect.bottom);
310
+
DeleteObject(stationPen);
312
+
SetTextColor(hdc, RGB(0, 255, 0));
313
+
HFONT stationFont = CreateFont(12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
314
+
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
315
+
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
316
+
DEFAULT_PITCH | FF_SWISS, "Arial");
317
+
SelectObject(hdc, stationFont);
319
+
char stationText[256];
320
+
sprintf(stationText, "%.3f MHz - %s: %s",
321
+
currentStation->frequency, currentStation->name, currentStation->description);
323
+
SetTextAlign(hdc, TA_LEFT);
324
+
TextOut(hdc, stationRect.left + 5, stationRect.top + 5, stationText, strlen(stationText));
326
+
DeleteObject(stationFont);
330
+
SetBkMode(hdc, TRANSPARENT);
331
+
SetTextColor(hdc, RGB(255, 255, 255));
332
+
HFONT font = CreateFont(14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
333
+
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
334
+
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
335
+
DEFAULT_PITCH | FF_SWISS, "Arial");
336
+
SelectObject(hdc, font);
338
+
TextOut(hdc, 180, 300, "TUNING", 6);
339
+
TextOut(hdc, 330, 260, "VOLUME", 6);
340
+
TextOut(hdc, 430, 200, "SIGNAL", 6);
341
+
TextOut(hdc, 485, 160, "POWER", 5);
343
+
DeleteObject(font);
346
+
void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency) {
347
+
// Draw display background (black LCD style)
348
+
RECT display = {x - 80, y - 20, x + 80, y + 20};
349
+
HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
350
+
FillRect(hdc, &display, blackBrush);
351
+
DeleteObject(blackBrush);
353
+
// Draw display border
354
+
HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
355
+
SelectObject(hdc, borderPen);
356
+
Rectangle(hdc, display.left, display.top, display.right, display.bottom);
357
+
DeleteObject(borderPen);
359
+
// Draw frequency text
361
+
sprintf(freqText, "%.3f MHz", frequency);
363
+
SetBkMode(hdc, TRANSPARENT);
364
+
SetTextColor(hdc, RGB(0, 255, 0)); // Green LCD color
365
+
HFONT lcdFont = CreateFont(16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
366
+
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
367
+
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
368
+
FIXED_PITCH | FF_MODERN, "Courier New");
369
+
SelectObject(hdc, lcdFont);
371
+
SetTextAlign(hdc, TA_CENTER);
372
+
TextOut(hdc, x, y - 8, freqText, strlen(freqText));
374
+
DeleteObject(lcdFont);
377
+
void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency) {
378
+
// Draw dial background
379
+
HBRUSH dialBrush = CreateSolidBrush(RGB(160, 120, 80));
380
+
SelectObject(hdc, dialBrush);
381
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
382
+
DeleteObject(dialBrush);
384
+
// Draw dial border
385
+
HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(60, 40, 20));
386
+
SelectObject(hdc, borderPen);
387
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
388
+
DeleteObject(borderPen);
390
+
// Draw frequency markings
391
+
SetTextColor(hdc, RGB(0, 0, 0));
392
+
HFONT smallFont = CreateFont(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
393
+
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
394
+
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
395
+
DEFAULT_PITCH | FF_SWISS, "Arial");
396
+
SelectObject(hdc, smallFont);
397
+
SetTextAlign(hdc, TA_CENTER);
399
+
for (int i = 0; i < 12; i++) {
400
+
float angle = (float)i * 3.14159f / 6.0f;
401
+
int markX = x + (int)((radius - 15) * cos(angle));
402
+
int markY = y + (int)((radius - 15) * sin(angle));
405
+
sprintf(mark, "%d", 10 + i * 2);
406
+
TextOut(hdc, markX, markY - 5, mark, strlen(mark));
409
+
// Draw pointer based on frequency
410
+
float angle = (frequency - 10.0f) / 24.0f * 3.14159f;
411
+
int pointerX = x + (int)((radius - 10) * cos(angle));
412
+
int pointerY = y + (int)((radius - 10) * sin(angle));
414
+
HPEN pointerPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
415
+
SelectObject(hdc, pointerPen);
416
+
MoveToEx(hdc, x, y, NULL);
417
+
LineTo(hdc, pointerX, pointerY);
418
+
DeleteObject(pointerPen);
420
+
DeleteObject(smallFont);
423
+
void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume) {
424
+
// Draw knob background
425
+
HBRUSH knobBrush = CreateSolidBrush(RGB(140, 100, 60));
426
+
SelectObject(hdc, knobBrush);
427
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
428
+
DeleteObject(knobBrush);
430
+
// Draw knob border
431
+
HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(60, 40, 20));
432
+
SelectObject(hdc, borderPen);
433
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
434
+
DeleteObject(borderPen);
436
+
// Draw volume indicator
437
+
float angle = volume * 3.14159f * 1.5f - 3.14159f * 0.75f;
438
+
int indicatorX = x + (int)((radius - 5) * cos(angle));
439
+
int indicatorY = y + (int)((radius - 5) * sin(angle));
441
+
HPEN indicatorPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
442
+
SelectObject(hdc, indicatorPen);
443
+
MoveToEx(hdc, x, y, NULL);
444
+
LineTo(hdc, indicatorX, indicatorY);
445
+
DeleteObject(indicatorPen);
448
+
void DrawSignalMeter(HDC hdc, int x, int y, int strength) {
449
+
// Draw meter background
450
+
RECT meter = {x, y, x + 80, y + 20};
451
+
HBRUSH meterBrush = CreateSolidBrush(RGB(0, 0, 0));
452
+
FillRect(hdc, &meter, meterBrush);
453
+
DeleteObject(meterBrush);
455
+
// Draw meter border
456
+
HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
457
+
SelectObject(hdc, borderPen);
458
+
Rectangle(hdc, meter.left, meter.top, meter.right, meter.bottom);
459
+
DeleteObject(borderPen);
461
+
// Draw signal bars
463
+
int numBars = strength / 10;
464
+
for (int i = 0; i < numBars && i < 10; i++) {
465
+
RECT bar = {x + 2 + i * barWidth, y + 2,
466
+
x + 2 + (i + 1) * barWidth - 1, y + 18};
469
+
if (i < 3) barColor = RGB(0, 255, 0); // Green
470
+
else if (i < 7) barColor = RGB(255, 255, 0); // Yellow
471
+
else barColor = RGB(255, 0, 0); // Red
473
+
HBRUSH barBrush = CreateSolidBrush(barColor);
474
+
FillRect(hdc, &bar, barBrush);
475
+
DeleteObject(barBrush);
479
+
void DrawPowerButton(HDC hdc, int x, int y, int radius, int power) {
480
+
// Draw button background
481
+
COLORREF buttonColor = power ? RGB(255, 0, 0) : RGB(100, 100, 100);
482
+
HBRUSH buttonBrush = CreateSolidBrush(buttonColor);
483
+
SelectObject(hdc, buttonBrush);
484
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
485
+
DeleteObject(buttonBrush);
487
+
// Draw button border
488
+
HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(60, 60, 60));
489
+
SelectObject(hdc, borderPen);
490
+
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
491
+
DeleteObject(borderPen);
493
+
// Draw power symbol
495
+
HPEN symbolPen = CreatePen(PS_SOLID, 3, RGB(255, 255, 255));
496
+
SelectObject(hdc, symbolPen);
498
+
// Draw power symbol (circle with line)
499
+
Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6);
500
+
MoveToEx(hdc, x, y - 10, NULL);
501
+
LineTo(hdc, x, y - 2);
503
+
DeleteObject(symbolPen);
507
+
int IsPointInCircle(int px, int py, int cx, int cy, int radius) {
510
+
return (dx * dx + dy * dy) <= (radius * radius);
513
+
float GetAngleFromPoint(int px, int py, int cx, int cy) {
514
+
return atan2((float)(py - cy), (float)(px - cx));
517
+
void UpdateFrequencyFromMouse(int mouseX, int mouseY) {
518
+
float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200);
520
+
// Convert angle to frequency (10-34 MHz range)
521
+
// Normalize angle from -PI to PI to 0-1 range
522
+
float normalizedAngle = (angle + 3.14159f) / (2.0f * 3.14159f);
524
+
// Map to frequency range
525
+
g_radio.frequency = 10.0f + normalizedAngle * 24.0f;
528
+
if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
529
+
if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
531
+
// Calculate signal strength based on nearest station
532
+
RadioStation* nearestStation = FindNearestStation(g_radio.frequency);
533
+
if (nearestStation) {
534
+
g_radio.signalStrength = (int)(GetStationSignalStrength(nearestStation, g_radio.frequency) * 100.0f);
536
+
g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
539
+
if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
540
+
if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
543
+
void UpdateVolumeFromMouse(int mouseX, int mouseY) {
544
+
float angle = GetAngleFromPoint(mouseX, mouseY, 350, 200);
546
+
// Convert angle to volume (0-1 range)
547
+
// Map from -135 degrees to +135 degrees
548
+
float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
550
+
g_radio.volume = normalizedAngle;
553
+
if (g_radio.volume < 0.0f) g_radio.volume = 0.0f;
554
+
if (g_radio.volume > 1.0f) g_radio.volume = 1.0f;
557
+
int InitializeAudio() {
558
+
WAVEFORMATEX waveFormat;
559
+
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
560
+
waveFormat.nChannels = CHANNELS;
561
+
waveFormat.nSamplesPerSec = SAMPLE_RATE;
562
+
waveFormat.wBitsPerSample = BITS_PER_SAMPLE;
563
+
waveFormat.nBlockAlign = (waveFormat.nChannels * waveFormat.wBitsPerSample) / 8;
564
+
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
565
+
waveFormat.cbSize = 0;
567
+
MMRESULT result = waveOutOpen(&g_audio.hWaveOut, WAVE_MAPPER, &waveFormat,
568
+
(DWORD_PTR)WaveOutProc, 0, CALLBACK_FUNCTION);
570
+
if (result != MMSYSERR_NOERROR) {
574
+
// Initialize audio buffers
575
+
for (int i = 0; i < NUM_BUFFERS; i++) {
576
+
memset(&g_audio.waveHeaders[i], 0, sizeof(WAVEHDR));
577
+
g_audio.waveHeaders[i].lpData = (LPSTR)g_audio.audioBuffers[i];
578
+
g_audio.waveHeaders[i].dwBufferLength = BUFFER_SIZE * sizeof(short);
579
+
g_audio.waveHeaders[i].dwFlags = 0;
581
+
result = waveOutPrepareHeader(g_audio.hWaveOut, &g_audio.waveHeaders[i], sizeof(WAVEHDR));
582
+
if (result != MMSYSERR_NOERROR) {
587
+
g_audio.currentBuffer = 0;
588
+
g_audio.isPlaying = 0;
589
+
g_audio.staticVolume = 0.3f;
590
+
g_audio.radioVolume = 0.0f;
595
+
void CleanupAudio() {
596
+
if (g_audio.hWaveOut) {
597
+
waveOutReset(g_audio.hWaveOut);
599
+
for (int i = 0; i < NUM_BUFFERS; i++) {
600
+
waveOutUnprepareHeader(g_audio.hWaveOut, &g_audio.waveHeaders[i], sizeof(WAVEHDR));
603
+
waveOutClose(g_audio.hWaveOut);
604
+
g_audio.hWaveOut = NULL;
608
+
void GenerateStaticBuffer(short* buffer, int size) {
609
+
for (int i = 0; i < size; i++) {
610
+
// Generate white noise
611
+
float noise = ((float)rand() / RAND_MAX) * 2.0f - 1.0f;
613
+
// Apply volume and convert to 16-bit
614
+
float volume = g_audio.staticVolume * g_radio.volume;
615
+
buffer[i] = (short)(noise * volume * 32767.0f);
619
+
void FillAudioBuffer(short* buffer, int size) {
620
+
// Only generate audio if radio is powered on
621
+
if (!g_radio.power) {
622
+
memset(buffer, 0, size * sizeof(short));
627
+
GenerateStaticBuffer(buffer, size);
629
+
// Find nearest station and mix in signal
630
+
RadioStation* station = FindNearestStation(g_radio.frequency);
631
+
if (station && g_radio.signalStrength > 20) {
632
+
float signalStrength = GetStationSignalStrength(station, g_radio.frequency);
633
+
float radioVolume = signalStrength * g_radio.volume * 0.7f;
635
+
for (int i = 0; i < size; i++) {
636
+
// Generate different tones for different stations
637
+
float time = (float)i / SAMPLE_RATE;
638
+
float tone1 = sin(2.0f * 3.14159f * (station->frequency * 50.0f) * time);
639
+
float tone2 = sin(2.0f * 3.14159f * (station->frequency * 75.0f) * time) * 0.5f;
640
+
float tone = (tone1 + tone2) * 0.5f;
642
+
// Add some modulation to simulate voice/music
643
+
float modulation = sin(2.0f * 3.14159f * 3.0f * time) * 0.3f + 0.7f;
644
+
tone *= modulation;
646
+
// Mix tone with existing static
647
+
float mixed = (float)buffer[i] / 32767.0f;
648
+
mixed = mixed * (1.0f - radioVolume) + tone * radioVolume;
650
+
buffer[i] = (short)(mixed * 32767.0f);
655
+
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
656
+
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
657
+
if (uMsg == WOM_DONE && g_audio.isPlaying) {
658
+
WAVEHDR* waveHeader = (WAVEHDR*)dwParam1;
660
+
// Refill the buffer
661
+
FillAudioBuffer((short*)waveHeader->lpData, BUFFER_SIZE);
663
+
// Queue the buffer for playback
664
+
waveOutWrite(g_audio.hWaveOut, waveHeader, sizeof(WAVEHDR));
668
+
void StartAudio() {
669
+
if (!g_audio.isPlaying) {
670
+
g_audio.isPlaying = 1;
672
+
// Fill and queue all buffers
673
+
for (int i = 0; i < NUM_BUFFERS; i++) {
674
+
FillAudioBuffer(g_audio.audioBuffers[i], BUFFER_SIZE);
675
+
waveOutWrite(g_audio.hWaveOut, &g_audio.waveHeaders[i], sizeof(WAVEHDR));
681
+
if (g_audio.isPlaying) {
682
+
g_audio.isPlaying = 0;
683
+
waveOutReset(g_audio.hWaveOut);
687
+
RadioStation* FindNearestStation(float frequency) {
688
+
RadioStation* nearest = NULL;
689
+
float minDistance = 999.0f;
691
+
for (int i = 0; i < NUM_STATIONS; i++) {
692
+
float distance = fabs(g_stations[i].frequency - frequency);
693
+
if (distance < minDistance) {
694
+
minDistance = distance;
695
+
nearest = &g_stations[i];
699
+
// Only return station if we're close enough (within 0.5 MHz)
700
+
if (minDistance <= 0.5f) {
707
+
float GetStationSignalStrength(RadioStation* station, float currentFreq) {
708
+
if (!station) return 0.0f;
710
+
float distance = fabs(station->frequency - currentFreq);
712
+
// Signal strength drops off with distance from exact frequency
713
+
if (distance < 0.05f) {
714
+
return 0.9f; // Very strong signal
715
+
} else if (distance < 0.1f) {
716
+
return 0.7f; // Strong signal
717
+
} else if (distance < 0.2f) {
718
+
return 0.5f; // Medium signal
719
+
} else if (distance < 0.5f) {
720
+
return 0.2f; // Weak signal
723
+
return 0.0f; // No signal