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(24, 24, 24)); // Dark Winamp-style background
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 // Winamp-style dark gradient background
414 HBRUSH darkBrush = CreateSolidBrush(RGB(24, 24, 24));
415 FillRect(hdc, rect, darkBrush);
416 DeleteObject(darkBrush);
417
418 // Main panel with metallic gradient effect
419 RECT panel = {10, 10, rect->right - 10, rect->bottom - 10};
420
421 // Create gradient effect by drawing multiple rectangles
422 for (int i = 0; i < 20; i++) {
423 int gray = 45 + i * 2;
424 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
425 RECT gradRect = {panel.left + i, panel.top + i, panel.right - i, panel.bottom - i};
426 FrameRect(hdc, &gradRect, gradBrush);
427 DeleteObject(gradBrush);
428 }
429
430 // Inner panel with darker metallic look
431 RECT innerPanel = {30, 30, rect->right - 30, rect->bottom - 30};
432 HBRUSH innerBrush = CreateSolidBrush(RGB(32, 32, 32));
433 FillRect(hdc, &innerPanel, innerBrush);
434 DeleteObject(innerBrush);
435
436 // Winamp-style beveled border
437 HPEN lightPen = CreatePen(PS_SOLID, 1, RGB(128, 128, 128));
438 HPEN darkPen = CreatePen(PS_SOLID, 1, RGB(16, 16, 16));
439
440 SelectObject(hdc, lightPen);
441 MoveToEx(hdc, innerPanel.left, innerPanel.bottom, NULL);
442 LineTo(hdc, innerPanel.left, innerPanel.top);
443 LineTo(hdc, innerPanel.right, innerPanel.top);
444
445 SelectObject(hdc, darkPen);
446 LineTo(hdc, innerPanel.right, innerPanel.bottom);
447 LineTo(hdc, innerPanel.left, innerPanel.bottom);
448
449 DeleteObject(lightPen);
450 DeleteObject(darkPen);
451
452 // Draw frequency display with Winamp-style LCD
453 DrawFrequencyDisplay(hdc, 200, 80, g_radio.frequency);
454
455 // Draw tuning dial with metallic look
456 DrawTuningDial(hdc, 150, 200, 60, g_radio.frequency);
457
458 // Draw volume knob with chrome effect
459 DrawVolumeKnob(hdc, 350, 200, 30, g_radio.volume);
460
461 // Draw signal meter with neon bars
462 DrawSignalMeter(hdc, 450, 170, g_radio.signalStrength);
463
464 // Draw VU meter with classic Winamp style
465 DrawVUMeter(hdc, 450, 200, g_audio.vuLevelLeft, g_audio.vuLevelRight);
466
467 // Draw power button with LED glow
468 DrawPowerButton(hdc, 500, 120, 25, g_radio.power);
469
470 // Draw station info with Winamp-style ticker
471 RadioStation* currentStation = FindNearestStation(g_radio.frequency);
472 if (currentStation && g_radio.signalStrength > 30) {
473 RECT stationRect = {50, 320, 550, 360};
474
475 // Winamp-style display background
476 HBRUSH displayBrush = CreateSolidBrush(RGB(0, 0, 0));
477 FillRect(hdc, &stationRect, displayBrush);
478 DeleteObject(displayBrush);
479
480 // Beveled border
481 HPEN lightBorderPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
482 HPEN darkBorderPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
483
484 SelectObject(hdc, darkBorderPen);
485 MoveToEx(hdc, stationRect.left, stationRect.bottom, NULL);
486 LineTo(hdc, stationRect.left, stationRect.top);
487 LineTo(hdc, stationRect.right, stationRect.top);
488
489 SelectObject(hdc, lightBorderPen);
490 LineTo(hdc, stationRect.right, stationRect.bottom);
491 LineTo(hdc, stationRect.left, stationRect.bottom);
492
493 DeleteObject(lightBorderPen);
494 DeleteObject(darkBorderPen);
495
496 // Winamp-style green text
497 SetTextColor(hdc, RGB(0, 255, 0));
498 SetBkMode(hdc, TRANSPARENT);
499 HFONT stationFont = CreateFont(14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
500 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
501 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
502 DEFAULT_PITCH | FF_MODERN, "Tahoma");
503 SelectObject(hdc, stationFont);
504
505 char stationText[256];
506 sprintf(stationText, "%.3f MHz - %s: %s",
507 currentStation->frequency, currentStation->name, currentStation->description);
508
509 SetTextAlign(hdc, TA_LEFT);
510 TextOut(hdc, stationRect.left + 10, stationRect.top + 12, stationText, strlen(stationText));
511
512 DeleteObject(stationFont);
513 }
514}
515
516void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency) {
517 // Winamp-style LCD display with beveled edges
518 RECT display = {x - 100, y - 25, x + 100, y + 25};
519
520 // Dark background
521 HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
522 FillRect(hdc, &display, blackBrush);
523 DeleteObject(blackBrush);
524
525 // Beveled border effect
526 HPEN lightPen = CreatePen(PS_SOLID, 2, RGB(96, 96, 96));
527 HPEN darkPen = CreatePen(PS_SOLID, 2, RGB(32, 32, 32));
528
529 SelectObject(hdc, darkPen);
530 MoveToEx(hdc, display.left, display.bottom, NULL);
531 LineTo(hdc, display.left, display.top);
532 LineTo(hdc, display.right, display.top);
533
534 SelectObject(hdc, lightPen);
535 LineTo(hdc, display.right, display.bottom);
536 LineTo(hdc, display.left, display.bottom);
537
538 DeleteObject(lightPen);
539 DeleteObject(darkPen);
540
541 // Inner shadow
542 RECT innerDisplay = {display.left + 3, display.top + 3, display.right - 3, display.bottom - 3};
543 HPEN shadowPen = CreatePen(PS_SOLID, 1, RGB(16, 16, 16));
544 SelectObject(hdc, shadowPen);
545 Rectangle(hdc, innerDisplay.left, innerDisplay.top, innerDisplay.right, innerDisplay.bottom);
546 DeleteObject(shadowPen);
547
548 // Frequency text with glow effect
549 char freqText[32];
550 sprintf(freqText, "%.3f MHz", frequency);
551
552 SetBkMode(hdc, TRANSPARENT);
553
554 // Create glow effect by drawing text multiple times with slight offsets
555 HFONT lcdFont = CreateFont(20, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
556 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
557 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
558 FIXED_PITCH | FF_MODERN, "Consolas");
559 SelectObject(hdc, lcdFont);
560 SetTextAlign(hdc, TA_CENTER);
561
562 // Glow effect (dark green)
563 SetTextColor(hdc, RGB(0, 128, 0));
564 for (int dx = -1; dx <= 1; dx++) {
565 for (int dy = -1; dy <= 1; dy++) {
566 if (dx != 0 || dy != 0) {
567 TextOut(hdc, x + dx, y - 10 + dy, freqText, strlen(freqText));
568 }
569 }
570 }
571
572 // Main text (bright green)
573 SetTextColor(hdc, RGB(0, 255, 0));
574 TextOut(hdc, x, y - 10, freqText, strlen(freqText));
575
576 DeleteObject(lcdFont);
577}
578
579void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency) {
580 // Metallic dial with chrome gradient
581 for (int i = 0; i < 8; i++) {
582 int gray = 80 + i * 10;
583 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
584 SelectObject(hdc, gradBrush);
585 Ellipse(hdc, x - radius + i, y - radius + i, x + radius - i, y + radius - i);
586 DeleteObject(gradBrush);
587 }
588
589 // Inner dial surface
590 HBRUSH dialBrush = CreateSolidBrush(RGB(160, 160, 160));
591 SelectObject(hdc, dialBrush);
592 Ellipse(hdc, x - radius + 8, y - radius + 8, x + radius - 8, y + radius - 8);
593 DeleteObject(dialBrush);
594
595 // Outer ring
596 HPEN ringPen = CreatePen(PS_SOLID, 2, RGB(48, 48, 48));
597 SelectObject(hdc, ringPen);
598 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
599 DeleteObject(ringPen);
600
601 // Tick marks with better contrast
602 HPEN tickPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
603 SelectObject(hdc, tickPen);
604
605 // Draw major tick marks and frequency labels
606 SetTextColor(hdc, RGB(0, 0, 0));
607 SetBkMode(hdc, TRANSPARENT);
608 HFONT smallFont = CreateFont(9, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
609 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
610 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
611 DEFAULT_PITCH | FF_SWISS, "Tahoma");
612 SelectObject(hdc, smallFont);
613 SetTextAlign(hdc, TA_CENTER);
614
615 // 270 degree sweep from -135 to +135 degrees
616 for (int i = 0; i < 12; i++) {
617 float angle = -3.14159f * 0.75f + (float)i * (3.14159f * 1.5f) / 11.0f;
618
619 // Major tick marks
620 int tickStartX = x + (int)((radius - 12) * cos(angle));
621 int tickStartY = y + (int)((radius - 12) * sin(angle));
622 int tickEndX = x + (int)((radius - 4) * cos(angle));
623 int tickEndY = y + (int)((radius - 4) * sin(angle));
624 MoveToEx(hdc, tickStartX, tickStartY, NULL);
625 LineTo(hdc, tickEndX, tickEndY);
626
627 // Frequency labels
628 int markX = x + (int)((radius - 22) * cos(angle));
629 int markY = y + (int)((radius - 22) * sin(angle));
630 char mark[8];
631 sprintf(mark, "%d", 10 + i * 2);
632 TextOut(hdc, markX, markY - 4, mark, strlen(mark));
633 }
634
635 // Draw minor tick marks
636 for (int i = 0; i < 11; i++) {
637 float angle = -3.14159f * 0.75f + ((float)i + 0.5f) * (3.14159f * 1.5f) / 11.0f;
638 int tickStartX = x + (int)((radius - 8) * cos(angle));
639 int tickStartY = y + (int)((radius - 8) * sin(angle));
640 int tickEndX = x + (int)((radius - 4) * cos(angle));
641 int tickEndY = y + (int)((radius - 4) * sin(angle));
642 MoveToEx(hdc, tickStartX, tickStartY, NULL);
643 LineTo(hdc, tickEndX, tickEndY);
644 }
645
646 DeleteObject(tickPen);
647
648 // Chrome-style pointer with shadow
649 float normalizedFreq = (frequency - 10.0f) / 24.0f;
650 float angle = -3.14159f * 0.75f + normalizedFreq * (3.14159f * 1.5f);
651 int pointerX = x + (int)((radius - 15) * cos(angle));
652 int pointerY = y + (int)((radius - 15) * sin(angle));
653
654 // Pointer shadow
655 HPEN shadowPen = CreatePen(PS_SOLID, 4, RGB(32, 32, 32));
656 SelectObject(hdc, shadowPen);
657 MoveToEx(hdc, x + 1, y + 1, NULL);
658 LineTo(hdc, pointerX + 1, pointerY + 1);
659 DeleteObject(shadowPen);
660
661 // Main pointer
662 HPEN pointerPen = CreatePen(PS_SOLID, 3, RGB(255, 64, 64));
663 SelectObject(hdc, pointerPen);
664 MoveToEx(hdc, x, y, NULL);
665 LineTo(hdc, pointerX, pointerY);
666 DeleteObject(pointerPen);
667
668 // Center dot
669 HBRUSH centerBrush = CreateSolidBrush(RGB(64, 64, 64));
670 SelectObject(hdc, centerBrush);
671 Ellipse(hdc, x - 4, y - 4, x + 4, y + 4);
672 DeleteObject(centerBrush);
673
674 DeleteObject(smallFont);
675
676 // Label with Winamp style
677 SetTextColor(hdc, RGB(192, 192, 192));
678 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
679 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
680 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
681 DEFAULT_PITCH | FF_SWISS, "Tahoma");
682 SelectObject(hdc, labelFont);
683 SetTextAlign(hdc, TA_CENTER);
684 TextOut(hdc, x, y + radius + 15, "TUNING", 6);
685 DeleteObject(labelFont);
686}
687
688void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume) {
689 // Chrome gradient knob
690 for (int i = 0; i < 6; i++) {
691 int gray = 100 + i * 15;
692 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
693 SelectObject(hdc, gradBrush);
694 Ellipse(hdc, x - radius + i, y - radius + i, x + radius - i, y + radius - i);
695 DeleteObject(gradBrush);
696 }
697
698 // Inner knob surface
699 HBRUSH knobBrush = CreateSolidBrush(RGB(180, 180, 180));
700 SelectObject(hdc, knobBrush);
701 Ellipse(hdc, x - radius + 6, y - radius + 6, x + radius - 6, y + radius - 6);
702 DeleteObject(knobBrush);
703
704 // Outer ring
705 HPEN ringPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
706 SelectObject(hdc, ringPen);
707 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
708 DeleteObject(ringPen);
709
710 // Volume indicator with glow
711 float angle = volume * 3.14159f * 1.5f - 3.14159f * 0.75f;
712 int indicatorX = x + (int)((radius - 8) * cos(angle));
713 int indicatorY = y + (int)((radius - 8) * sin(angle));
714
715 // Indicator shadow
716 HPEN shadowPen = CreatePen(PS_SOLID, 3, RGB(32, 32, 32));
717 SelectObject(hdc, shadowPen);
718 MoveToEx(hdc, x + 1, y + 1, NULL);
719 LineTo(hdc, indicatorX + 1, indicatorY + 1);
720 DeleteObject(shadowPen);
721
722 // Main indicator
723 HPEN indicatorPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
724 SelectObject(hdc, indicatorPen);
725 MoveToEx(hdc, x, y, NULL);
726 LineTo(hdc, indicatorX, indicatorY);
727 DeleteObject(indicatorPen);
728
729 // Center dot
730 HBRUSH centerBrush = CreateSolidBrush(RGB(64, 64, 64));
731 SelectObject(hdc, centerBrush);
732 Ellipse(hdc, x - 3, y - 3, x + 3, y + 3);
733 DeleteObject(centerBrush);
734
735 // Label
736 SetBkMode(hdc, TRANSPARENT);
737 SetTextColor(hdc, RGB(192, 192, 192));
738 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
739 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
740 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
741 DEFAULT_PITCH | FF_SWISS, "Tahoma");
742 SelectObject(hdc, labelFont);
743 SetTextAlign(hdc, TA_CENTER);
744 TextOut(hdc, x, y + radius + 15, "VOLUME", 6);
745 DeleteObject(labelFont);
746}
747
748void DrawSignalMeter(HDC hdc, int x, int y, int strength) {
749 // Winamp-style meter background with bevel
750 RECT meter = {x, y, x + 80, y + 20};
751
752 // Dark background
753 HBRUSH meterBrush = CreateSolidBrush(RGB(16, 16, 16));
754 FillRect(hdc, &meter, meterBrush);
755 DeleteObject(meterBrush);
756
757 // Beveled border
758 HPEN lightPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
759 HPEN darkPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
760
761 SelectObject(hdc, darkPen);
762 MoveToEx(hdc, meter.left, meter.bottom, NULL);
763 LineTo(hdc, meter.left, meter.top);
764 LineTo(hdc, meter.right, meter.top);
765
766 SelectObject(hdc, lightPen);
767 LineTo(hdc, meter.right, meter.bottom);
768 LineTo(hdc, meter.left, meter.bottom);
769
770 DeleteObject(lightPen);
771 DeleteObject(darkPen);
772
773 // Neon-style signal bars
774 int barWidth = 7;
775 int numBars = strength / 10;
776 for (int i = 0; i < numBars && i < 10; i++) {
777 RECT bar = {x + 3 + i * barWidth, y + 3,
778 x + 3 + (i + 1) * barWidth - 1, y + 17};
779
780 COLORREF barColor;
781 if (i < 3) barColor = RGB(0, 255, 64); // Bright green
782 else if (i < 7) barColor = RGB(255, 255, 0); // Yellow
783 else barColor = RGB(255, 64, 64); // Bright red
784
785 HBRUSH barBrush = CreateSolidBrush(barColor);
786 FillRect(hdc, &bar, barBrush);
787 DeleteObject(barBrush);
788
789 // Add glow effect
790 COLORREF glowColor;
791 if (i < 3) glowColor = RGB(0, 128, 32);
792 else if (i < 7) glowColor = RGB(128, 128, 0);
793 else glowColor = RGB(128, 32, 32);
794
795 HPEN glowPen = CreatePen(PS_SOLID, 1, glowColor);
796 SelectObject(hdc, glowPen);
797 Rectangle(hdc, bar.left - 1, bar.top - 1, bar.right + 1, bar.bottom + 1);
798 DeleteObject(glowPen);
799 }
800
801 // Label
802 SetBkMode(hdc, TRANSPARENT);
803 SetTextColor(hdc, RGB(192, 192, 192));
804 HFONT labelFont = CreateFont(11, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
805 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
806 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
807 DEFAULT_PITCH | FF_SWISS, "Tahoma");
808 SelectObject(hdc, labelFont);
809 SetTextAlign(hdc, TA_LEFT);
810 TextOut(hdc, x, y - 16, "SIGNAL", 6);
811 DeleteObject(labelFont);
812}
813
814void DrawVUMeter(HDC hdc, int x, int y, float leftLevel, float rightLevel) {
815 // Winamp-style VU meter with classic look
816 RECT meterBg = {x, y, x + 80, y + 40};
817
818 // Dark background
819 HBRUSH bgBrush = CreateSolidBrush(RGB(16, 16, 16));
820 FillRect(hdc, &meterBg, bgBrush);
821 DeleteObject(bgBrush);
822
823 // Beveled border
824 HPEN lightPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
825 HPEN darkPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
826
827 SelectObject(hdc, darkPen);
828 MoveToEx(hdc, meterBg.left, meterBg.bottom, NULL);
829 LineTo(hdc, meterBg.left, meterBg.top);
830 LineTo(hdc, meterBg.right, meterBg.top);
831
832 SelectObject(hdc, lightPen);
833 LineTo(hdc, meterBg.right, meterBg.bottom);
834 LineTo(hdc, meterBg.left, meterBg.bottom);
835
836 DeleteObject(lightPen);
837 DeleteObject(darkPen);
838
839 // "VU" label with classic styling
840 SetTextColor(hdc, RGB(0, 255, 0));
841 SetBkMode(hdc, TRANSPARENT);
842 HFONT vuFont = CreateFont(10, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
843 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
844 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
845 DEFAULT_PITCH | FF_SWISS, "Tahoma");
846 SelectObject(hdc, vuFont);
847 TextOut(hdc, x + 5, y + 2, "VU", 2);
848
849 // Left channel meter with neon effect
850 int leftWidth = (int)(leftLevel * 65);
851 if (leftWidth > 0) {
852 RECT leftBar = {x + 8, y + 12, x + 8 + leftWidth, y + 17};
853
854 // Determine color based on level
855 COLORREF leftColor;
856 if (leftLevel > 0.8f) leftColor = RGB(255, 64, 64); // Red
857 else if (leftLevel > 0.6f) leftColor = RGB(255, 255, 0); // Yellow
858 else leftColor = RGB(0, 255, 64); // Green
859
860 HBRUSH leftBrush = CreateSolidBrush(leftColor);
861 FillRect(hdc, &leftBar, leftBrush);
862 DeleteObject(leftBrush);
863
864 // Add glow effect
865 COLORREF glowColor;
866 if (leftLevel > 0.8f) glowColor = RGB(128, 32, 32);
867 else if (leftLevel > 0.6f) glowColor = RGB(128, 128, 0);
868 else glowColor = RGB(0, 128, 32);
869
870 HPEN glowPen = CreatePen(PS_SOLID, 1, glowColor);
871 SelectObject(hdc, glowPen);
872 Rectangle(hdc, leftBar.left - 1, leftBar.top - 1, leftBar.right + 1, leftBar.bottom + 1);
873 DeleteObject(glowPen);
874 }
875
876 // Right channel meter with neon effect
877 int rightWidth = (int)(rightLevel * 65);
878 if (rightWidth > 0) {
879 RECT rightBar = {x + 8, y + 22, x + 8 + rightWidth, y + 27};
880
881 // Determine color based on level
882 COLORREF rightColor;
883 if (rightLevel > 0.8f) rightColor = RGB(255, 64, 64); // Red
884 else if (rightLevel > 0.6f) rightColor = RGB(255, 255, 0); // Yellow
885 else rightColor = RGB(0, 255, 64); // Green
886
887 HBRUSH rightBrush = CreateSolidBrush(rightColor);
888 FillRect(hdc, &rightBar, rightBrush);
889 DeleteObject(rightBrush);
890
891 // Add glow effect
892 COLORREF glowColor;
893 if (rightLevel > 0.8f) glowColor = RGB(128, 32, 32);
894 else if (rightLevel > 0.6f) glowColor = RGB(128, 128, 0);
895 else glowColor = RGB(0, 128, 32);
896
897 HPEN glowPen = CreatePen(PS_SOLID, 1, glowColor);
898 SelectObject(hdc, glowPen);
899 Rectangle(hdc, rightBar.left - 1, rightBar.top - 1, rightBar.right + 1, rightBar.bottom + 1);
900 DeleteObject(glowPen);
901 }
902
903 // Channel labels
904 SetTextColor(hdc, RGB(192, 192, 192));
905 TextOut(hdc, x + 75, y + 12, "L", 1);
906 TextOut(hdc, x + 75, y + 22, "R", 1);
907
908 // Scale marks
909 HPEN scalePen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
910 SelectObject(hdc, scalePen);
911 for (int i = 1; i < 10; i++) {
912 int markX = x + 8 + (i * 7);
913 MoveToEx(hdc, markX, y + 30, NULL);
914 LineTo(hdc, markX, y + 32);
915 }
916 DeleteObject(scalePen);
917 DeleteObject(vuFont);
918}
919
920void DrawPowerButton(HDC hdc, int x, int y, int radius, int power) {
921 // Chrome gradient button
922 for (int i = 0; i < 6; i++) {
923 int intensity = power ? (80 + i * 20) : (60 + i * 10);
924 COLORREF buttonColor = power ? RGB(255 - i * 20, intensity, intensity) : RGB(intensity, intensity, intensity);
925 HBRUSH buttonBrush = CreateSolidBrush(buttonColor);
926 SelectObject(hdc, buttonBrush);
927 Ellipse(hdc, x - radius + i, y - radius + i, x + radius - i, y + radius - i);
928 DeleteObject(buttonBrush);
929 }
930
931 // Inner button surface
932 COLORREF innerColor = power ? RGB(255, 128, 128) : RGB(128, 128, 128);
933 HBRUSH innerBrush = CreateSolidBrush(innerColor);
934 SelectObject(hdc, innerBrush);
935 Ellipse(hdc, x - radius + 6, y - radius + 6, x + radius - 6, y + radius - 6);
936 DeleteObject(innerBrush);
937
938 // Button border
939 HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(32, 32, 32));
940 SelectObject(hdc, borderPen);
941 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
942 DeleteObject(borderPen);
943
944 // Power symbol with glow effect
945 if (power) {
946 // Glow effect
947 HPEN glowPen = CreatePen(PS_SOLID, 5, RGB(255, 64, 64));
948 SelectObject(hdc, glowPen);
949 Arc(hdc, x - 10, y - 10, x + 10, y + 10, x + 8, y - 8, x - 8, y - 8);
950 MoveToEx(hdc, x, y - 12, NULL);
951 LineTo(hdc, x, y - 4);
952 DeleteObject(glowPen);
953
954 // Main symbol
955 HPEN symbolPen = CreatePen(PS_SOLID, 3, RGB(255, 255, 255));
956 SelectObject(hdc, symbolPen);
957 Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6);
958 MoveToEx(hdc, x, y - 10, NULL);
959 LineTo(hdc, x, y - 2);
960 DeleteObject(symbolPen);
961 } else {
962 // Dim power symbol
963 HPEN symbolPen = CreatePen(PS_SOLID, 2, RGB(64, 64, 64));
964 SelectObject(hdc, symbolPen);
965 Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6);
966 MoveToEx(hdc, x, y - 10, NULL);
967 LineTo(hdc, x, y - 2);
968 DeleteObject(symbolPen);
969 }
970
971 // Label
972 SetBkMode(hdc, TRANSPARENT);
973 SetTextColor(hdc, power ? RGB(255, 192, 192) : RGB(192, 192, 192));
974 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
975 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
976 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
977 DEFAULT_PITCH | FF_SWISS, "Tahoma");
978 SelectObject(hdc, labelFont);
979 SetTextAlign(hdc, TA_CENTER);
980 TextOut(hdc, x, y - radius - 18, "POWER", 5);
981 DeleteObject(labelFont);
982}
983
984int IsPointInCircle(int px, int py, int cx, int cy, int radius) {
985 int dx = px - cx;
986 int dy = py - cy;
987 return (dx * dx + dy * dy) <= (radius * radius);
988}
989
990float GetAngleFromPoint(int px, int py, int cx, int cy) {
991 return atan2((float)(py - cy), (float)(px - cx));
992}
993
994void UpdateFrequencyFromMouse(int mouseX, int mouseY) {
995 float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200);
996
997 // Convert angle to frequency (10-34 MHz range)
998 // Map 270 degree sweep from -135 to +135 degrees
999 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
1000
1001 // Clamp to valid range
1002 if (normalizedAngle < 0.0f) normalizedAngle = 0.0f;
1003 if (normalizedAngle > 1.0f) normalizedAngle = 1.0f;
1004
1005 // Map to frequency range
1006 g_radio.frequency = 10.0f + normalizedAngle * 24.0f;
1007
1008 // Clamp frequency
1009 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
1010 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
1011
1012 // Calculate signal strength based on nearest station
1013 RadioStation* nearestStation = FindNearestStation(g_radio.frequency);
1014 if (nearestStation) {
1015 g_radio.signalStrength = (int)(GetStationSignalStrength(nearestStation, g_radio.frequency) * 100.0f);
1016
1017 // Start streaming if signal is strong enough and station changed
1018 if (g_radio.signalStrength > 50 && nearestStation != g_audio.currentStation) {
1019 StopBassStreaming();
1020 StartBassStreaming(nearestStation);
1021 }
1022 } else {
1023 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
1024 StopBassStreaming();
1025 }
1026
1027 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
1028 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
1029
1030 UpdateStaticVolume(g_radio.signalStrength);
1031}
1032
1033void UpdateVolumeFromMouse(int mouseX, int mouseY) {
1034 float angle = GetAngleFromPoint(mouseX, mouseY, 350, 200);
1035
1036 // Convert angle to volume (0-1 range)
1037 // Map from -135 degrees to +135 degrees
1038 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
1039
1040 g_radio.volume = normalizedAngle;
1041
1042 // Clamp volume
1043 if (g_radio.volume < 0.0f) g_radio.volume = 0.0f;
1044 if (g_radio.volume > 1.0f) g_radio.volume = 1.0f;
1045
1046 // Update static volume when main volume changes
1047 UpdateStaticVolume(g_radio.signalStrength);
1048}
1049
1050int InitializeAudio() {
1051 // Initialize BASS with more detailed error reporting
1052 printf("Initializing BASS audio system...\n");
1053
1054 if (!BASS_Init(-1, 44100, 0, 0, NULL)) {
1055 DWORD error = BASS_ErrorGetCode();
1056 printf("BASS initialization failed (Error: %lu)\n", error);
1057
1058 // Try alternative initialization methods
1059 printf("Trying alternative audio device...\n");
1060 if (!BASS_Init(0, 44100, 0, 0, NULL)) {
1061 error = BASS_ErrorGetCode();
1062 printf("Alternative BASS init also failed (Error: %lu)\n", error);
1063 printf("BASS Error meanings:\n");
1064 printf(" 1 = BASS_ERROR_MEM (memory error)\n");
1065 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL error)\n");
1066 printf(" 3 = BASS_ERROR_DRIVER (no audio driver)\n");
1067 printf(" 8 = BASS_ERROR_ALREADY (already initialized)\n");
1068 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n");
1069 return -1;
1070 }
1071 }
1072
1073 printf("BASS initialized successfully\n");
1074
1075 // Get BASS version info
1076 DWORD version = BASS_GetVersion();
1077 printf("BASS version: %d.%d.%d.%d\n",
1078 HIBYTE(HIWORD(version)), LOBYTE(HIWORD(version)),
1079 HIBYTE(LOWORD(version)), LOBYTE(LOWORD(version)));
1080
1081 g_audio.currentStream = 0;
1082 g_audio.staticStream = 0;
1083 g_audio.isPlaying = 0;
1084 g_audio.staticVolume = 0.8f;
1085 g_audio.radioVolume = 0.0f;
1086 g_audio.currentStation = NULL;
1087 g_audio.vuLevelLeft = 0.0f;
1088 g_audio.vuLevelRight = 0.0f;
1089
1090 return 0;
1091}
1092
1093void CleanupAudio() {
1094 StopBassStreaming();
1095
1096 // Free BASS
1097 BASS_Free();
1098 printf("BASS cleaned up\n");
1099}
1100
1101void StartAudio() {
1102 if (!g_audio.isPlaying) {
1103 g_audio.isPlaying = 1;
1104 StartStaticNoise();
1105 printf("Audio started with static\n");
1106 }
1107}
1108
1109void StopAudio() {
1110 if (g_audio.isPlaying) {
1111 g_audio.isPlaying = 0;
1112 StopBassStreaming();
1113 StopStaticNoise();
1114 printf("Audio stopped\n");
1115 }
1116}
1117
1118RadioStation* FindNearestStation(float frequency) {
1119 RadioStation* nearest = NULL;
1120 float minDistance = 999.0f;
1121
1122 for (int i = 0; i < NUM_STATIONS; i++) {
1123 float distance = fabs(g_stations[i].frequency - frequency);
1124 if (distance < minDistance) {
1125 minDistance = distance;
1126 nearest = &g_stations[i];
1127 }
1128 }
1129
1130 // Only return station if we're close enough (within 0.5 MHz)
1131 if (minDistance <= 0.5f) {
1132 return nearest;
1133 }
1134
1135 return NULL;
1136}
1137
1138float GetStationSignalStrength(RadioStation* station, float currentFreq) {
1139 if (!station) return 0.0f;
1140
1141 float distance = fabs(station->frequency - currentFreq);
1142
1143 // Signal strength drops off with distance from exact frequency
1144 if (distance < 0.05f) {
1145 return 0.9f; // Very strong signal
1146 } else if (distance < 0.1f) {
1147 return 0.7f; // Strong signal
1148 } else if (distance < 0.2f) {
1149 return 0.5f; // Medium signal
1150 } else if (distance < 0.5f) {
1151 return 0.2f; // Weak signal
1152 }
1153
1154 return 0.0f; // No signal
1155}
1156
1157int StartBassStreaming(RadioStation* station) {
1158 if (!station) {
1159 printf("StartBassStreaming failed: no station\n");
1160 return 0;
1161 }
1162
1163 StopBassStreaming();
1164
1165 printf("Attempting to stream: %s at %s\n", station->name, station->streamUrl);
1166
1167 // Check if BASS is initialized
1168 if (!BASS_GetVersion()) {
1169 printf("BASS not initialized - cannot stream\n");
1170 return 0;
1171 }
1172
1173 // Create BASS stream from URL with more options
1174 printf("Creating BASS stream...\n");
1175 g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0,
1176 BASS_STREAM_BLOCK | BASS_STREAM_STATUS | BASS_STREAM_AUTOFREE, NULL, 0);
1177
1178 if (g_audio.currentStream) {
1179 printf("Successfully connected to stream: %s\n", station->name);
1180
1181 // Get stream info
1182 BASS_CHANNELINFO info;
1183 if (BASS_ChannelGetInfo(g_audio.currentStream, &info)) {
1184 printf("Stream info: %lu Hz, %lu channels, type=%lu\n",
1185 info.freq, info.chans, info.ctype);
1186 }
1187
1188 // Set volume based on signal strength and radio volume
1189 float volume = g_radio.volume * (g_radio.signalStrength / 100.0f);
1190 BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume);
1191 printf("Set volume to: %.2f\n", volume);
1192
1193 // Start playing
1194 if (BASS_ChannelPlay(g_audio.currentStream, FALSE)) {
1195 printf("Stream playback started\n");
1196 } else {
1197 DWORD error = BASS_ErrorGetCode();
1198 printf("Failed to start playback (BASS Error: %lu)\n", error);
1199 }
1200
1201 g_audio.currentStation = station;
1202 return 1;
1203 } else {
1204 DWORD error = BASS_ErrorGetCode();
1205 printf("Failed to connect to stream: %s (BASS Error: %lu)\n", station->name, error);
1206 printf("BASS Error meanings:\n");
1207 printf(" 1 = BASS_ERROR_MEM (out of memory)\n");
1208 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL cannot be opened)\n");
1209 printf(" 3 = BASS_ERROR_DRIVER (no audio driver available)\n");
1210 printf(" 6 = BASS_ERROR_FORMAT (unsupported format)\n");
1211 printf(" 7 = BASS_ERROR_POSITION (invalid position)\n");
1212 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n");
1213 printf(" 21 = BASS_ERROR_TIMEOUT (connection timeout)\n");
1214 printf(" 41 = BASS_ERROR_SSL (SSL/HTTPS not supported)\n");
1215 }
1216
1217 return 0;
1218}
1219
1220void StopBassStreaming() {
1221 if (g_audio.currentStream) {
1222 BASS_StreamFree(g_audio.currentStream);
1223 g_audio.currentStream = 0;
1224 printf("Stopped streaming\n");
1225 }
1226
1227 g_audio.currentStation = NULL;
1228}
1229
1230// Static noise generation callback
1231DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user) {
1232 short* samples = (short*)buffer;
1233 DWORD sampleCount = length / sizeof(short);
1234
1235 // Get current time for oscillation
1236 static DWORD startTime = GetTickCount();
1237 DWORD currentTime = GetTickCount();
1238 float timeSeconds = (currentTime - startTime) / 1000.0f;
1239
1240 // Create subtle volume oscillations (5-7% variation)
1241 // Use multiple sine waves at different frequencies for natural variation
1242 float oscillation1 = sin(timeSeconds * 0.7f) * 0.03f; // 3% slow oscillation
1243 float oscillation2 = sin(timeSeconds * 2.3f) * 0.02f; // 2% medium oscillation
1244 float oscillation3 = sin(timeSeconds * 5.1f) * 0.015f; // 1.5% fast oscillation
1245 float volumeVariation = 1.0f + oscillation1 + oscillation2 + oscillation3;
1246
1247 // Generate white noise with volume variation
1248 for (DWORD i = 0; i < sampleCount; i++) {
1249 // Generate random value between -32767 and 32767
1250 short baseNoise = (short)((rand() % 65535) - 32767);
1251
1252 // Apply volume variation
1253 samples[i] = (short)(baseNoise * volumeVariation);
1254 }
1255
1256 return length;
1257}
1258
1259void StartStaticNoise() {
1260 if (!g_audio.staticStream) {
1261 // Create a stream for static noise generation
1262 g_audio.staticStream = BASS_StreamCreate(SAMPLE_RATE, CHANNELS, 0, StaticStreamProc, NULL);
1263
1264 if (g_audio.staticStream) {
1265 // Set initial volume based on signal strength
1266 UpdateStaticVolume(g_radio.signalStrength);
1267
1268 // Start playing static
1269 BASS_ChannelPlay(g_audio.staticStream, FALSE);
1270 printf("Static noise started\n");
1271 } else {
1272 printf("Failed to create static stream\n");
1273 }
1274 }
1275}
1276
1277void StopStaticNoise() {
1278 if (g_audio.staticStream) {
1279 BASS_StreamFree(g_audio.staticStream);
1280 g_audio.staticStream = 0;
1281 printf("Static noise stopped\n");
1282 }
1283}
1284
1285void UpdateStaticVolume(float signalStrength) {
1286 if (g_audio.staticStream) {
1287 // Static volume is inverse of signal strength
1288 // Strong signal = less static, weak signal = more static
1289 float staticLevel = (100.0f - signalStrength) / 100.0f;
1290 float volume = g_radio.volume * staticLevel * g_audio.staticVolume;
1291
1292 // Ensure minimum static when radio is on but no strong signal
1293 if (g_radio.power && signalStrength < 50.0f) {
1294 volume = fmax(volume, g_radio.volume * 0.1f);
1295 }
1296
1297 BASS_ChannelSetAttribute(g_audio.staticStream, BASS_ATTRIB_VOL, volume);
1298 }
1299}
1300
1301void UpdateVULevels() {
1302 // Initialize levels to zero
1303 g_audio.vuLevelLeft = 0.0f;
1304 g_audio.vuLevelRight = 0.0f;
1305
1306 // Get levels from current stream if playing
1307 if (g_audio.currentStream && BASS_ChannelIsActive(g_audio.currentStream) == BASS_ACTIVE_PLAYING) {
1308 DWORD level = BASS_ChannelGetLevel(g_audio.currentStream);
1309 if (level != -1) {
1310 // Extract left and right channel levels
1311 g_audio.vuLevelLeft = (float)LOWORD(level) / 32768.0f;
1312 g_audio.vuLevelRight = (float)HIWORD(level) / 32768.0f;
1313 }
1314 }
1315
1316 // Add static contribution if static is playing
1317 if (g_audio.staticStream && BASS_ChannelIsActive(g_audio.staticStream) == BASS_ACTIVE_PLAYING) {
1318 DWORD staticLevel = BASS_ChannelGetLevel(g_audio.staticStream);
1319 if (staticLevel != -1) {
1320 float staticLeft = (float)LOWORD(staticLevel) / 32768.0f;
1321 float staticRight = (float)HIWORD(staticLevel) / 32768.0f;
1322
1323 // Combine with existing levels (simulate mixing)
1324 g_audio.vuLevelLeft = fmin(1.0f, g_audio.vuLevelLeft + staticLeft * 0.3f);
1325 g_audio.vuLevelRight = fmin(1.0f, g_audio.vuLevelRight + staticRight * 0.3f);
1326 }
1327 }
1328
1329 // Apply some smoothing/decay for more realistic VU behavior
1330 static float lastLeft = 0.0f, lastRight = 0.0f;
1331 g_audio.vuLevelLeft = g_audio.vuLevelLeft * 0.7f + lastLeft * 0.3f;
1332 g_audio.vuLevelRight = g_audio.vuLevelRight * 0.7f + lastRight * 0.3f;
1333 lastLeft = g_audio.vuLevelLeft;
1334 lastRight = g_audio.vuLevelRight;
1335}