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#define ID_TOGGLE_CONSOLE 1003
15
16// Radio control IDs
17#define ID_TUNING_DIAL 2001
18#define ID_VOLUME_KNOB 2002
19#define ID_POWER_BUTTON 2003
20
21// Radio station data
22typedef struct {
23 float frequency;
24 char name[64];
25 char description[128];
26 char streamUrl[256];
27} RadioStation;
28
29RadioStation g_stations[] = {
30 {10.230f, "SomaFM Groove", "Downtempo and chillout", "http://ice1.somafm.com/groovesalad-128-mp3"},
31 {11.470f, "WBGO Jazz88", "Jazz from Newark", "http://wbgo.streamguys.net/wbgo128"},
32 {12.650f, "Radio Paradise", "Eclectic music mix", "http://stream.radioparadise.com/mp3-128"},
33 {13.890f, "Classical Music", "Classical radio", "http://stream.wqxr.org/wqxr"},
34 {15.120f, "Jazz Radio", "Smooth jazz", "http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3"},
35 {16.350f, "FIP", "Eclectic French radio", "http://direct.fipradio.fr/live/fip-midfi.mp3"},
36 {18.810f, "TSF Jazz", "French jazz radio", "http://tsfjazz.ice.infomaniak.ch/tsfjazz-high.mp3"},
37 {20.040f, "Dublab", "Electronic and experimental", "http://dublab.out.airtime.pro:8000/dublab_a"},
38 {21.270f, "BBC World Service", "Global news and culture", "http://stream.live.vc.bbcmedia.co.uk/bbc_world_service"},
39 {23.730f, "WFMU", "Freeform experimental radio", "http://stream0.wfmu.org/freeform-128k"},
40 {24.960f, "ChillHop Music", "Lo-fi hip hop", "http://ice1.somafm.com/fluid"},
41 {27.420f, "Worldwide FM", "Global music discovery", "http://worldwidefm.out.airtime.pro:8000/worldwidefm_a"},
42};
43
44#define NUM_STATIONS (sizeof(g_stations) / sizeof(RadioStation))
45#define SAMPLE_RATE 44100
46#define BITS_PER_SAMPLE 16
47#define CHANNELS 1
48#define BUFFER_SIZE 4410 // 0.1 seconds of audio
49#define NUM_BUFFERS 4
50
51// Audio state
52typedef struct {
53 // BASS handles
54 HSTREAM currentStream;
55 HSTREAM staticStream;
56 int isPlaying;
57 float staticVolume;
58 float radioVolume;
59
60 // Station tracking
61 RadioStation* currentStation;
62
63 // VU meter levels (0.0 to 1.0)
64 float vuLevelLeft;
65 float vuLevelRight;
66} AudioState;
67
68// Radio state
69typedef struct {
70 float frequency;
71 float volume;
72 int power;
73 int signalStrength;
74 int isDraggingDial;
75 int isDraggingVolume;
76} RadioState;
77
78// Global console state
79int g_consoleVisible = 0;
80HWND g_consoleWindow = NULL;
81
82RadioState g_radio = {14.230f, 0.8f, 0, 0, 0, 0}; // Increase default volume to 0.8
83AudioState g_audio = {0};
84
85LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
86void DrawRadioInterface(HDC hdc, RECT* rect);
87void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency);
88void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency);
89void DrawSignalMeter(HDC hdc, int x, int y, int strength);
90void DrawVUMeter(HDC hdc, int x, int y, float leftLevel, float rightLevel);
91void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume);
92void DrawPowerButton(HDC hdc, int x, int y, int radius, int power);
93int IsPointInCircle(int px, int py, int cx, int cy, int radius);
94float GetAngleFromPoint(int px, int py, int cx, int cy);
95void UpdateFrequencyFromMouse(int mouseX, int mouseY);
96void UpdateVolumeFromMouse(int mouseX, int mouseY);
97
98// Audio functions
99int InitializeAudio();
100void CleanupAudio();
101void StartAudio();
102void StopAudio();
103RadioStation* FindNearestStation(float frequency);
104float GetStationSignalStrength(RadioStation* station, float currentFreq);
105
106// BASS streaming functions
107int StartBassStreaming(RadioStation* station);
108void StopBassStreaming();
109
110// Static noise functions
111DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user);
112void StartStaticNoise();
113void StopStaticNoise();
114void UpdateStaticVolume(float signalStrength);
115void UpdateStreamVolume();
116
117// VU meter functions
118void UpdateVULevels();
119
120int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
121 // Don't allocate console by default - will be toggled via menu
122
123 const char* CLASS_NAME = "ShortwaveRadio";
124
125 WNDCLASS wc = {};
126 wc.lpfnWndProc = WindowProc;
127 wc.hInstance = hInstance;
128 wc.lpszClassName = CLASS_NAME;
129 wc.hbrBackground = CreateSolidBrush(RGB(24, 24, 24)); // Dark Winamp-style background
130 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
131
132 RegisterClass(&wc);
133
134 HWND hwnd = CreateWindow(
135 CLASS_NAME,
136 "Shortwave Radio Tuner",
137 WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, // Fixed size
138 CW_USEDEFAULT, CW_USEDEFAULT, 600, 450,
139 NULL,
140 NULL,
141 hInstance,
142 NULL
143 );
144
145 if (hwnd == NULL) {
146 return 0;
147 }
148
149 // Initialize audio system
150 if (InitializeAudio() != 0) {
151 MessageBox(hwnd, "Failed to initialize audio system", "Error", MB_OK | MB_ICONERROR);
152 return 0;
153 }
154
155 // Audio starts when power button is pressed
156
157 // Create menu
158 HMENU hMenu = CreateMenu();
159
160 // Radio menu
161 HMENU hRadioMenu = CreatePopupMenu();
162 AppendMenu(hRadioMenu, MF_STRING, ID_TOGGLE_CONSOLE, "&Debug Console");
163 AppendMenu(hRadioMenu, MF_SEPARATOR, 0, NULL);
164 AppendMenu(hRadioMenu, MF_STRING, ID_ABOUT, "&About");
165 AppendMenu(hRadioMenu, MF_SEPARATOR, 0, NULL);
166 AppendMenu(hRadioMenu, MF_STRING, ID_EXIT, "E&xit");
167 AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT_PTR)hRadioMenu, "&Radio");
168
169 SetMenu(hwnd, hMenu);
170
171 ShowWindow(hwnd, nCmdShow);
172 UpdateWindow(hwnd);
173
174 // Set timer for VU meter updates (30 FPS)
175 SetTimer(hwnd, 1, 33, NULL);
176
177 MSG msg = {};
178 while (GetMessage(&msg, NULL, 0, 0)) {
179 TranslateMessage(&msg);
180 DispatchMessage(&msg);
181 }
182
183 // Cleanup audio
184 StopAudio();
185 CleanupAudio();
186
187 // Cleanup console if it exists
188 if (g_consoleWindow) {
189 FreeConsole();
190 }
191
192 return 0;
193}
194
195LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
196 switch (uMsg) {
197 case WM_SIZE:
198 InvalidateRect(hwnd, NULL, TRUE);
199 return 0;
200
201 case WM_DESTROY:
202 PostQuitMessage(0);
203 return 0;
204
205 case WM_PAINT: {
206 PAINTSTRUCT ps;
207 HDC hdc = BeginPaint(hwnd, &ps);
208
209 RECT rect;
210 GetClientRect(hwnd, &rect);
211
212 // Create double buffer to eliminate flicker
213 HDC memDC = CreateCompatibleDC(hdc);
214 HBITMAP memBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
215 HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
216
217 // Update VU levels before drawing
218 if (g_radio.power) {
219 UpdateVULevels();
220 }
221
222 DrawRadioInterface(memDC, &rect);
223
224 // Copy from memory DC to screen
225 BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
226
227 // Cleanup
228 SelectObject(memDC, oldBitmap);
229 DeleteObject(memBitmap);
230 DeleteDC(memDC);
231
232 EndPaint(hwnd, &ps);
233 return 0;
234 }
235
236 case WM_LBUTTONDOWN: {
237 int mouseX = LOWORD(lParam);
238 int mouseY = HIWORD(lParam);
239
240 // Check if clicking on tuning dial
241 if (IsPointInCircle(mouseX, mouseY, 150, 200, 60)) {
242 g_radio.isDraggingDial = 1;
243 SetCapture(hwnd);
244 UpdateFrequencyFromMouse(mouseX, mouseY);
245 InvalidateRect(hwnd, NULL, TRUE);
246 }
247 // Check if clicking on volume knob
248 else if (IsPointInCircle(mouseX, mouseY, 350, 200, 30)) {
249 g_radio.isDraggingVolume = 1;
250 SetCapture(hwnd);
251 UpdateVolumeFromMouse(mouseX, mouseY);
252 InvalidateRect(hwnd, NULL, TRUE);
253 }
254 // Check if clicking on power button
255 else if (IsPointInCircle(mouseX, mouseY, 500, 120, 25)) {
256 g_radio.power = !g_radio.power;
257 if (g_radio.power) {
258 StartAudio();
259 } else {
260 StopAudio();
261 }
262 InvalidateRect(hwnd, NULL, TRUE);
263 }
264 return 0;
265 }
266
267 case WM_LBUTTONUP: {
268 if (g_radio.isDraggingDial || g_radio.isDraggingVolume) {
269 g_radio.isDraggingDial = 0;
270 g_radio.isDraggingVolume = 0;
271 ReleaseCapture();
272 }
273 return 0;
274 }
275
276 case WM_MOUSEMOVE: {
277 if (g_radio.isDraggingDial) {
278 int mouseX = LOWORD(lParam);
279 int mouseY = HIWORD(lParam);
280 UpdateFrequencyFromMouse(mouseX, mouseY);
281 InvalidateRect(hwnd, NULL, TRUE);
282 }
283 else if (g_radio.isDraggingVolume) {
284 int mouseX = LOWORD(lParam);
285 int mouseY = HIWORD(lParam);
286 UpdateVolumeFromMouse(mouseX, mouseY);
287 InvalidateRect(hwnd, NULL, TRUE);
288 }
289 return 0;
290 }
291
292 case WM_KEYDOWN: {
293 switch (wParam) {
294 case VK_UP: {
295 // Increase frequency by 0.1 MHz (fine tuning)
296 g_radio.frequency += 0.1f;
297 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
298
299 // Update signal strength for new frequency
300 RadioStation* station = FindNearestStation(g_radio.frequency);
301 if (station) {
302 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
303 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
304 StopBassStreaming();
305 StartBassStreaming(station);
306 }
307 } else {
308 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
309 StopBassStreaming();
310 }
311
312 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
313 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
314
315 UpdateStaticVolume(g_radio.signalStrength);
316 UpdateStreamVolume();
317 InvalidateRect(hwnd, NULL, TRUE);
318 break;
319 }
320
321 case VK_DOWN: {
322 // Decrease frequency by 0.1 MHz (fine tuning)
323 g_radio.frequency -= 0.1f;
324 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
325
326 // Update signal strength for new frequency
327 RadioStation* station = FindNearestStation(g_radio.frequency);
328 if (station) {
329 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
330 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
331 StopBassStreaming();
332 StartBassStreaming(station);
333 }
334 } else {
335 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
336 StopBassStreaming();
337 }
338
339 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
340 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
341
342 UpdateStaticVolume(g_radio.signalStrength);
343 UpdateStreamVolume();
344 InvalidateRect(hwnd, NULL, TRUE);
345 break;
346 }
347
348 case VK_RIGHT: {
349 // Increase frequency by 1.0 MHz (coarse tuning)
350 g_radio.frequency += 1.0f;
351 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
352
353 // Update signal strength for new frequency
354 RadioStation* station = FindNearestStation(g_radio.frequency);
355 if (station) {
356 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
357 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
358 StopBassStreaming();
359 StartBassStreaming(station);
360 }
361 } else {
362 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
363 StopBassStreaming();
364 }
365
366 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
367 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
368
369 UpdateStaticVolume(g_radio.signalStrength);
370 UpdateStreamVolume();
371 InvalidateRect(hwnd, NULL, TRUE);
372 break;
373 }
374
375 case VK_LEFT: {
376 // Decrease frequency by 1.0 MHz (coarse tuning)
377 g_radio.frequency -= 1.0f;
378 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
379
380 // Update signal strength for new frequency
381 RadioStation* station = FindNearestStation(g_radio.frequency);
382 if (station) {
383 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
384 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
385 StopBassStreaming();
386 StartBassStreaming(station);
387 }
388 } else {
389 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
390 StopBassStreaming();
391 }
392
393 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
394 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
395
396 UpdateStaticVolume(g_radio.signalStrength);
397 UpdateStreamVolume();
398 InvalidateRect(hwnd, NULL, TRUE);
399 break;
400 }
401 }
402 return 0;
403 }
404
405 case WM_COMMAND:
406 switch (LOWORD(wParam)) {
407 case ID_TOGGLE_CONSOLE: {
408 if (g_consoleVisible) {
409 // Hide console
410 if (g_consoleWindow) {
411 ShowWindow(g_consoleWindow, SW_HIDE);
412 }
413 g_consoleVisible = 0;
414 } else {
415 // Show console
416 if (!g_consoleWindow) {
417 // First time - allocate console
418 AllocConsole();
419 freopen("CONOUT$", "w", stdout);
420 freopen("CONOUT$", "w", stderr);
421 g_consoleWindow = GetConsoleWindow();
422 printf("Shortwave Radio Debug Console\n");
423 printf("=============================\n");
424 } else {
425 // Console exists, just show it
426 ShowWindow(g_consoleWindow, SW_SHOW);
427 }
428 g_consoleVisible = 1;
429 }
430 break;
431 }
432 case ID_ABOUT: {
433 const char* aboutText = "Shortwave Radio Tuner\n\n"
434 "Version: 1.0.0\n"
435 "Built for Rewind V2 Hackathon\n\n"
436 "A vintage shortwave radio simulator\n"
437 "compatible with Windows XP\n\n"
438 "Features:\n"
439 "- Realistic tuning interface\n"
440 "- Internet radio streaming\n"
441 "- Authentic static noise\n\n"
442 "Controls:\n"
443 "- Drag tuning dial to change frequency\n"
444 "- UP/DOWN arrows: Fine tuning (0.1 MHz)\n"
445 "- LEFT/RIGHT arrows: Coarse tuning (1.0 MHz)\n"
446 "- Click power button to turn on/off\n"
447 "- Drag volume knob to adjust volume";
448 MessageBox(hwnd, aboutText, "About Shortwave Radio",
449 MB_OK | MB_ICONINFORMATION);
450 break;
451 }
452 case ID_EXIT:
453 PostQuitMessage(0);
454 break;
455 }
456 return 0;
457
458 case WM_TIMER: {
459 // Timer for VU meter updates - only invalidate VU meter area
460 if (g_radio.power) {
461 RECT vuRect = {440, 190, 540, 250};
462 InvalidateRect(hwnd, &vuRect, FALSE);
463 }
464 return 0;
465 }
466 }
467
468 return DefWindowProc(hwnd, uMsg, wParam, lParam);
469}
470
471void DrawRadioInterface(HDC hdc, RECT* rect) {
472 // Winamp-style dark gradient background
473 HBRUSH darkBrush = CreateSolidBrush(RGB(24, 24, 24));
474 FillRect(hdc, rect, darkBrush);
475 DeleteObject(darkBrush);
476
477 // Main panel with simplified metallic gradient effect
478 RECT panel = {10, 10, rect->right - 10, rect->bottom - 10};
479
480 // Simplified gradient effect with fewer steps
481 for (int i = 0; i < 8; i++) {
482 int gray = 45 + i * 3;
483 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
484 RECT gradRect = {panel.left + i, panel.top + i, panel.right - i, panel.bottom - i};
485 FrameRect(hdc, &gradRect, gradBrush);
486 DeleteObject(gradBrush);
487 }
488
489 // Inner panel with darker metallic look
490 RECT innerPanel = {30, 30, rect->right - 30, rect->bottom - 30};
491 HBRUSH innerBrush = CreateSolidBrush(RGB(32, 32, 32));
492 FillRect(hdc, &innerPanel, innerBrush);
493 DeleteObject(innerBrush);
494
495 // Winamp-style beveled border
496 HPEN lightPen = CreatePen(PS_SOLID, 1, RGB(128, 128, 128));
497 HPEN darkPen = CreatePen(PS_SOLID, 1, RGB(16, 16, 16));
498
499 SelectObject(hdc, lightPen);
500 MoveToEx(hdc, innerPanel.left, innerPanel.bottom, NULL);
501 LineTo(hdc, innerPanel.left, innerPanel.top);
502 LineTo(hdc, innerPanel.right, innerPanel.top);
503
504 SelectObject(hdc, darkPen);
505 LineTo(hdc, innerPanel.right, innerPanel.bottom);
506 LineTo(hdc, innerPanel.left, innerPanel.bottom);
507
508 DeleteObject(lightPen);
509 DeleteObject(darkPen);
510
511 // Draw frequency display with Winamp-style LCD
512 DrawFrequencyDisplay(hdc, 200, 80, g_radio.frequency);
513
514 // Draw tuning dial with metallic look
515 DrawTuningDial(hdc, 150, 200, 60, g_radio.frequency);
516
517 // Draw volume knob with chrome effect
518 DrawVolumeKnob(hdc, 350, 200, 30, g_radio.volume);
519
520 // Draw signal meter with neon bars
521 DrawSignalMeter(hdc, 450, 170, g_radio.signalStrength);
522
523 // Draw VU meter with classic Winamp style
524 DrawVUMeter(hdc, 450, 200, g_audio.vuLevelLeft, g_audio.vuLevelRight);
525
526 // Draw power button with LED glow
527 DrawPowerButton(hdc, 500, 120, 25, g_radio.power);
528
529 // Draw station info with Winamp-style ticker
530 RadioStation* currentStation = FindNearestStation(g_radio.frequency);
531 if (currentStation && g_radio.signalStrength > 30) {
532 RECT stationRect = {50, 320, 550, 360};
533
534 // Winamp-style display background
535 HBRUSH displayBrush = CreateSolidBrush(RGB(0, 0, 0));
536 FillRect(hdc, &stationRect, displayBrush);
537 DeleteObject(displayBrush);
538
539 // Beveled border
540 HPEN lightBorderPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
541 HPEN darkBorderPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
542
543 SelectObject(hdc, darkBorderPen);
544 MoveToEx(hdc, stationRect.left, stationRect.bottom, NULL);
545 LineTo(hdc, stationRect.left, stationRect.top);
546 LineTo(hdc, stationRect.right, stationRect.top);
547
548 SelectObject(hdc, lightBorderPen);
549 LineTo(hdc, stationRect.right, stationRect.bottom);
550 LineTo(hdc, stationRect.left, stationRect.bottom);
551
552 DeleteObject(lightBorderPen);
553 DeleteObject(darkBorderPen);
554
555 // Winamp-style green text
556 SetTextColor(hdc, RGB(0, 255, 0));
557 SetBkMode(hdc, TRANSPARENT);
558 HFONT stationFont = CreateFont(14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
559 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
560 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
561 DEFAULT_PITCH | FF_MODERN, "Tahoma");
562 SelectObject(hdc, stationFont);
563
564 char stationText[256];
565 sprintf(stationText, "%.3f MHz - %s: %s",
566 currentStation->frequency, currentStation->name, currentStation->description);
567
568 SetTextAlign(hdc, TA_LEFT);
569 TextOut(hdc, stationRect.left + 10, stationRect.top + 12, stationText, strlen(stationText));
570
571 DeleteObject(stationFont);
572 }
573}
574
575void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency) {
576 // Winamp-style LCD display with beveled edges
577 RECT display = {x - 100, y - 25, x + 100, y + 25};
578
579 // Dark background
580 HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
581 FillRect(hdc, &display, blackBrush);
582 DeleteObject(blackBrush);
583
584 // Beveled border effect
585 HPEN lightPen = CreatePen(PS_SOLID, 2, RGB(96, 96, 96));
586 HPEN darkPen = CreatePen(PS_SOLID, 2, RGB(32, 32, 32));
587
588 SelectObject(hdc, darkPen);
589 MoveToEx(hdc, display.left, display.bottom, NULL);
590 LineTo(hdc, display.left, display.top);
591 LineTo(hdc, display.right, display.top);
592
593 SelectObject(hdc, lightPen);
594 LineTo(hdc, display.right, display.bottom);
595 LineTo(hdc, display.left, display.bottom);
596
597 DeleteObject(lightPen);
598 DeleteObject(darkPen);
599
600 // Inner shadow
601 RECT innerDisplay = {display.left + 3, display.top + 3, display.right - 3, display.bottom - 3};
602 HPEN shadowPen = CreatePen(PS_SOLID, 1, RGB(16, 16, 16));
603 SelectObject(hdc, shadowPen);
604 Rectangle(hdc, innerDisplay.left, innerDisplay.top, innerDisplay.right, innerDisplay.bottom);
605 DeleteObject(shadowPen);
606
607 // Frequency text with glow effect
608 char freqText[32];
609 sprintf(freqText, "%.3f MHz", frequency);
610
611 SetBkMode(hdc, TRANSPARENT);
612
613 // Create glow effect by drawing text multiple times with slight offsets
614 HFONT lcdFont = CreateFont(20, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
615 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
616 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
617 FIXED_PITCH | FF_MODERN, "Consolas");
618 SelectObject(hdc, lcdFont);
619 SetTextAlign(hdc, TA_CENTER);
620
621 // Glow effect (dark green)
622 SetTextColor(hdc, RGB(0, 128, 0));
623 for (int dx = -1; dx <= 1; dx++) {
624 for (int dy = -1; dy <= 1; dy++) {
625 if (dx != 0 || dy != 0) {
626 TextOut(hdc, x + dx, y - 10 + dy, freqText, strlen(freqText));
627 }
628 }
629 }
630
631 // Main text (bright green)
632 SetTextColor(hdc, RGB(0, 255, 0));
633 TextOut(hdc, x, y - 10, freqText, strlen(freqText));
634
635 DeleteObject(lcdFont);
636}
637
638void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency) {
639 // Simplified metallic dial with fewer gradient steps
640 for (int i = 0; i < 4; i++) {
641 int gray = 80 + i * 20;
642 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
643 SelectObject(hdc, gradBrush);
644 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2);
645 DeleteObject(gradBrush);
646 }
647
648 // Inner dial surface
649 HBRUSH dialBrush = CreateSolidBrush(RGB(160, 160, 160));
650 SelectObject(hdc, dialBrush);
651 Ellipse(hdc, x - radius + 8, y - radius + 8, x + radius - 8, y + radius - 8);
652 DeleteObject(dialBrush);
653
654 // Outer ring
655 HPEN ringPen = CreatePen(PS_SOLID, 2, RGB(48, 48, 48));
656 SelectObject(hdc, ringPen);
657 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
658 DeleteObject(ringPen);
659
660 // Tick marks with better contrast
661 HPEN tickPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
662 SelectObject(hdc, tickPen);
663
664 // Draw major tick marks and frequency labels
665 SetTextColor(hdc, RGB(0, 0, 0));
666 SetBkMode(hdc, TRANSPARENT);
667 HFONT smallFont = CreateFont(9, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
668 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
669 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
670 DEFAULT_PITCH | FF_SWISS, "Tahoma");
671 SelectObject(hdc, smallFont);
672 SetTextAlign(hdc, TA_CENTER);
673
674 // 270 degree sweep from -135 to +135 degrees
675 for (int i = 0; i < 12; i++) {
676 float angle = -3.14159f * 0.75f + (float)i * (3.14159f * 1.5f) / 11.0f;
677
678 // Major tick marks
679 int tickStartX = x + (int)((radius - 12) * cos(angle));
680 int tickStartY = y + (int)((radius - 12) * sin(angle));
681 int tickEndX = x + (int)((radius - 4) * cos(angle));
682 int tickEndY = y + (int)((radius - 4) * sin(angle));
683 MoveToEx(hdc, tickStartX, tickStartY, NULL);
684 LineTo(hdc, tickEndX, tickEndY);
685
686 // Frequency labels
687 int markX = x + (int)((radius - 22) * cos(angle));
688 int markY = y + (int)((radius - 22) * sin(angle));
689 char mark[8];
690 sprintf(mark, "%d", 10 + i * 2);
691 TextOut(hdc, markX, markY - 4, mark, strlen(mark));
692 }
693
694 // Draw minor tick marks
695 for (int i = 0; i < 11; i++) {
696 float angle = -3.14159f * 0.75f + ((float)i + 0.5f) * (3.14159f * 1.5f) / 11.0f;
697 int tickStartX = x + (int)((radius - 8) * cos(angle));
698 int tickStartY = y + (int)((radius - 8) * sin(angle));
699 int tickEndX = x + (int)((radius - 4) * cos(angle));
700 int tickEndY = y + (int)((radius - 4) * sin(angle));
701 MoveToEx(hdc, tickStartX, tickStartY, NULL);
702 LineTo(hdc, tickEndX, tickEndY);
703 }
704
705 DeleteObject(tickPen);
706
707 // Simplified pointer
708 float normalizedFreq = (frequency - 10.0f) / 24.0f;
709 float angle = -3.14159f * 0.75f + normalizedFreq * (3.14159f * 1.5f);
710 int pointerX = x + (int)((radius - 15) * cos(angle));
711 int pointerY = y + (int)((radius - 15) * sin(angle));
712
713 // Main pointer
714 HPEN pointerPen = CreatePen(PS_SOLID, 3, RGB(255, 64, 64));
715 SelectObject(hdc, pointerPen);
716 MoveToEx(hdc, x, y, NULL);
717 LineTo(hdc, pointerX, pointerY);
718 DeleteObject(pointerPen);
719
720 // Center dot
721 HBRUSH centerBrush = CreateSolidBrush(RGB(64, 64, 64));
722 SelectObject(hdc, centerBrush);
723 Ellipse(hdc, x - 4, y - 4, x + 4, y + 4);
724 DeleteObject(centerBrush);
725
726 DeleteObject(smallFont);
727
728 // Label with Winamp style
729 SetTextColor(hdc, RGB(192, 192, 192));
730 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
731 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
732 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
733 DEFAULT_PITCH | FF_SWISS, "Tahoma");
734 SelectObject(hdc, labelFont);
735 SetTextAlign(hdc, TA_CENTER);
736 TextOut(hdc, x, y + radius + 15, "TUNING", 6);
737 DeleteObject(labelFont);
738}
739
740void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume) {
741 // Simplified chrome gradient knob
742 for (int i = 0; i < 3; i++) {
743 int gray = 100 + i * 30;
744 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
745 SelectObject(hdc, gradBrush);
746 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2);
747 DeleteObject(gradBrush);
748 }
749
750 // Inner knob surface
751 HBRUSH knobBrush = CreateSolidBrush(RGB(180, 180, 180));
752 SelectObject(hdc, knobBrush);
753 Ellipse(hdc, x - radius + 6, y - radius + 6, x + radius - 6, y + radius - 6);
754 DeleteObject(knobBrush);
755
756 // Outer ring
757 HPEN ringPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
758 SelectObject(hdc, ringPen);
759 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
760 DeleteObject(ringPen);
761
762 // Volume indicator
763 float angle = volume * 3.14159f * 1.5f - 3.14159f * 0.75f;
764 int indicatorX = x + (int)((radius - 8) * cos(angle));
765 int indicatorY = y + (int)((radius - 8) * sin(angle));
766
767 // Main indicator
768 HPEN indicatorPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
769 SelectObject(hdc, indicatorPen);
770 MoveToEx(hdc, x, y, NULL);
771 LineTo(hdc, indicatorX, indicatorY);
772 DeleteObject(indicatorPen);
773
774 // Center dot
775 HBRUSH centerBrush = CreateSolidBrush(RGB(64, 64, 64));
776 SelectObject(hdc, centerBrush);
777 Ellipse(hdc, x - 3, y - 3, x + 3, y + 3);
778 DeleteObject(centerBrush);
779
780 // Label
781 SetBkMode(hdc, TRANSPARENT);
782 SetTextColor(hdc, RGB(192, 192, 192));
783 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
784 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
785 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
786 DEFAULT_PITCH | FF_SWISS, "Tahoma");
787 SelectObject(hdc, labelFont);
788 SetTextAlign(hdc, TA_CENTER);
789 TextOut(hdc, x, y + radius + 15, "VOLUME", 6);
790 DeleteObject(labelFont);
791}
792
793void DrawSignalMeter(HDC hdc, int x, int y, int strength) {
794 // Winamp-style meter background with bevel
795 RECT meter = {x, y, x + 80, y + 20};
796
797 // Dark background
798 HBRUSH meterBrush = CreateSolidBrush(RGB(16, 16, 16));
799 FillRect(hdc, &meter, meterBrush);
800 DeleteObject(meterBrush);
801
802 // Beveled border
803 HPEN lightPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
804 HPEN darkPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
805
806 SelectObject(hdc, darkPen);
807 MoveToEx(hdc, meter.left, meter.bottom, NULL);
808 LineTo(hdc, meter.left, meter.top);
809 LineTo(hdc, meter.right, meter.top);
810
811 SelectObject(hdc, lightPen);
812 LineTo(hdc, meter.right, meter.bottom);
813 LineTo(hdc, meter.left, meter.bottom);
814
815 DeleteObject(lightPen);
816 DeleteObject(darkPen);
817
818 // Neon-style signal bars
819 int barWidth = 7;
820 int numBars = strength / 10;
821 for (int i = 0; i < numBars && i < 10; i++) {
822 RECT bar = {x + 3 + i * barWidth, y + 3,
823 x + 3 + (i + 1) * barWidth - 1, y + 17};
824
825 COLORREF barColor;
826 if (i < 3) barColor = RGB(0, 255, 64); // Bright green
827 else if (i < 7) barColor = RGB(255, 255, 0); // Yellow
828 else barColor = RGB(255, 64, 64); // Bright red
829
830 HBRUSH barBrush = CreateSolidBrush(barColor);
831 FillRect(hdc, &bar, barBrush);
832 DeleteObject(barBrush);
833
834 // Add glow effect
835 COLORREF glowColor;
836 if (i < 3) glowColor = RGB(0, 128, 32);
837 else if (i < 7) glowColor = RGB(128, 128, 0);
838 else glowColor = RGB(128, 32, 32);
839
840 HPEN glowPen = CreatePen(PS_SOLID, 1, glowColor);
841 SelectObject(hdc, glowPen);
842 Rectangle(hdc, bar.left - 1, bar.top - 1, bar.right + 1, bar.bottom + 1);
843 DeleteObject(glowPen);
844 }
845
846 // Label
847 SetBkMode(hdc, TRANSPARENT);
848 SetTextColor(hdc, RGB(192, 192, 192));
849 HFONT labelFont = CreateFont(11, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
850 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
851 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
852 DEFAULT_PITCH | FF_SWISS, "Tahoma");
853 SelectObject(hdc, labelFont);
854 SetTextAlign(hdc, TA_LEFT);
855 TextOut(hdc, x, y - 16, "SIGNAL", 6);
856 DeleteObject(labelFont);
857}
858
859void DrawVUMeter(HDC hdc, int x, int y, float leftLevel, float rightLevel) {
860 // Winamp-style VU meter with classic look
861 RECT meterBg = {x, y, x + 80, y + 40};
862
863 // Dark background
864 HBRUSH bgBrush = CreateSolidBrush(RGB(16, 16, 16));
865 FillRect(hdc, &meterBg, bgBrush);
866 DeleteObject(bgBrush);
867
868 // Beveled border
869 HPEN lightPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
870 HPEN darkPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
871
872 SelectObject(hdc, darkPen);
873 MoveToEx(hdc, meterBg.left, meterBg.bottom, NULL);
874 LineTo(hdc, meterBg.left, meterBg.top);
875 LineTo(hdc, meterBg.right, meterBg.top);
876
877 SelectObject(hdc, lightPen);
878 LineTo(hdc, meterBg.right, meterBg.bottom);
879 LineTo(hdc, meterBg.left, meterBg.bottom);
880
881 DeleteObject(lightPen);
882 DeleteObject(darkPen);
883
884 // "VU" label with classic styling
885 SetTextColor(hdc, RGB(0, 255, 0));
886 SetBkMode(hdc, TRANSPARENT);
887 HFONT vuFont = CreateFont(10, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
888 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
889 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
890 DEFAULT_PITCH | FF_SWISS, "Tahoma");
891 SelectObject(hdc, vuFont);
892 TextOut(hdc, x + 5, y + 2, "VU", 2);
893
894 // Left channel meter with neon effect
895 int leftWidth = (int)(leftLevel * 65);
896 if (leftWidth > 0) {
897 RECT leftBar = {x + 8, y + 12, x + 8 + leftWidth, y + 17};
898
899 // Determine color based on level
900 COLORREF leftColor;
901 if (leftLevel > 0.8f) leftColor = RGB(255, 64, 64); // Red
902 else if (leftLevel > 0.6f) leftColor = RGB(255, 255, 0); // Yellow
903 else leftColor = RGB(0, 255, 64); // Green
904
905 HBRUSH leftBrush = CreateSolidBrush(leftColor);
906 FillRect(hdc, &leftBar, leftBrush);
907 DeleteObject(leftBrush);
908
909 // Add glow effect
910 COLORREF glowColor;
911 if (leftLevel > 0.8f) glowColor = RGB(128, 32, 32);
912 else if (leftLevel > 0.6f) glowColor = RGB(128, 128, 0);
913 else glowColor = RGB(0, 128, 32);
914
915 HPEN glowPen = CreatePen(PS_SOLID, 1, glowColor);
916 SelectObject(hdc, glowPen);
917 Rectangle(hdc, leftBar.left - 1, leftBar.top - 1, leftBar.right + 1, leftBar.bottom + 1);
918 DeleteObject(glowPen);
919 }
920
921 // Right channel meter with neon effect
922 int rightWidth = (int)(rightLevel * 65);
923 if (rightWidth > 0) {
924 RECT rightBar = {x + 8, y + 22, x + 8 + rightWidth, y + 27};
925
926 // Determine color based on level
927 COLORREF rightColor;
928 if (rightLevel > 0.8f) rightColor = RGB(255, 64, 64); // Red
929 else if (rightLevel > 0.6f) rightColor = RGB(255, 255, 0); // Yellow
930 else rightColor = RGB(0, 255, 64); // Green
931
932 HBRUSH rightBrush = CreateSolidBrush(rightColor);
933 FillRect(hdc, &rightBar, rightBrush);
934 DeleteObject(rightBrush);
935
936 // Add glow effect
937 COLORREF glowColor;
938 if (rightLevel > 0.8f) glowColor = RGB(128, 32, 32);
939 else if (rightLevel > 0.6f) glowColor = RGB(128, 128, 0);
940 else glowColor = RGB(0, 128, 32);
941
942 HPEN glowPen = CreatePen(PS_SOLID, 1, glowColor);
943 SelectObject(hdc, glowPen);
944 Rectangle(hdc, rightBar.left - 1, rightBar.top - 1, rightBar.right + 1, rightBar.bottom + 1);
945 DeleteObject(glowPen);
946 }
947
948 // Channel labels
949 SetTextColor(hdc, RGB(192, 192, 192));
950 TextOut(hdc, x + 75, y + 12, "L", 1);
951 TextOut(hdc, x + 75, y + 22, "R", 1);
952
953 // Scale marks
954 HPEN scalePen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
955 SelectObject(hdc, scalePen);
956 for (int i = 1; i < 10; i++) {
957 int markX = x + 8 + (i * 7);
958 MoveToEx(hdc, markX, y + 30, NULL);
959 LineTo(hdc, markX, y + 32);
960 }
961 DeleteObject(scalePen);
962 DeleteObject(vuFont);
963}
964
965void DrawPowerButton(HDC hdc, int x, int y, int radius, int power) {
966 // Simplified chrome gradient button
967 for (int i = 0; i < 3; i++) {
968 int intensity = power ? (80 + i * 40) : (60 + i * 20);
969 COLORREF buttonColor = power ? RGB(255 - i * 40, intensity, intensity) : RGB(intensity, intensity, intensity);
970 HBRUSH buttonBrush = CreateSolidBrush(buttonColor);
971 SelectObject(hdc, buttonBrush);
972 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2);
973 DeleteObject(buttonBrush);
974 }
975
976 // Inner button surface
977 COLORREF innerColor = power ? RGB(255, 128, 128) : RGB(128, 128, 128);
978 HBRUSH innerBrush = CreateSolidBrush(innerColor);
979 SelectObject(hdc, innerBrush);
980 Ellipse(hdc, x - radius + 6, y - radius + 6, x + radius - 6, y + radius - 6);
981 DeleteObject(innerBrush);
982
983 // Button border
984 HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(32, 32, 32));
985 SelectObject(hdc, borderPen);
986 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
987 DeleteObject(borderPen);
988
989 // Power symbol
990 if (power) {
991 // Main symbol
992 HPEN symbolPen = CreatePen(PS_SOLID, 3, RGB(255, 255, 255));
993 SelectObject(hdc, symbolPen);
994 Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6);
995 MoveToEx(hdc, x, y - 10, NULL);
996 LineTo(hdc, x, y - 2);
997 DeleteObject(symbolPen);
998 } else {
999 // Dim power symbol
1000 HPEN symbolPen = CreatePen(PS_SOLID, 2, RGB(64, 64, 64));
1001 SelectObject(hdc, symbolPen);
1002 Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6);
1003 MoveToEx(hdc, x, y - 10, NULL);
1004 LineTo(hdc, x, y - 2);
1005 DeleteObject(symbolPen);
1006 }
1007
1008 // Label
1009 SetBkMode(hdc, TRANSPARENT);
1010 SetTextColor(hdc, power ? RGB(255, 192, 192) : RGB(192, 192, 192));
1011 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
1012 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
1013 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
1014 DEFAULT_PITCH | FF_SWISS, "Tahoma");
1015 SelectObject(hdc, labelFont);
1016 SetTextAlign(hdc, TA_CENTER);
1017 TextOut(hdc, x, y - radius - 18, "POWER", 5);
1018 DeleteObject(labelFont);
1019}
1020
1021int IsPointInCircle(int px, int py, int cx, int cy, int radius) {
1022 int dx = px - cx;
1023 int dy = py - cy;
1024 return (dx * dx + dy * dy) <= (radius * radius);
1025}
1026
1027float GetAngleFromPoint(int px, int py, int cx, int cy) {
1028 return atan2((float)(py - cy), (float)(px - cx));
1029}
1030
1031void UpdateFrequencyFromMouse(int mouseX, int mouseY) {
1032 float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200);
1033
1034 // Convert angle to frequency (10-34 MHz range)
1035 // Map 270 degree sweep from -135 to +135 degrees
1036 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
1037
1038 // Clamp to valid range
1039 if (normalizedAngle < 0.0f) normalizedAngle = 0.0f;
1040 if (normalizedAngle > 1.0f) normalizedAngle = 1.0f;
1041
1042 // Map to frequency range
1043 g_radio.frequency = 10.0f + normalizedAngle * 24.0f;
1044
1045 // Clamp frequency
1046 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
1047 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
1048
1049 // Calculate signal strength based on nearest station
1050 RadioStation* nearestStation = FindNearestStation(g_radio.frequency);
1051 if (nearestStation) {
1052 g_radio.signalStrength = (int)(GetStationSignalStrength(nearestStation, g_radio.frequency) * 100.0f);
1053
1054 // Start streaming if signal is strong enough and station changed
1055 if (g_radio.signalStrength > 50 && nearestStation != g_audio.currentStation) {
1056 StopBassStreaming();
1057 StartBassStreaming(nearestStation);
1058 }
1059 } else {
1060 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
1061 StopBassStreaming();
1062 }
1063
1064 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
1065 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
1066
1067 UpdateStaticVolume(g_radio.signalStrength);
1068 UpdateStreamVolume();
1069}
1070
1071void UpdateVolumeFromMouse(int mouseX, int mouseY) {
1072 float angle = GetAngleFromPoint(mouseX, mouseY, 350, 200);
1073
1074 // Convert angle to volume (0-1 range)
1075 // Map from -135 degrees to +135 degrees
1076 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
1077
1078 g_radio.volume = normalizedAngle;
1079
1080 // Clamp volume
1081 if (g_radio.volume < 0.0f) g_radio.volume = 0.0f;
1082 if (g_radio.volume > 1.0f) g_radio.volume = 1.0f;
1083
1084 // Update volumes when main volume changes
1085 UpdateStaticVolume(g_radio.signalStrength);
1086 UpdateStreamVolume();
1087}
1088
1089int InitializeAudio() {
1090 // Initialize BASS with more detailed error reporting
1091 printf("Initializing BASS audio system...\n");
1092
1093 if (!BASS_Init(-1, 44100, 0, 0, NULL)) {
1094 DWORD error = BASS_ErrorGetCode();
1095 printf("BASS initialization failed (Error: %lu)\n", error);
1096
1097 // Try alternative initialization methods
1098 printf("Trying alternative audio device...\n");
1099 if (!BASS_Init(0, 44100, 0, 0, NULL)) {
1100 error = BASS_ErrorGetCode();
1101 printf("Alternative BASS init also failed (Error: %lu)\n", error);
1102 printf("BASS Error meanings:\n");
1103 printf(" 1 = BASS_ERROR_MEM (memory error)\n");
1104 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL error)\n");
1105 printf(" 3 = BASS_ERROR_DRIVER (no audio driver)\n");
1106 printf(" 8 = BASS_ERROR_ALREADY (already initialized)\n");
1107 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n");
1108 return -1;
1109 }
1110 }
1111
1112 printf("BASS initialized successfully\n");
1113
1114 // Get BASS version info
1115 DWORD version = BASS_GetVersion();
1116 printf("BASS version: %d.%d.%d.%d\n",
1117 HIBYTE(HIWORD(version)), LOBYTE(HIWORD(version)),
1118 HIBYTE(LOWORD(version)), LOBYTE(LOWORD(version)));
1119
1120 g_audio.currentStream = 0;
1121 g_audio.staticStream = 0;
1122 g_audio.isPlaying = 0;
1123 g_audio.staticVolume = 0.8f;
1124 g_audio.radioVolume = 0.0f;
1125 g_audio.currentStation = NULL;
1126 g_audio.vuLevelLeft = 0.0f;
1127 g_audio.vuLevelRight = 0.0f;
1128
1129 return 0;
1130}
1131
1132void CleanupAudio() {
1133 StopBassStreaming();
1134
1135 // Free BASS
1136 BASS_Free();
1137 printf("BASS cleaned up\n");
1138}
1139
1140void StartAudio() {
1141 if (!g_audio.isPlaying) {
1142 g_audio.isPlaying = 1;
1143 StartStaticNoise();
1144 printf("Audio started with static\n");
1145 }
1146}
1147
1148void StopAudio() {
1149 if (g_audio.isPlaying) {
1150 g_audio.isPlaying = 0;
1151 StopBassStreaming();
1152 StopStaticNoise();
1153 printf("Audio stopped\n");
1154 }
1155}
1156
1157RadioStation* FindNearestStation(float frequency) {
1158 RadioStation* nearest = NULL;
1159 float minDistance = 999.0f;
1160
1161 for (int i = 0; i < NUM_STATIONS; i++) {
1162 float distance = fabs(g_stations[i].frequency - frequency);
1163 if (distance < minDistance) {
1164 minDistance = distance;
1165 nearest = &g_stations[i];
1166 }
1167 }
1168
1169 // Only return station if we're close enough (within 0.5 MHz)
1170 if (minDistance <= 0.5f) {
1171 return nearest;
1172 }
1173
1174 return NULL;
1175}
1176
1177float GetStationSignalStrength(RadioStation* station, float currentFreq) {
1178 if (!station) return 0.0f;
1179
1180 float distance = fabs(station->frequency - currentFreq);
1181
1182 // Signal strength drops off with distance from exact frequency
1183 if (distance < 0.05f) {
1184 return 0.9f; // Very strong signal
1185 } else if (distance < 0.1f) {
1186 return 0.7f; // Strong signal
1187 } else if (distance < 0.2f) {
1188 return 0.5f; // Medium signal
1189 } else if (distance < 0.5f) {
1190 return 0.2f; // Weak signal
1191 }
1192
1193 return 0.0f; // No signal
1194}
1195
1196int StartBassStreaming(RadioStation* station) {
1197 if (!station) {
1198 printf("StartBassStreaming failed: no station\n");
1199 return 0;
1200 }
1201
1202 StopBassStreaming();
1203
1204 printf("Attempting to stream: %s at %s\n", station->name, station->streamUrl);
1205
1206 // Check if BASS is initialized
1207 if (!BASS_GetVersion()) {
1208 printf("BASS not initialized - cannot stream\n");
1209 return 0;
1210 }
1211
1212 // Create BASS stream from URL with more options
1213 printf("Creating BASS stream...\n");
1214 g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0,
1215 BASS_STREAM_BLOCK | BASS_STREAM_STATUS | BASS_STREAM_AUTOFREE, NULL, 0);
1216
1217 if (g_audio.currentStream) {
1218 printf("Successfully connected to stream: %s\n", station->name);
1219
1220 // Get stream info
1221 BASS_CHANNELINFO info;
1222 if (BASS_ChannelGetInfo(g_audio.currentStream, &info)) {
1223 printf("Stream info: %lu Hz, %lu channels, type=%lu\n",
1224 info.freq, info.chans, info.ctype);
1225 }
1226
1227 // Set volume based on signal strength and radio volume
1228 float volume = g_radio.volume * (g_radio.signalStrength / 100.0f);
1229 BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume);
1230 printf("Set volume to: %.2f\n", volume);
1231
1232 // Start playing
1233 if (BASS_ChannelPlay(g_audio.currentStream, FALSE)) {
1234 printf("Stream playback started\n");
1235 } else {
1236 DWORD error = BASS_ErrorGetCode();
1237 printf("Failed to start playback (BASS Error: %lu)\n", error);
1238 }
1239
1240 g_audio.currentStation = station;
1241 return 1;
1242 } else {
1243 DWORD error = BASS_ErrorGetCode();
1244 printf("Failed to connect to stream: %s (BASS Error: %lu)\n", station->name, error);
1245 printf("BASS Error meanings:\n");
1246 printf(" 1 = BASS_ERROR_MEM (out of memory)\n");
1247 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL cannot be opened)\n");
1248 printf(" 3 = BASS_ERROR_DRIVER (no audio driver available)\n");
1249 printf(" 6 = BASS_ERROR_FORMAT (unsupported format)\n");
1250 printf(" 7 = BASS_ERROR_POSITION (invalid position)\n");
1251 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n");
1252 printf(" 21 = BASS_ERROR_TIMEOUT (connection timeout)\n");
1253 printf(" 41 = BASS_ERROR_SSL (SSL/HTTPS not supported)\n");
1254 }
1255
1256 return 0;
1257}
1258
1259void StopBassStreaming() {
1260 if (g_audio.currentStream) {
1261 BASS_StreamFree(g_audio.currentStream);
1262 g_audio.currentStream = 0;
1263 printf("Stopped streaming\n");
1264 }
1265
1266 g_audio.currentStation = NULL;
1267}
1268
1269// Static noise generation callback
1270DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user) {
1271 short* samples = (short*)buffer;
1272 DWORD sampleCount = length / sizeof(short);
1273
1274 // Get current time for oscillation
1275 static DWORD startTime = GetTickCount();
1276 DWORD currentTime = GetTickCount();
1277 float timeSeconds = (currentTime - startTime) / 1000.0f;
1278
1279 // Create subtle volume oscillations (5-7% variation)
1280 // Use multiple sine waves at different frequencies for natural variation
1281 float oscillation1 = sin(timeSeconds * 0.7f) * 0.03f; // 3% slow oscillation
1282 float oscillation2 = sin(timeSeconds * 2.3f) * 0.02f; // 2% medium oscillation
1283 float oscillation3 = sin(timeSeconds * 5.1f) * 0.015f; // 1.5% fast oscillation
1284 float volumeVariation = 1.0f + oscillation1 + oscillation2 + oscillation3;
1285
1286 // Generate white noise with volume variation
1287 for (DWORD i = 0; i < sampleCount; i++) {
1288 // Generate random value between -32767 and 32767
1289 short baseNoise = (short)((rand() % 65535) - 32767);
1290
1291 // Apply volume variation
1292 samples[i] = (short)(baseNoise * volumeVariation);
1293 }
1294
1295 return length;
1296}
1297
1298void StartStaticNoise() {
1299 if (!g_audio.staticStream) {
1300 // Create a stream for static noise generation
1301 g_audio.staticStream = BASS_StreamCreate(SAMPLE_RATE, CHANNELS, 0, StaticStreamProc, NULL);
1302
1303 if (g_audio.staticStream) {
1304 // Set initial volume based on signal strength
1305 UpdateStaticVolume(g_radio.signalStrength);
1306
1307 // Start playing static
1308 BASS_ChannelPlay(g_audio.staticStream, FALSE);
1309 printf("Static noise started\n");
1310 } else {
1311 printf("Failed to create static stream\n");
1312 }
1313 }
1314}
1315
1316void StopStaticNoise() {
1317 if (g_audio.staticStream) {
1318 BASS_StreamFree(g_audio.staticStream);
1319 g_audio.staticStream = 0;
1320 printf("Static noise stopped\n");
1321 }
1322}
1323
1324void UpdateStaticVolume(float signalStrength) {
1325 if (g_audio.staticStream) {
1326 // Static volume is inverse of signal strength
1327 // Strong signal = less static, weak signal = more static
1328 float staticLevel = (100.0f - signalStrength) / 100.0f;
1329 float volume = g_radio.volume * staticLevel * g_audio.staticVolume;
1330
1331 // Ensure minimum static when radio is on but no strong signal
1332 if (g_radio.power && signalStrength < 50.0f) {
1333 volume = fmax(volume, g_radio.volume * 0.1f);
1334 }
1335
1336 BASS_ChannelSetAttribute(g_audio.staticStream, BASS_ATTRIB_VOL, volume);
1337 }
1338}
1339
1340void UpdateStreamVolume() {
1341 if (g_audio.currentStream) {
1342 // Stream volume based on signal strength and radio volume
1343 float volume = g_radio.volume * (g_radio.signalStrength / 100.0f);
1344 BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume);
1345 if (g_consoleVisible) {
1346 printf("Updated stream volume to: %.2f\n", volume);
1347 }
1348 }
1349}
1350
1351void UpdateVULevels() {
1352 // Initialize levels to zero
1353 g_audio.vuLevelLeft = 0.0f;
1354 g_audio.vuLevelRight = 0.0f;
1355
1356 // Get levels from current stream if playing
1357 if (g_audio.currentStream && BASS_ChannelIsActive(g_audio.currentStream) == BASS_ACTIVE_PLAYING) {
1358 DWORD level = BASS_ChannelGetLevel(g_audio.currentStream);
1359 if (level != -1) {
1360 // Extract left and right channel levels and apply volume scaling
1361 float rawLeft = (float)LOWORD(level) / 32768.0f;
1362 float rawRight = (float)HIWORD(level) / 32768.0f;
1363
1364 // Apply the same volume scaling as the actual audio output
1365 float streamVolume = g_radio.volume * (g_radio.signalStrength / 100.0f);
1366 g_audio.vuLevelLeft = rawLeft * streamVolume;
1367 g_audio.vuLevelRight = rawRight * streamVolume;
1368 }
1369 }
1370
1371 // Add static contribution if static is playing
1372 if (g_audio.staticStream && BASS_ChannelIsActive(g_audio.staticStream) == BASS_ACTIVE_PLAYING) {
1373 DWORD staticLevel = BASS_ChannelGetLevel(g_audio.staticStream);
1374 if (staticLevel != -1) {
1375 float staticLeft = (float)LOWORD(staticLevel) / 32768.0f;
1376 float staticRight = (float)HIWORD(staticLevel) / 32768.0f;
1377
1378 // Apply static volume scaling
1379 float staticVolume = (100.0f - g_radio.signalStrength) / 100.0f;
1380 float scaledStaticVolume = g_radio.volume * staticVolume * g_audio.staticVolume;
1381
1382 // Ensure minimum static when radio is on but no strong signal
1383 if (g_radio.power && g_radio.signalStrength < 50.0f) {
1384 scaledStaticVolume = fmax(scaledStaticVolume, g_radio.volume * 0.1f);
1385 }
1386
1387 // Combine with existing levels (simulate mixing)
1388 g_audio.vuLevelLeft = fmin(1.0f, g_audio.vuLevelLeft + staticLeft * scaledStaticVolume * 0.3f);
1389 g_audio.vuLevelRight = fmin(1.0f, g_audio.vuLevelRight + staticRight * scaledStaticVolume * 0.3f);
1390 }
1391 }
1392
1393 // Apply some smoothing/decay for more realistic VU behavior
1394 static float lastLeft = 0.0f, lastRight = 0.0f;
1395 g_audio.vuLevelLeft = g_audio.vuLevelLeft * 0.7f + lastLeft * 0.3f;
1396 g_audio.vuLevelRight = g_audio.vuLevelRight * 0.7f + lastRight * 0.3f;
1397 lastLeft = g_audio.vuLevelLeft;
1398 lastRight = g_audio.vuLevelRight;
1399}