a mega cool windows xp app
1#include <windows.h>
2#include <math.h>
3#include <stdio.h>
4#include <string.h>
5#include <mmsystem.h>
6#include <wininet.h>
7#include "libs/bass.h"
8
9#pragma comment(lib, "winmm.lib")
10#pragma comment(lib, "wininet.lib")
11
12#define ID_ABOUT 1001
13#define ID_EXIT 1002
14
15// Radio control IDs
16#define ID_TUNING_DIAL 2001
17#define ID_VOLUME_KNOB 2002
18#define ID_POWER_BUTTON 2003
19
20// Radio station data
21typedef struct {
22 float frequency;
23 char name[64];
24 char description[128];
25 char streamUrl[256];
26} RadioStation;
27
28RadioStation g_stations[] = {
29 {14.230f, "SomaFM Groove", "Downtempo and chillout", "http://ice1.somafm.com/groovesalad-128-mp3"},
30 {15.770f, "Radio Paradise", "Eclectic music mix", "http://stream.radioparadise.com/mp3-128"},
31 {17.895f, "Jazz Radio", "Smooth jazz", "http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3"},
32 {21.500f, "Classical Music", "Classical radio", "http://stream.wqxr.org/wqxr"},
33};
34
35#define NUM_STATIONS (sizeof(g_stations) / sizeof(RadioStation))
36#define SAMPLE_RATE 44100
37#define BITS_PER_SAMPLE 16
38#define CHANNELS 1
39#define BUFFER_SIZE 4410 // 0.1 seconds of audio
40#define NUM_BUFFERS 4
41
42// Audio state
43typedef struct {
44 // BASS handles
45 HSTREAM currentStream;
46 HSTREAM staticStream;
47 int isPlaying;
48 float staticVolume;
49 float radioVolume;
50
51 // Station tracking
52 RadioStation* currentStation;
53
54 // VU meter levels (0.0 to 1.0)
55 float vuLevelLeft;
56 float vuLevelRight;
57} AudioState;
58
59// Radio state
60typedef struct {
61 float frequency;
62 float volume;
63 int power;
64 int signalStrength;
65 int isDraggingDial;
66 int isDraggingVolume;
67} RadioState;
68
69RadioState g_radio = {14.230f, 0.8f, 0, 0, 0, 0}; // Increase default volume to 0.8
70AudioState g_audio = {0};
71
72LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
73void DrawRadioInterface(HDC hdc, RECT* rect);
74void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency);
75void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency);
76void DrawSignalMeter(HDC hdc, int x, int y, int strength);
77void DrawVUMeter(HDC hdc, int x, int y, float leftLevel, float rightLevel);
78void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume);
79void DrawPowerButton(HDC hdc, int x, int y, int radius, int power);
80int IsPointInCircle(int px, int py, int cx, int cy, int radius);
81float GetAngleFromPoint(int px, int py, int cx, int cy);
82void UpdateFrequencyFromMouse(int mouseX, int mouseY);
83void UpdateVolumeFromMouse(int mouseX, int mouseY);
84
85// Audio functions
86int InitializeAudio();
87void CleanupAudio();
88void StartAudio();
89void StopAudio();
90RadioStation* FindNearestStation(float frequency);
91float GetStationSignalStrength(RadioStation* station, float currentFreq);
92
93// BASS streaming functions
94int StartBassStreaming(RadioStation* station);
95void StopBassStreaming();
96
97// Static noise functions
98DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user);
99void StartStaticNoise();
100void StopStaticNoise();
101void UpdateStaticVolume(float signalStrength);
102
103// VU meter functions
104void UpdateVULevels();
105
106int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
107 // Allocate console for debugging
108 AllocConsole();
109 freopen("CONOUT$", "w", stdout);
110 freopen("CONOUT$", "w", stderr);
111 printf("Shortwave Radio Debug Console\n");
112 printf("=============================\n");
113
114 const char* CLASS_NAME = "ShortwaveRadio";
115
116 WNDCLASS wc = {};
117 wc.lpfnWndProc = WindowProc;
118 wc.hInstance = hInstance;
119 wc.lpszClassName = CLASS_NAME;
120 wc.hbrBackground = CreateSolidBrush(RGB(101, 67, 33)); // Wood grain brown
121 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
122
123 RegisterClass(&wc);
124
125 HWND hwnd = CreateWindow(
126 CLASS_NAME,
127 "Shortwave Radio Tuner",
128 WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, // Fixed size
129 CW_USEDEFAULT, CW_USEDEFAULT, 600, 450,
130 NULL,
131 NULL,
132 hInstance,
133 NULL
134 );
135
136 if (hwnd == NULL) {
137 return 0;
138 }
139
140 // Initialize audio system
141 if (InitializeAudio() != 0) {
142 MessageBox(hwnd, "Failed to initialize audio system", "Error", MB_OK | MB_ICONERROR);
143 return 0;
144 }
145
146 // Audio starts when power button is pressed
147
148 // Create menu
149 HMENU hMenu = CreateMenu();
150
151 // Radio menu
152 HMENU hRadioMenu = CreatePopupMenu();
153 AppendMenu(hRadioMenu, MF_STRING, ID_ABOUT, "&About");
154 AppendMenu(hRadioMenu, MF_SEPARATOR, 0, NULL);
155 AppendMenu(hRadioMenu, MF_STRING, ID_EXIT, "E&xit");
156 AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hRadioMenu, "&Radio");
157
158 SetMenu(hwnd, hMenu);
159
160 ShowWindow(hwnd, nCmdShow);
161 UpdateWindow(hwnd);
162
163 // Set timer for VU meter updates (30 FPS)
164 SetTimer(hwnd, 1, 33, NULL);
165
166 MSG msg = {};
167 while (GetMessage(&msg, NULL, 0, 0)) {
168 TranslateMessage(&msg);
169 DispatchMessage(&msg);
170 }
171
172 // Cleanup audio
173 StopAudio();
174 CleanupAudio();
175
176 return 0;
177}
178
179LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
180 switch (uMsg) {
181 case WM_SIZE:
182 InvalidateRect(hwnd, NULL, TRUE);
183 return 0;
184
185 case WM_DESTROY:
186 PostQuitMessage(0);
187 return 0;
188
189 case WM_PAINT: {
190 PAINTSTRUCT ps;
191 HDC hdc = BeginPaint(hwnd, &ps);
192
193 RECT rect;
194 GetClientRect(hwnd, &rect);
195
196 // Update VU levels before drawing
197 if (g_radio.power) {
198 UpdateVULevels();
199 }
200
201 DrawRadioInterface(hdc, &rect);
202
203 EndPaint(hwnd, &ps);
204 return 0;
205 }
206
207 case WM_LBUTTONDOWN: {
208 int mouseX = LOWORD(lParam);
209 int mouseY = HIWORD(lParam);
210
211 // Check if clicking on tuning dial
212 if (IsPointInCircle(mouseX, mouseY, 150, 200, 60)) {
213 g_radio.isDraggingDial = 1;
214 SetCapture(hwnd);
215 UpdateFrequencyFromMouse(mouseX, mouseY);
216 InvalidateRect(hwnd, NULL, TRUE);
217 }
218 // Check if clicking on volume knob
219 else if (IsPointInCircle(mouseX, mouseY, 350, 200, 30)) {
220 g_radio.isDraggingVolume = 1;
221 SetCapture(hwnd);
222 UpdateVolumeFromMouse(mouseX, mouseY);
223 InvalidateRect(hwnd, NULL, TRUE);
224 }
225 // Check if clicking on power button
226 else if (IsPointInCircle(mouseX, mouseY, 500, 120, 25)) {
227 g_radio.power = !g_radio.power;
228 if (g_radio.power) {
229 StartAudio();
230 } else {
231 StopAudio();
232 }
233 InvalidateRect(hwnd, NULL, TRUE);
234 }
235 return 0;
236 }
237
238 case WM_LBUTTONUP: {
239 if (g_radio.isDraggingDial || g_radio.isDraggingVolume) {
240 g_radio.isDraggingDial = 0;
241 g_radio.isDraggingVolume = 0;
242 ReleaseCapture();
243 }
244 return 0;
245 }
246
247 case WM_MOUSEMOVE: {
248 if (g_radio.isDraggingDial) {
249 int mouseX = LOWORD(lParam);
250 int mouseY = HIWORD(lParam);
251 UpdateFrequencyFromMouse(mouseX, mouseY);
252 InvalidateRect(hwnd, NULL, TRUE);
253 }
254 else if (g_radio.isDraggingVolume) {
255 int mouseX = LOWORD(lParam);
256 int mouseY = HIWORD(lParam);
257 UpdateVolumeFromMouse(mouseX, mouseY);
258 InvalidateRect(hwnd, NULL, TRUE);
259 }
260 return 0;
261 }
262
263 case WM_KEYDOWN: {
264 switch (wParam) {
265 case VK_UP: {
266 // Increase frequency by 0.1 MHz (fine tuning)
267 g_radio.frequency += 0.1f;
268 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
269
270 // Update signal strength for new frequency
271 RadioStation* station = FindNearestStation(g_radio.frequency);
272 if (station) {
273 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
274 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
275 StopBassStreaming();
276 StartBassStreaming(station);
277 }
278 } else {
279 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
280 StopBassStreaming();
281 }
282
283 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
284 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
285
286 UpdateStaticVolume(g_radio.signalStrength);
287 InvalidateRect(hwnd, NULL, TRUE);
288 break;
289 }
290
291 case VK_DOWN: {
292 // Decrease frequency by 0.1 MHz (fine tuning)
293 g_radio.frequency -= 0.1f;
294 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
295
296 // Update signal strength for new frequency
297 RadioStation* station = FindNearestStation(g_radio.frequency);
298 if (station) {
299 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
300 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
301 StopBassStreaming();
302 StartBassStreaming(station);
303 }
304 } else {
305 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
306 StopBassStreaming();
307 }
308
309 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
310 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
311
312 UpdateStaticVolume(g_radio.signalStrength);
313 InvalidateRect(hwnd, NULL, TRUE);
314 break;
315 }
316
317 case VK_RIGHT: {
318 // Increase frequency by 1.0 MHz (coarse tuning)
319 g_radio.frequency += 1.0f;
320 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
321
322 // Update signal strength for new frequency
323 RadioStation* station = FindNearestStation(g_radio.frequency);
324 if (station) {
325 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
326 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
327 StopBassStreaming();
328 StartBassStreaming(station);
329 }
330 } else {
331 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
332 StopBassStreaming();
333 }
334
335 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
336 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
337
338 UpdateStaticVolume(g_radio.signalStrength);
339 InvalidateRect(hwnd, NULL, TRUE);
340 break;
341 }
342
343 case VK_LEFT: {
344 // Decrease frequency by 1.0 MHz (coarse tuning)
345 g_radio.frequency -= 1.0f;
346 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
347
348 // Update signal strength for new frequency
349 RadioStation* station = FindNearestStation(g_radio.frequency);
350 if (station) {
351 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
352 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
353 StopBassStreaming();
354 StartBassStreaming(station);
355 }
356 } else {
357 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
358 StopBassStreaming();
359 }
360
361 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
362 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
363
364 UpdateStaticVolume(g_radio.signalStrength);
365 InvalidateRect(hwnd, NULL, TRUE);
366 break;
367 }
368 }
369 return 0;
370 }
371
372 case WM_COMMAND:
373 switch (LOWORD(wParam)) {
374 case ID_ABOUT: {
375 const char* aboutText = "Shortwave Radio Tuner\n\n"
376 "Version: 1.0.0\n"
377 "Built for Rewind V2 Hackathon\n\n"
378 "A vintage shortwave radio simulator\n"
379 "compatible with Windows XP\n\n"
380 "Features:\n"
381 "- Realistic tuning interface\n"
382 "- Internet radio streaming\n"
383 "- Authentic static noise\n\n"
384 "Controls:\n"
385 "- Drag tuning dial to change frequency\n"
386 "- UP/DOWN arrows: Fine tuning (0.1 MHz)\n"
387 "- LEFT/RIGHT arrows: Coarse tuning (1.0 MHz)\n"
388 "- Click power button to turn on/off\n"
389 "- Drag volume knob to adjust volume";
390 MessageBox(hwnd, aboutText, "About Shortwave Radio",
391 MB_OK | MB_ICONINFORMATION);
392 break;
393 }
394 case ID_EXIT:
395 PostQuitMessage(0);
396 break;
397 }
398 return 0;
399
400 case WM_TIMER: {
401 // Timer for VU meter updates
402 if (g_radio.power) {
403 InvalidateRect(hwnd, NULL, FALSE);
404 }
405 return 0;
406 }
407 }
408
409 return DefWindowProc(hwnd, uMsg, wParam, lParam);
410}
411
412void DrawRadioInterface(HDC hdc, RECT* rect) {
413 // Create vintage radio background
414 HBRUSH woodBrush = CreateSolidBrush(RGB(101, 67, 33));
415 FillRect(hdc, rect, woodBrush);
416 DeleteObject(woodBrush);
417
418 // Draw radio panel (darker inset)
419 RECT panel = {50, 50, rect->right - 50, rect->bottom - 50};
420 HBRUSH panelBrush = CreateSolidBrush(RGB(80, 50, 25));
421 FillRect(hdc, &panel, panelBrush);
422 DeleteObject(panelBrush);
423
424 // Draw panel border (raised effect)
425 HPEN lightPen = CreatePen(PS_SOLID, 2, RGB(140, 100, 60));
426 HPEN darkPen = CreatePen(PS_SOLID, 2, RGB(40, 25, 15));
427
428 SelectObject(hdc, lightPen);
429 MoveToEx(hdc, panel.left, panel.bottom, NULL);
430 LineTo(hdc, panel.left, panel.top);
431 LineTo(hdc, panel.right, panel.top);
432
433 SelectObject(hdc, darkPen);
434 LineTo(hdc, panel.right, panel.bottom);
435 LineTo(hdc, panel.left, panel.bottom);
436
437 DeleteObject(lightPen);
438 DeleteObject(darkPen);
439
440 // Draw frequency display
441 DrawFrequencyDisplay(hdc, 200, 80, g_radio.frequency);
442
443 // Draw tuning dial
444 DrawTuningDial(hdc, 150, 200, 60, g_radio.frequency);
445
446 // Draw volume knob
447 DrawVolumeKnob(hdc, 350, 200, 30, g_radio.volume);
448
449 // Draw signal meter
450 DrawSignalMeter(hdc, 450, 150, g_radio.signalStrength);
451
452 // Draw VU meter
453 DrawVUMeter(hdc, 450, 180, g_audio.vuLevelLeft, g_audio.vuLevelRight);
454
455 // Draw power button
456 DrawPowerButton(hdc, 500, 120, 25, g_radio.power);
457
458 // Draw station info if tuned to a station
459 RadioStation* currentStation = FindNearestStation(g_radio.frequency);
460 if (currentStation && g_radio.signalStrength > 30) {
461 RECT stationRect = {80, 320, 520, 360};
462 HBRUSH stationBrush = CreateSolidBrush(RGB(0, 0, 0));
463 FillRect(hdc, &stationRect, stationBrush);
464 DeleteObject(stationBrush);
465
466 HPEN stationPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
467 SelectObject(hdc, stationPen);
468 Rectangle(hdc, stationRect.left, stationRect.top, stationRect.right, stationRect.bottom);
469 DeleteObject(stationPen);
470
471 SetTextColor(hdc, RGB(0, 255, 0));
472 HFONT stationFont = CreateFont(12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
473 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
474 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
475 DEFAULT_PITCH | FF_SWISS, "Arial");
476 SelectObject(hdc, stationFont);
477
478 char stationText[256];
479 sprintf(stationText, "%.3f MHz - %s: %s",
480 currentStation->frequency, currentStation->name, currentStation->description);
481
482 SetTextAlign(hdc, TA_LEFT);
483 TextOut(hdc, stationRect.left + 5, stationRect.top + 5, stationText, strlen(stationText));
484
485 DeleteObject(stationFont);
486 }
487
488 // Draw labels
489 SetBkMode(hdc, TRANSPARENT);
490 SetTextColor(hdc, RGB(255, 255, 255));
491 HFONT font = CreateFont(14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
492 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
493 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
494 DEFAULT_PITCH | FF_SWISS, "Arial");
495 SelectObject(hdc, font);
496
497 TextOut(hdc, 180, 300, "TUNING", 6);
498 TextOut(hdc, 330, 260, "VOLUME", 6);
499 TextOut(hdc, 430, 200, "SIGNAL", 6);
500 TextOut(hdc, 485, 160, "POWER", 5);
501
502 DeleteObject(font);
503}
504
505void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency) {
506 // Draw display background (black LCD style)
507 RECT display = {x - 80, y - 20, x + 80, y + 20};
508 HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
509 FillRect(hdc, &display, blackBrush);
510 DeleteObject(blackBrush);
511
512 // Draw display border
513 HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
514 SelectObject(hdc, borderPen);
515 Rectangle(hdc, display.left, display.top, display.right, display.bottom);
516 DeleteObject(borderPen);
517
518 // Draw frequency text
519 char freqText[32];
520 sprintf(freqText, "%.3f MHz", frequency);
521
522 SetBkMode(hdc, TRANSPARENT);
523 SetTextColor(hdc, RGB(0, 255, 0)); // Green LCD color
524 HFONT lcdFont = CreateFont(16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
525 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
526 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
527 FIXED_PITCH | FF_MODERN, "Courier New");
528 SelectObject(hdc, lcdFont);
529
530 SetTextAlign(hdc, TA_CENTER);
531 TextOut(hdc, x, y - 8, freqText, strlen(freqText));
532
533 DeleteObject(lcdFont);
534}
535
536void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency) {
537 // Draw dial background
538 HBRUSH dialBrush = CreateSolidBrush(RGB(160, 120, 80));
539 SelectObject(hdc, dialBrush);
540 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
541 DeleteObject(dialBrush);
542
543 // Draw dial border
544 HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(60, 40, 20));
545 SelectObject(hdc, borderPen);
546 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
547 DeleteObject(borderPen);
548
549 // Draw tick marks and frequency markings (270 degree sweep)
550 HPEN tickPen = CreatePen(PS_SOLID, 1, RGB(60, 40, 20));
551 SelectObject(hdc, tickPen);
552
553 // Draw major tick marks and frequency labels
554 SetTextColor(hdc, RGB(0, 0, 0));
555 HFONT smallFont = CreateFont(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
556 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
557 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
558 DEFAULT_PITCH | FF_SWISS, "Arial");
559 SelectObject(hdc, smallFont);
560 SetTextAlign(hdc, TA_CENTER);
561
562 // 270 degree sweep from -135 to +135 degrees
563 for (int i = 0; i < 12; i++) {
564 float angle = -3.14159f * 0.75f + (float)i * (3.14159f * 1.5f) / 11.0f;
565
566 // Major tick marks
567 int tickStartX = x + (int)((radius - 8) * cos(angle));
568 int tickStartY = y + (int)((radius - 8) * sin(angle));
569 int tickEndX = x + (int)((radius - 2) * cos(angle));
570 int tickEndY = y + (int)((radius - 2) * sin(angle));
571 MoveToEx(hdc, tickStartX, tickStartY, NULL);
572 LineTo(hdc, tickEndX, tickEndY);
573
574 // Frequency labels
575 int markX = x + (int)((radius - 18) * cos(angle));
576 int markY = y + (int)((radius - 18) * sin(angle));
577 char mark[8];
578 sprintf(mark, "%d", 10 + i * 2);
579 TextOut(hdc, markX, markY - 5, mark, strlen(mark));
580 }
581
582 // Draw minor tick marks between major ones
583 for (int i = 0; i < 11; i++) {
584 float angle = -3.14159f * 0.75f + ((float)i + 0.5f) * (3.14159f * 1.5f) / 11.0f;
585 int tickStartX = x + (int)((radius - 5) * cos(angle));
586 int tickStartY = y + (int)((radius - 5) * sin(angle));
587 int tickEndX = x + (int)((radius - 2) * cos(angle));
588 int tickEndY = y + (int)((radius - 2) * sin(angle));
589 MoveToEx(hdc, tickStartX, tickStartY, NULL);
590 LineTo(hdc, tickEndX, tickEndY);
591 }
592
593 // Draw range limit markers at start and end positions
594 HPEN limitPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
595 SelectObject(hdc, limitPen);
596
597 // Start position (-135 degrees, 10 MHz)
598 float startAngle = -3.14159f * 0.75f;
599 int startX = x + (int)((radius - 12) * cos(startAngle));
600 int startY = y + (int)((radius - 12) * sin(startAngle));
601 int startEndX = x + (int)(radius * cos(startAngle));
602 int startEndY = y + (int)(radius * sin(startAngle));
603 MoveToEx(hdc, startX, startY, NULL);
604 LineTo(hdc, startEndX, startEndY);
605
606 // End position (+135 degrees, 34 MHz)
607 float endAngle = 3.14159f * 0.75f;
608 int endX = x + (int)((radius - 12) * cos(endAngle));
609 int endY = y + (int)((radius - 12) * sin(endAngle));
610 int endEndX = x + (int)(radius * cos(endAngle));
611 int endEndY = y + (int)(radius * sin(endAngle));
612 MoveToEx(hdc, endX, endY, NULL);
613 LineTo(hdc, endEndX, endEndY);
614
615 DeleteObject(tickPen);
616 DeleteObject(limitPen);
617
618 // Draw pointer based on frequency (270 degree sweep)
619 float normalizedFreq = (frequency - 10.0f) / 24.0f;
620 float angle = -3.14159f * 0.75f + normalizedFreq * (3.14159f * 1.5f);
621 int pointerX = x + (int)((radius - 10) * cos(angle));
622 int pointerY = y + (int)((radius - 10) * sin(angle));
623
624 HPEN pointerPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
625 SelectObject(hdc, pointerPen);
626 MoveToEx(hdc, x, y, NULL);
627 LineTo(hdc, pointerX, pointerY);
628 DeleteObject(pointerPen);
629
630 DeleteObject(smallFont);
631}
632
633void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume) {
634 // Draw knob background
635 HBRUSH knobBrush = CreateSolidBrush(RGB(140, 100, 60));
636 SelectObject(hdc, knobBrush);
637 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
638 DeleteObject(knobBrush);
639
640 // Draw knob border
641 HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(60, 40, 20));
642 SelectObject(hdc, borderPen);
643 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
644 DeleteObject(borderPen);
645
646 // Draw volume indicator
647 float angle = volume * 3.14159f * 1.5f - 3.14159f * 0.75f;
648 int indicatorX = x + (int)((radius - 5) * cos(angle));
649 int indicatorY = y + (int)((radius - 5) * sin(angle));
650
651 HPEN indicatorPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
652 SelectObject(hdc, indicatorPen);
653 MoveToEx(hdc, x, y, NULL);
654 LineTo(hdc, indicatorX, indicatorY);
655 DeleteObject(indicatorPen);
656}
657
658void DrawSignalMeter(HDC hdc, int x, int y, int strength) {
659 // Draw meter background
660 RECT meter = {x, y, x + 80, y + 20};
661 HBRUSH meterBrush = CreateSolidBrush(RGB(0, 0, 0));
662 FillRect(hdc, &meter, meterBrush);
663 DeleteObject(meterBrush);
664
665 // Draw meter border
666 HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
667 SelectObject(hdc, borderPen);
668 Rectangle(hdc, meter.left, meter.top, meter.right, meter.bottom);
669 DeleteObject(borderPen);
670
671 // Draw signal bars
672 int barWidth = 8;
673 int numBars = strength / 10;
674 for (int i = 0; i < numBars && i < 10; i++) {
675 RECT bar = {x + 2 + i * barWidth, y + 2,
676 x + 2 + (i + 1) * barWidth - 1, y + 18};
677
678 COLORREF barColor;
679 if (i < 3) barColor = RGB(0, 255, 0); // Green
680 else if (i < 7) barColor = RGB(255, 255, 0); // Yellow
681 else barColor = RGB(255, 0, 0); // Red
682
683 HBRUSH barBrush = CreateSolidBrush(barColor);
684 FillRect(hdc, &bar, barBrush);
685 DeleteObject(barBrush);
686 }
687}
688
689void DrawVUMeter(HDC hdc, int x, int y, float leftLevel, float rightLevel) {
690 // Draw VU meter background
691 RECT meterBg = {x, y, x + 80, y + 40};
692 HBRUSH bgBrush = CreateSolidBrush(RGB(20, 20, 20));
693 FillRect(hdc, &meterBg, bgBrush);
694 DeleteObject(bgBrush);
695
696 // Draw border
697 HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));
698 SelectObject(hdc, borderPen);
699 Rectangle(hdc, meterBg.left, meterBg.top, meterBg.right, meterBg.bottom);
700 DeleteObject(borderPen);
701
702 // Draw "VU" label
703 SetTextColor(hdc, RGB(200, 200, 200));
704 SetBkMode(hdc, TRANSPARENT);
705 HFONT smallFont = CreateFont(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
706 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
707 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
708 DEFAULT_PITCH | FF_SWISS, "Arial");
709 SelectObject(hdc, smallFont);
710 TextOut(hdc, x + 2, y + 2, "VU", 2);
711
712 // Draw left channel meter
713 int leftWidth = (int)(leftLevel * 70);
714 if (leftWidth > 0) {
715 RECT leftBar = {x + 5, y + 12, x + 5 + leftWidth, y + 18};
716 COLORREF leftColor = leftLevel > 0.8f ? RGB(255, 0, 0) :
717 leftLevel > 0.6f ? RGB(255, 255, 0) : RGB(0, 255, 0);
718 HBRUSH leftBrush = CreateSolidBrush(leftColor);
719 FillRect(hdc, &leftBar, leftBrush);
720 DeleteObject(leftBrush);
721 }
722
723 // Draw right channel meter
724 int rightWidth = (int)(rightLevel * 70);
725 if (rightWidth > 0) {
726 RECT rightBar = {x + 5, y + 22, x + 5 + rightWidth, y + 28};
727 COLORREF rightColor = rightLevel > 0.8f ? RGB(255, 0, 0) :
728 rightLevel > 0.6f ? RGB(255, 255, 0) : RGB(0, 255, 0);
729 HBRUSH rightBrush = CreateSolidBrush(rightColor);
730 FillRect(hdc, &rightBar, rightBrush);
731 DeleteObject(rightBrush);
732 }
733
734 // Draw channel labels
735 TextOut(hdc, x + 77, y + 10, "L", 1);
736 TextOut(hdc, x + 77, y + 20, "R", 1);
737
738 // Draw scale marks
739 HPEN scalePen = CreatePen(PS_SOLID, 1, RGB(80, 80, 80));
740 SelectObject(hdc, scalePen);
741 for (int i = 1; i < 10; i++) {
742 int markX = x + 5 + (i * 7);
743 MoveToEx(hdc, markX, y + 30, NULL);
744 LineTo(hdc, markX, y + 32);
745 }
746 DeleteObject(scalePen);
747 DeleteObject(smallFont);
748}
749
750void DrawPowerButton(HDC hdc, int x, int y, int radius, int power) {
751 // Draw button background
752 COLORREF buttonColor = power ? RGB(255, 0, 0) : RGB(100, 100, 100);
753 HBRUSH buttonBrush = CreateSolidBrush(buttonColor);
754 SelectObject(hdc, buttonBrush);
755 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
756 DeleteObject(buttonBrush);
757
758 // Draw button border
759 HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(60, 60, 60));
760 SelectObject(hdc, borderPen);
761 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
762 DeleteObject(borderPen);
763
764 // Draw power symbol
765 if (power) {
766 HPEN symbolPen = CreatePen(PS_SOLID, 3, RGB(255, 255, 255));
767 SelectObject(hdc, symbolPen);
768
769 // Draw power symbol (circle with line)
770 Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6);
771 MoveToEx(hdc, x, y - 10, NULL);
772 LineTo(hdc, x, y - 2);
773
774 DeleteObject(symbolPen);
775 }
776}
777
778int IsPointInCircle(int px, int py, int cx, int cy, int radius) {
779 int dx = px - cx;
780 int dy = py - cy;
781 return (dx * dx + dy * dy) <= (radius * radius);
782}
783
784float GetAngleFromPoint(int px, int py, int cx, int cy) {
785 return atan2((float)(py - cy), (float)(px - cx));
786}
787
788void UpdateFrequencyFromMouse(int mouseX, int mouseY) {
789 float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200);
790
791 // Convert angle to frequency (10-34 MHz range)
792 // Map 270 degree sweep from -135 to +135 degrees
793 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
794
795 // Clamp to valid range
796 if (normalizedAngle < 0.0f) normalizedAngle = 0.0f;
797 if (normalizedAngle > 1.0f) normalizedAngle = 1.0f;
798
799 // Map to frequency range
800 g_radio.frequency = 10.0f + normalizedAngle * 24.0f;
801
802 // Clamp frequency
803 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
804 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
805
806 // Calculate signal strength based on nearest station
807 RadioStation* nearestStation = FindNearestStation(g_radio.frequency);
808 if (nearestStation) {
809 g_radio.signalStrength = (int)(GetStationSignalStrength(nearestStation, g_radio.frequency) * 100.0f);
810
811 // Start streaming if signal is strong enough and station changed
812 if (g_radio.signalStrength > 50 && nearestStation != g_audio.currentStation) {
813 StopBassStreaming();
814 StartBassStreaming(nearestStation);
815 }
816 } else {
817 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
818 StopBassStreaming();
819 }
820
821 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
822 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
823
824 UpdateStaticVolume(g_radio.signalStrength);
825}
826
827void UpdateVolumeFromMouse(int mouseX, int mouseY) {
828 float angle = GetAngleFromPoint(mouseX, mouseY, 350, 200);
829
830 // Convert angle to volume (0-1 range)
831 // Map from -135 degrees to +135 degrees
832 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
833
834 g_radio.volume = normalizedAngle;
835
836 // Clamp volume
837 if (g_radio.volume < 0.0f) g_radio.volume = 0.0f;
838 if (g_radio.volume > 1.0f) g_radio.volume = 1.0f;
839
840 // Update static volume when main volume changes
841 UpdateStaticVolume(g_radio.signalStrength);
842}
843
844int InitializeAudio() {
845 // Initialize BASS with more detailed error reporting
846 printf("Initializing BASS audio system...\n");
847
848 if (!BASS_Init(-1, 44100, 0, 0, NULL)) {
849 DWORD error = BASS_ErrorGetCode();
850 printf("BASS initialization failed (Error: %lu)\n", error);
851
852 // Try alternative initialization methods
853 printf("Trying alternative audio device...\n");
854 if (!BASS_Init(0, 44100, 0, 0, NULL)) {
855 error = BASS_ErrorGetCode();
856 printf("Alternative BASS init also failed (Error: %lu)\n", error);
857 printf("BASS Error meanings:\n");
858 printf(" 1 = BASS_ERROR_MEM (memory error)\n");
859 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL error)\n");
860 printf(" 3 = BASS_ERROR_DRIVER (no audio driver)\n");
861 printf(" 8 = BASS_ERROR_ALREADY (already initialized)\n");
862 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n");
863 return -1;
864 }
865 }
866
867 printf("BASS initialized successfully\n");
868
869 // Get BASS version info
870 DWORD version = BASS_GetVersion();
871 printf("BASS version: %d.%d.%d.%d\n",
872 HIBYTE(HIWORD(version)), LOBYTE(HIWORD(version)),
873 HIBYTE(LOWORD(version)), LOBYTE(LOWORD(version)));
874
875 g_audio.currentStream = 0;
876 g_audio.staticStream = 0;
877 g_audio.isPlaying = 0;
878 g_audio.staticVolume = 0.8f;
879 g_audio.radioVolume = 0.0f;
880 g_audio.currentStation = NULL;
881 g_audio.vuLevelLeft = 0.0f;
882 g_audio.vuLevelRight = 0.0f;
883
884 return 0;
885}
886
887void CleanupAudio() {
888 StopBassStreaming();
889
890 // Free BASS
891 BASS_Free();
892 printf("BASS cleaned up\n");
893}
894
895void StartAudio() {
896 if (!g_audio.isPlaying) {
897 g_audio.isPlaying = 1;
898 StartStaticNoise();
899 printf("Audio started with static\n");
900 }
901}
902
903void StopAudio() {
904 if (g_audio.isPlaying) {
905 g_audio.isPlaying = 0;
906 StopBassStreaming();
907 StopStaticNoise();
908 printf("Audio stopped\n");
909 }
910}
911
912RadioStation* FindNearestStation(float frequency) {
913 RadioStation* nearest = NULL;
914 float minDistance = 999.0f;
915
916 for (int i = 0; i < NUM_STATIONS; i++) {
917 float distance = fabs(g_stations[i].frequency - frequency);
918 if (distance < minDistance) {
919 minDistance = distance;
920 nearest = &g_stations[i];
921 }
922 }
923
924 // Only return station if we're close enough (within 0.5 MHz)
925 if (minDistance <= 0.5f) {
926 return nearest;
927 }
928
929 return NULL;
930}
931
932float GetStationSignalStrength(RadioStation* station, float currentFreq) {
933 if (!station) return 0.0f;
934
935 float distance = fabs(station->frequency - currentFreq);
936
937 // Signal strength drops off with distance from exact frequency
938 if (distance < 0.05f) {
939 return 0.9f; // Very strong signal
940 } else if (distance < 0.1f) {
941 return 0.7f; // Strong signal
942 } else if (distance < 0.2f) {
943 return 0.5f; // Medium signal
944 } else if (distance < 0.5f) {
945 return 0.2f; // Weak signal
946 }
947
948 return 0.0f; // No signal
949}
950
951int StartBassStreaming(RadioStation* station) {
952 if (!station) {
953 printf("StartBassStreaming failed: no station\n");
954 return 0;
955 }
956
957 StopBassStreaming();
958
959 printf("Attempting to stream: %s at %s\n", station->name, station->streamUrl);
960
961 // Check if BASS is initialized
962 if (!BASS_GetVersion()) {
963 printf("BASS not initialized - cannot stream\n");
964 return 0;
965 }
966
967 // Create BASS stream from URL with more options
968 printf("Creating BASS stream...\n");
969 g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0,
970 BASS_STREAM_BLOCK | BASS_STREAM_STATUS | BASS_STREAM_AUTOFREE, NULL, 0);
971
972 if (g_audio.currentStream) {
973 printf("Successfully connected to stream: %s\n", station->name);
974
975 // Get stream info
976 BASS_CHANNELINFO info;
977 if (BASS_ChannelGetInfo(g_audio.currentStream, &info)) {
978 printf("Stream info: %lu Hz, %lu channels, type=%lu\n",
979 info.freq, info.chans, info.ctype);
980 }
981
982 // Set volume based on signal strength and radio volume
983 float volume = g_radio.volume * (g_radio.signalStrength / 100.0f);
984 BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume);
985 printf("Set volume to: %.2f\n", volume);
986
987 // Start playing
988 if (BASS_ChannelPlay(g_audio.currentStream, FALSE)) {
989 printf("Stream playback started\n");
990 } else {
991 DWORD error = BASS_ErrorGetCode();
992 printf("Failed to start playback (BASS Error: %lu)\n", error);
993 }
994
995 g_audio.currentStation = station;
996 return 1;
997 } else {
998 DWORD error = BASS_ErrorGetCode();
999 printf("Failed to connect to stream: %s (BASS Error: %lu)\n", station->name, error);
1000 printf("BASS Error meanings:\n");
1001 printf(" 1 = BASS_ERROR_MEM (out of memory)\n");
1002 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL cannot be opened)\n");
1003 printf(" 3 = BASS_ERROR_DRIVER (no audio driver available)\n");
1004 printf(" 6 = BASS_ERROR_FORMAT (unsupported format)\n");
1005 printf(" 7 = BASS_ERROR_POSITION (invalid position)\n");
1006 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n");
1007 printf(" 21 = BASS_ERROR_TIMEOUT (connection timeout)\n");
1008 printf(" 41 = BASS_ERROR_SSL (SSL/HTTPS not supported)\n");
1009 }
1010
1011 return 0;
1012}
1013
1014void StopBassStreaming() {
1015 if (g_audio.currentStream) {
1016 BASS_StreamFree(g_audio.currentStream);
1017 g_audio.currentStream = 0;
1018 printf("Stopped streaming\n");
1019 }
1020
1021 g_audio.currentStation = NULL;
1022}
1023
1024// Static noise generation callback
1025DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user) {
1026 short* samples = (short*)buffer;
1027 DWORD sampleCount = length / sizeof(short);
1028
1029 // Get current time for oscillation
1030 static DWORD startTime = GetTickCount();
1031 DWORD currentTime = GetTickCount();
1032 float timeSeconds = (currentTime - startTime) / 1000.0f;
1033
1034 // Create subtle volume oscillations (5-7% variation)
1035 // Use multiple sine waves at different frequencies for natural variation
1036 float oscillation1 = sin(timeSeconds * 0.7f) * 0.03f; // 3% slow oscillation
1037 float oscillation2 = sin(timeSeconds * 2.3f) * 0.02f; // 2% medium oscillation
1038 float oscillation3 = sin(timeSeconds * 5.1f) * 0.015f; // 1.5% fast oscillation
1039 float volumeVariation = 1.0f + oscillation1 + oscillation2 + oscillation3;
1040
1041 // Generate white noise with volume variation
1042 for (DWORD i = 0; i < sampleCount; i++) {
1043 // Generate random value between -32767 and 32767
1044 short baseNoise = (short)((rand() % 65535) - 32767);
1045
1046 // Apply volume variation
1047 samples[i] = (short)(baseNoise * volumeVariation);
1048 }
1049
1050 return length;
1051}
1052
1053void StartStaticNoise() {
1054 if (!g_audio.staticStream) {
1055 // Create a stream for static noise generation
1056 g_audio.staticStream = BASS_StreamCreate(SAMPLE_RATE, CHANNELS, 0, StaticStreamProc, NULL);
1057
1058 if (g_audio.staticStream) {
1059 // Set initial volume based on signal strength
1060 UpdateStaticVolume(g_radio.signalStrength);
1061
1062 // Start playing static
1063 BASS_ChannelPlay(g_audio.staticStream, FALSE);
1064 printf("Static noise started\n");
1065 } else {
1066 printf("Failed to create static stream\n");
1067 }
1068 }
1069}
1070
1071void StopStaticNoise() {
1072 if (g_audio.staticStream) {
1073 BASS_StreamFree(g_audio.staticStream);
1074 g_audio.staticStream = 0;
1075 printf("Static noise stopped\n");
1076 }
1077}
1078
1079void UpdateStaticVolume(float signalStrength) {
1080 if (g_audio.staticStream) {
1081 // Static volume is inverse of signal strength
1082 // Strong signal = less static, weak signal = more static
1083 float staticLevel = (100.0f - signalStrength) / 100.0f;
1084 float volume = g_radio.volume * staticLevel * g_audio.staticVolume;
1085
1086 // Ensure minimum static when radio is on but no strong signal
1087 if (g_radio.power && signalStrength < 50.0f) {
1088 volume = fmax(volume, g_radio.volume * 0.1f);
1089 }
1090
1091 BASS_ChannelSetAttribute(g_audio.staticStream, BASS_ATTRIB_VOL, volume);
1092 }
1093}
1094
1095void UpdateVULevels() {
1096 // Initialize levels to zero
1097 g_audio.vuLevelLeft = 0.0f;
1098 g_audio.vuLevelRight = 0.0f;
1099
1100 // Get levels from current stream if playing
1101 if (g_audio.currentStream && BASS_ChannelIsActive(g_audio.currentStream) == BASS_ACTIVE_PLAYING) {
1102 DWORD level = BASS_ChannelGetLevel(g_audio.currentStream);
1103 if (level != -1) {
1104 // Extract left and right channel levels
1105 g_audio.vuLevelLeft = (float)LOWORD(level) / 32768.0f;
1106 g_audio.vuLevelRight = (float)HIWORD(level) / 32768.0f;
1107 }
1108 }
1109
1110 // Add static contribution if static is playing
1111 if (g_audio.staticStream && BASS_ChannelIsActive(g_audio.staticStream) == BASS_ACTIVE_PLAYING) {
1112 DWORD staticLevel = BASS_ChannelGetLevel(g_audio.staticStream);
1113 if (staticLevel != -1) {
1114 float staticLeft = (float)LOWORD(staticLevel) / 32768.0f;
1115 float staticRight = (float)HIWORD(staticLevel) / 32768.0f;
1116
1117 // Combine with existing levels (simulate mixing)
1118 g_audio.vuLevelLeft = fmin(1.0f, g_audio.vuLevelLeft + staticLeft * 0.3f);
1119 g_audio.vuLevelRight = fmin(1.0f, g_audio.vuLevelRight + staticRight * 0.3f);
1120 }
1121 }
1122
1123 // Apply some smoothing/decay for more realistic VU behavior
1124 static float lastLeft = 0.0f, lastRight = 0.0f;
1125 g_audio.vuLevelLeft = g_audio.vuLevelLeft * 0.7f + lastLeft * 0.3f;
1126 g_audio.vuLevelRight = g_audio.vuLevelRight * 0.7f + lastRight * 0.3f;
1127 lastLeft = g_audio.vuLevelLeft;
1128 lastRight = g_audio.vuLevelRight;
1129}