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 // Create double buffer to eliminate flicker
197 HDC memDC = CreateCompatibleDC(hdc);
198 HBITMAP memBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
199 HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
200
201 // Update VU levels before drawing
202 if (g_radio.power) {
203 UpdateVULevels();
204 }
205
206 DrawRadioInterface(memDC, &rect);
207
208 // Copy from memory DC to screen
209 BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
210
211 // Cleanup
212 SelectObject(memDC, oldBitmap);
213 DeleteObject(memBitmap);
214 DeleteDC(memDC);
215
216 EndPaint(hwnd, &ps);
217 return 0;
218 }
219
220 case WM_LBUTTONDOWN: {
221 int mouseX = LOWORD(lParam);
222 int mouseY = HIWORD(lParam);
223
224 // Check if clicking on tuning dial
225 if (IsPointInCircle(mouseX, mouseY, 150, 200, 60)) {
226 g_radio.isDraggingDial = 1;
227 SetCapture(hwnd);
228 UpdateFrequencyFromMouse(mouseX, mouseY);
229 InvalidateRect(hwnd, NULL, TRUE);
230 }
231 // Check if clicking on volume knob
232 else if (IsPointInCircle(mouseX, mouseY, 350, 200, 30)) {
233 g_radio.isDraggingVolume = 1;
234 SetCapture(hwnd);
235 UpdateVolumeFromMouse(mouseX, mouseY);
236 InvalidateRect(hwnd, NULL, TRUE);
237 }
238 // Check if clicking on power button
239 else if (IsPointInCircle(mouseX, mouseY, 500, 120, 25)) {
240 g_radio.power = !g_radio.power;
241 if (g_radio.power) {
242 StartAudio();
243 } else {
244 StopAudio();
245 }
246 InvalidateRect(hwnd, NULL, TRUE);
247 }
248 return 0;
249 }
250
251 case WM_LBUTTONUP: {
252 if (g_radio.isDraggingDial || g_radio.isDraggingVolume) {
253 g_radio.isDraggingDial = 0;
254 g_radio.isDraggingVolume = 0;
255 ReleaseCapture();
256 }
257 return 0;
258 }
259
260 case WM_MOUSEMOVE: {
261 if (g_radio.isDraggingDial) {
262 int mouseX = LOWORD(lParam);
263 int mouseY = HIWORD(lParam);
264 UpdateFrequencyFromMouse(mouseX, mouseY);
265 InvalidateRect(hwnd, NULL, TRUE);
266 }
267 else if (g_radio.isDraggingVolume) {
268 int mouseX = LOWORD(lParam);
269 int mouseY = HIWORD(lParam);
270 UpdateVolumeFromMouse(mouseX, mouseY);
271 InvalidateRect(hwnd, NULL, TRUE);
272 }
273 return 0;
274 }
275
276 case WM_KEYDOWN: {
277 switch (wParam) {
278 case VK_UP: {
279 // Increase frequency by 0.1 MHz (fine tuning)
280 g_radio.frequency += 0.1f;
281 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
282
283 // Update signal strength for new frequency
284 RadioStation* station = FindNearestStation(g_radio.frequency);
285 if (station) {
286 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
287 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
288 StopBassStreaming();
289 StartBassStreaming(station);
290 }
291 } else {
292 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
293 StopBassStreaming();
294 }
295
296 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
297 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
298
299 UpdateStaticVolume(g_radio.signalStrength);
300 InvalidateRect(hwnd, NULL, TRUE);
301 break;
302 }
303
304 case VK_DOWN: {
305 // Decrease frequency by 0.1 MHz (fine tuning)
306 g_radio.frequency -= 0.1f;
307 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
308
309 // Update signal strength for new frequency
310 RadioStation* station = FindNearestStation(g_radio.frequency);
311 if (station) {
312 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
313 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
314 StopBassStreaming();
315 StartBassStreaming(station);
316 }
317 } else {
318 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
319 StopBassStreaming();
320 }
321
322 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
323 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
324
325 UpdateStaticVolume(g_radio.signalStrength);
326 InvalidateRect(hwnd, NULL, TRUE);
327 break;
328 }
329
330 case VK_RIGHT: {
331 // Increase frequency by 1.0 MHz (coarse tuning)
332 g_radio.frequency += 1.0f;
333 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
334
335 // Update signal strength for new frequency
336 RadioStation* station = FindNearestStation(g_radio.frequency);
337 if (station) {
338 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
339 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
340 StopBassStreaming();
341 StartBassStreaming(station);
342 }
343 } else {
344 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
345 StopBassStreaming();
346 }
347
348 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
349 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
350
351 UpdateStaticVolume(g_radio.signalStrength);
352 InvalidateRect(hwnd, NULL, TRUE);
353 break;
354 }
355
356 case VK_LEFT: {
357 // Decrease frequency by 1.0 MHz (coarse tuning)
358 g_radio.frequency -= 1.0f;
359 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
360
361 // Update signal strength for new frequency
362 RadioStation* station = FindNearestStation(g_radio.frequency);
363 if (station) {
364 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f);
365 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) {
366 StopBassStreaming();
367 StartBassStreaming(station);
368 }
369 } else {
370 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
371 StopBassStreaming();
372 }
373
374 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
375 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
376
377 UpdateStaticVolume(g_radio.signalStrength);
378 InvalidateRect(hwnd, NULL, TRUE);
379 break;
380 }
381 }
382 return 0;
383 }
384
385 case WM_COMMAND:
386 switch (LOWORD(wParam)) {
387 case ID_ABOUT: {
388 const char* aboutText = "Shortwave Radio Tuner\n\n"
389 "Version: 1.0.0\n"
390 "Built for Rewind V2 Hackathon\n\n"
391 "A vintage shortwave radio simulator\n"
392 "compatible with Windows XP\n\n"
393 "Features:\n"
394 "- Realistic tuning interface\n"
395 "- Internet radio streaming\n"
396 "- Authentic static noise\n\n"
397 "Controls:\n"
398 "- Drag tuning dial to change frequency\n"
399 "- UP/DOWN arrows: Fine tuning (0.1 MHz)\n"
400 "- LEFT/RIGHT arrows: Coarse tuning (1.0 MHz)\n"
401 "- Click power button to turn on/off\n"
402 "- Drag volume knob to adjust volume";
403 MessageBox(hwnd, aboutText, "About Shortwave Radio",
404 MB_OK | MB_ICONINFORMATION);
405 break;
406 }
407 case ID_EXIT:
408 PostQuitMessage(0);
409 break;
410 }
411 return 0;
412
413 case WM_TIMER: {
414 // Timer for VU meter updates - only invalidate VU meter area
415 if (g_radio.power) {
416 RECT vuRect = {440, 190, 540, 250};
417 InvalidateRect(hwnd, &vuRect, FALSE);
418 }
419 return 0;
420 }
421 }
422
423 return DefWindowProc(hwnd, uMsg, wParam, lParam);
424}
425
426void DrawRadioInterface(HDC hdc, RECT* rect) {
427 // Winamp-style dark gradient background
428 HBRUSH darkBrush = CreateSolidBrush(RGB(24, 24, 24));
429 FillRect(hdc, rect, darkBrush);
430 DeleteObject(darkBrush);
431
432 // Main panel with simplified metallic gradient effect
433 RECT panel = {10, 10, rect->right - 10, rect->bottom - 10};
434
435 // Simplified gradient effect with fewer steps
436 for (int i = 0; i < 8; i++) {
437 int gray = 45 + i * 3;
438 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
439 RECT gradRect = {panel.left + i, panel.top + i, panel.right - i, panel.bottom - i};
440 FrameRect(hdc, &gradRect, gradBrush);
441 DeleteObject(gradBrush);
442 }
443
444 // Inner panel with darker metallic look
445 RECT innerPanel = {30, 30, rect->right - 30, rect->bottom - 30};
446 HBRUSH innerBrush = CreateSolidBrush(RGB(32, 32, 32));
447 FillRect(hdc, &innerPanel, innerBrush);
448 DeleteObject(innerBrush);
449
450 // Winamp-style beveled border
451 HPEN lightPen = CreatePen(PS_SOLID, 1, RGB(128, 128, 128));
452 HPEN darkPen = CreatePen(PS_SOLID, 1, RGB(16, 16, 16));
453
454 SelectObject(hdc, lightPen);
455 MoveToEx(hdc, innerPanel.left, innerPanel.bottom, NULL);
456 LineTo(hdc, innerPanel.left, innerPanel.top);
457 LineTo(hdc, innerPanel.right, innerPanel.top);
458
459 SelectObject(hdc, darkPen);
460 LineTo(hdc, innerPanel.right, innerPanel.bottom);
461 LineTo(hdc, innerPanel.left, innerPanel.bottom);
462
463 DeleteObject(lightPen);
464 DeleteObject(darkPen);
465
466 // Draw frequency display with Winamp-style LCD
467 DrawFrequencyDisplay(hdc, 200, 80, g_radio.frequency);
468
469 // Draw tuning dial with metallic look
470 DrawTuningDial(hdc, 150, 200, 60, g_radio.frequency);
471
472 // Draw volume knob with chrome effect
473 DrawVolumeKnob(hdc, 350, 200, 30, g_radio.volume);
474
475 // Draw signal meter with neon bars
476 DrawSignalMeter(hdc, 450, 170, g_radio.signalStrength);
477
478 // Draw VU meter with classic Winamp style
479 DrawVUMeter(hdc, 450, 200, g_audio.vuLevelLeft, g_audio.vuLevelRight);
480
481 // Draw power button with LED glow
482 DrawPowerButton(hdc, 500, 120, 25, g_radio.power);
483
484 // Draw station info with Winamp-style ticker
485 RadioStation* currentStation = FindNearestStation(g_radio.frequency);
486 if (currentStation && g_radio.signalStrength > 30) {
487 RECT stationRect = {50, 320, 550, 360};
488
489 // Winamp-style display background
490 HBRUSH displayBrush = CreateSolidBrush(RGB(0, 0, 0));
491 FillRect(hdc, &stationRect, displayBrush);
492 DeleteObject(displayBrush);
493
494 // Beveled border
495 HPEN lightBorderPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
496 HPEN darkBorderPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
497
498 SelectObject(hdc, darkBorderPen);
499 MoveToEx(hdc, stationRect.left, stationRect.bottom, NULL);
500 LineTo(hdc, stationRect.left, stationRect.top);
501 LineTo(hdc, stationRect.right, stationRect.top);
502
503 SelectObject(hdc, lightBorderPen);
504 LineTo(hdc, stationRect.right, stationRect.bottom);
505 LineTo(hdc, stationRect.left, stationRect.bottom);
506
507 DeleteObject(lightBorderPen);
508 DeleteObject(darkBorderPen);
509
510 // Winamp-style green text
511 SetTextColor(hdc, RGB(0, 255, 0));
512 SetBkMode(hdc, TRANSPARENT);
513 HFONT stationFont = CreateFont(14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
514 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
515 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
516 DEFAULT_PITCH | FF_MODERN, "Tahoma");
517 SelectObject(hdc, stationFont);
518
519 char stationText[256];
520 sprintf(stationText, "%.3f MHz - %s: %s",
521 currentStation->frequency, currentStation->name, currentStation->description);
522
523 SetTextAlign(hdc, TA_LEFT);
524 TextOut(hdc, stationRect.left + 10, stationRect.top + 12, stationText, strlen(stationText));
525
526 DeleteObject(stationFont);
527 }
528}
529
530void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency) {
531 // Winamp-style LCD display with beveled edges
532 RECT display = {x - 100, y - 25, x + 100, y + 25};
533
534 // Dark background
535 HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
536 FillRect(hdc, &display, blackBrush);
537 DeleteObject(blackBrush);
538
539 // Beveled border effect
540 HPEN lightPen = CreatePen(PS_SOLID, 2, RGB(96, 96, 96));
541 HPEN darkPen = CreatePen(PS_SOLID, 2, RGB(32, 32, 32));
542
543 SelectObject(hdc, darkPen);
544 MoveToEx(hdc, display.left, display.bottom, NULL);
545 LineTo(hdc, display.left, display.top);
546 LineTo(hdc, display.right, display.top);
547
548 SelectObject(hdc, lightPen);
549 LineTo(hdc, display.right, display.bottom);
550 LineTo(hdc, display.left, display.bottom);
551
552 DeleteObject(lightPen);
553 DeleteObject(darkPen);
554
555 // Inner shadow
556 RECT innerDisplay = {display.left + 3, display.top + 3, display.right - 3, display.bottom - 3};
557 HPEN shadowPen = CreatePen(PS_SOLID, 1, RGB(16, 16, 16));
558 SelectObject(hdc, shadowPen);
559 Rectangle(hdc, innerDisplay.left, innerDisplay.top, innerDisplay.right, innerDisplay.bottom);
560 DeleteObject(shadowPen);
561
562 // Frequency text with glow effect
563 char freqText[32];
564 sprintf(freqText, "%.3f MHz", frequency);
565
566 SetBkMode(hdc, TRANSPARENT);
567
568 // Create glow effect by drawing text multiple times with slight offsets
569 HFONT lcdFont = CreateFont(20, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
570 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
571 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
572 FIXED_PITCH | FF_MODERN, "Consolas");
573 SelectObject(hdc, lcdFont);
574 SetTextAlign(hdc, TA_CENTER);
575
576 // Glow effect (dark green)
577 SetTextColor(hdc, RGB(0, 128, 0));
578 for (int dx = -1; dx <= 1; dx++) {
579 for (int dy = -1; dy <= 1; dy++) {
580 if (dx != 0 || dy != 0) {
581 TextOut(hdc, x + dx, y - 10 + dy, freqText, strlen(freqText));
582 }
583 }
584 }
585
586 // Main text (bright green)
587 SetTextColor(hdc, RGB(0, 255, 0));
588 TextOut(hdc, x, y - 10, freqText, strlen(freqText));
589
590 DeleteObject(lcdFont);
591}
592
593void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency) {
594 // Simplified metallic dial with fewer gradient steps
595 for (int i = 0; i < 4; i++) {
596 int gray = 80 + i * 20;
597 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
598 SelectObject(hdc, gradBrush);
599 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2);
600 DeleteObject(gradBrush);
601 }
602
603 // Inner dial surface
604 HBRUSH dialBrush = CreateSolidBrush(RGB(160, 160, 160));
605 SelectObject(hdc, dialBrush);
606 Ellipse(hdc, x - radius + 8, y - radius + 8, x + radius - 8, y + radius - 8);
607 DeleteObject(dialBrush);
608
609 // Outer ring
610 HPEN ringPen = CreatePen(PS_SOLID, 2, RGB(48, 48, 48));
611 SelectObject(hdc, ringPen);
612 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
613 DeleteObject(ringPen);
614
615 // Tick marks with better contrast
616 HPEN tickPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
617 SelectObject(hdc, tickPen);
618
619 // Draw major tick marks and frequency labels
620 SetTextColor(hdc, RGB(0, 0, 0));
621 SetBkMode(hdc, TRANSPARENT);
622 HFONT smallFont = CreateFont(9, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
623 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
624 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
625 DEFAULT_PITCH | FF_SWISS, "Tahoma");
626 SelectObject(hdc, smallFont);
627 SetTextAlign(hdc, TA_CENTER);
628
629 // 270 degree sweep from -135 to +135 degrees
630 for (int i = 0; i < 12; i++) {
631 float angle = -3.14159f * 0.75f + (float)i * (3.14159f * 1.5f) / 11.0f;
632
633 // Major tick marks
634 int tickStartX = x + (int)((radius - 12) * cos(angle));
635 int tickStartY = y + (int)((radius - 12) * sin(angle));
636 int tickEndX = x + (int)((radius - 4) * cos(angle));
637 int tickEndY = y + (int)((radius - 4) * sin(angle));
638 MoveToEx(hdc, tickStartX, tickStartY, NULL);
639 LineTo(hdc, tickEndX, tickEndY);
640
641 // Frequency labels
642 int markX = x + (int)((radius - 22) * cos(angle));
643 int markY = y + (int)((radius - 22) * sin(angle));
644 char mark[8];
645 sprintf(mark, "%d", 10 + i * 2);
646 TextOut(hdc, markX, markY - 4, mark, strlen(mark));
647 }
648
649 // Draw minor tick marks
650 for (int i = 0; i < 11; i++) {
651 float angle = -3.14159f * 0.75f + ((float)i + 0.5f) * (3.14159f * 1.5f) / 11.0f;
652 int tickStartX = x + (int)((radius - 8) * cos(angle));
653 int tickStartY = y + (int)((radius - 8) * sin(angle));
654 int tickEndX = x + (int)((radius - 4) * cos(angle));
655 int tickEndY = y + (int)((radius - 4) * sin(angle));
656 MoveToEx(hdc, tickStartX, tickStartY, NULL);
657 LineTo(hdc, tickEndX, tickEndY);
658 }
659
660 DeleteObject(tickPen);
661
662 // Simplified pointer
663 float normalizedFreq = (frequency - 10.0f) / 24.0f;
664 float angle = -3.14159f * 0.75f + normalizedFreq * (3.14159f * 1.5f);
665 int pointerX = x + (int)((radius - 15) * cos(angle));
666 int pointerY = y + (int)((radius - 15) * sin(angle));
667
668 // Main pointer
669 HPEN pointerPen = CreatePen(PS_SOLID, 3, RGB(255, 64, 64));
670 SelectObject(hdc, pointerPen);
671 MoveToEx(hdc, x, y, NULL);
672 LineTo(hdc, pointerX, pointerY);
673 DeleteObject(pointerPen);
674
675 // Center dot
676 HBRUSH centerBrush = CreateSolidBrush(RGB(64, 64, 64));
677 SelectObject(hdc, centerBrush);
678 Ellipse(hdc, x - 4, y - 4, x + 4, y + 4);
679 DeleteObject(centerBrush);
680
681 DeleteObject(smallFont);
682
683 // Label with Winamp style
684 SetTextColor(hdc, RGB(192, 192, 192));
685 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
686 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
687 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
688 DEFAULT_PITCH | FF_SWISS, "Tahoma");
689 SelectObject(hdc, labelFont);
690 SetTextAlign(hdc, TA_CENTER);
691 TextOut(hdc, x, y + radius + 15, "TUNING", 6);
692 DeleteObject(labelFont);
693}
694
695void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume) {
696 // Simplified chrome gradient knob
697 for (int i = 0; i < 3; i++) {
698 int gray = 100 + i * 30;
699 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray));
700 SelectObject(hdc, gradBrush);
701 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2);
702 DeleteObject(gradBrush);
703 }
704
705 // Inner knob surface
706 HBRUSH knobBrush = CreateSolidBrush(RGB(180, 180, 180));
707 SelectObject(hdc, knobBrush);
708 Ellipse(hdc, x - radius + 6, y - radius + 6, x + radius - 6, y + radius - 6);
709 DeleteObject(knobBrush);
710
711 // Outer ring
712 HPEN ringPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64));
713 SelectObject(hdc, ringPen);
714 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
715 DeleteObject(ringPen);
716
717 // Volume indicator
718 float angle = volume * 3.14159f * 1.5f - 3.14159f * 0.75f;
719 int indicatorX = x + (int)((radius - 8) * cos(angle));
720 int indicatorY = y + (int)((radius - 8) * sin(angle));
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 // Simplified chrome gradient button
922 for (int i = 0; i < 3; i++) {
923 int intensity = power ? (80 + i * 40) : (60 + i * 20);
924 COLORREF buttonColor = power ? RGB(255 - i * 40, intensity, intensity) : RGB(intensity, intensity, intensity);
925 HBRUSH buttonBrush = CreateSolidBrush(buttonColor);
926 SelectObject(hdc, buttonBrush);
927 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2);
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
945 if (power) {
946 // Main symbol
947 HPEN symbolPen = CreatePen(PS_SOLID, 3, RGB(255, 255, 255));
948 SelectObject(hdc, symbolPen);
949 Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6);
950 MoveToEx(hdc, x, y - 10, NULL);
951 LineTo(hdc, x, y - 2);
952 DeleteObject(symbolPen);
953 } else {
954 // Dim power symbol
955 HPEN symbolPen = CreatePen(PS_SOLID, 2, RGB(64, 64, 64));
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 }
962
963 // Label
964 SetBkMode(hdc, TRANSPARENT);
965 SetTextColor(hdc, power ? RGB(255, 192, 192) : RGB(192, 192, 192));
966 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
967 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
968 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
969 DEFAULT_PITCH | FF_SWISS, "Tahoma");
970 SelectObject(hdc, labelFont);
971 SetTextAlign(hdc, TA_CENTER);
972 TextOut(hdc, x, y - radius - 18, "POWER", 5);
973 DeleteObject(labelFont);
974}
975
976int IsPointInCircle(int px, int py, int cx, int cy, int radius) {
977 int dx = px - cx;
978 int dy = py - cy;
979 return (dx * dx + dy * dy) <= (radius * radius);
980}
981
982float GetAngleFromPoint(int px, int py, int cx, int cy) {
983 return atan2((float)(py - cy), (float)(px - cx));
984}
985
986void UpdateFrequencyFromMouse(int mouseX, int mouseY) {
987 float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200);
988
989 // Convert angle to frequency (10-34 MHz range)
990 // Map 270 degree sweep from -135 to +135 degrees
991 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
992
993 // Clamp to valid range
994 if (normalizedAngle < 0.0f) normalizedAngle = 0.0f;
995 if (normalizedAngle > 1.0f) normalizedAngle = 1.0f;
996
997 // Map to frequency range
998 g_radio.frequency = 10.0f + normalizedAngle * 24.0f;
999
1000 // Clamp frequency
1001 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f;
1002 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f;
1003
1004 // Calculate signal strength based on nearest station
1005 RadioStation* nearestStation = FindNearestStation(g_radio.frequency);
1006 if (nearestStation) {
1007 g_radio.signalStrength = (int)(GetStationSignalStrength(nearestStation, g_radio.frequency) * 100.0f);
1008
1009 // Start streaming if signal is strong enough and station changed
1010 if (g_radio.signalStrength > 50 && nearestStation != g_audio.currentStation) {
1011 StopBassStreaming();
1012 StartBassStreaming(nearestStation);
1013 }
1014 } else {
1015 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency));
1016 StopBassStreaming();
1017 }
1018
1019 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
1020 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
1021
1022 UpdateStaticVolume(g_radio.signalStrength);
1023}
1024
1025void UpdateVolumeFromMouse(int mouseX, int mouseY) {
1026 float angle = GetAngleFromPoint(mouseX, mouseY, 350, 200);
1027
1028 // Convert angle to volume (0-1 range)
1029 // Map from -135 degrees to +135 degrees
1030 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f);
1031
1032 g_radio.volume = normalizedAngle;
1033
1034 // Clamp volume
1035 if (g_radio.volume < 0.0f) g_radio.volume = 0.0f;
1036 if (g_radio.volume > 1.0f) g_radio.volume = 1.0f;
1037
1038 // Update static volume when main volume changes
1039 UpdateStaticVolume(g_radio.signalStrength);
1040}
1041
1042int InitializeAudio() {
1043 // Initialize BASS with more detailed error reporting
1044 printf("Initializing BASS audio system...\n");
1045
1046 if (!BASS_Init(-1, 44100, 0, 0, NULL)) {
1047 DWORD error = BASS_ErrorGetCode();
1048 printf("BASS initialization failed (Error: %lu)\n", error);
1049
1050 // Try alternative initialization methods
1051 printf("Trying alternative audio device...\n");
1052 if (!BASS_Init(0, 44100, 0, 0, NULL)) {
1053 error = BASS_ErrorGetCode();
1054 printf("Alternative BASS init also failed (Error: %lu)\n", error);
1055 printf("BASS Error meanings:\n");
1056 printf(" 1 = BASS_ERROR_MEM (memory error)\n");
1057 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL error)\n");
1058 printf(" 3 = BASS_ERROR_DRIVER (no audio driver)\n");
1059 printf(" 8 = BASS_ERROR_ALREADY (already initialized)\n");
1060 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n");
1061 return -1;
1062 }
1063 }
1064
1065 printf("BASS initialized successfully\n");
1066
1067 // Get BASS version info
1068 DWORD version = BASS_GetVersion();
1069 printf("BASS version: %d.%d.%d.%d\n",
1070 HIBYTE(HIWORD(version)), LOBYTE(HIWORD(version)),
1071 HIBYTE(LOWORD(version)), LOBYTE(LOWORD(version)));
1072
1073 g_audio.currentStream = 0;
1074 g_audio.staticStream = 0;
1075 g_audio.isPlaying = 0;
1076 g_audio.staticVolume = 0.8f;
1077 g_audio.radioVolume = 0.0f;
1078 g_audio.currentStation = NULL;
1079 g_audio.vuLevelLeft = 0.0f;
1080 g_audio.vuLevelRight = 0.0f;
1081
1082 return 0;
1083}
1084
1085void CleanupAudio() {
1086 StopBassStreaming();
1087
1088 // Free BASS
1089 BASS_Free();
1090 printf("BASS cleaned up\n");
1091}
1092
1093void StartAudio() {
1094 if (!g_audio.isPlaying) {
1095 g_audio.isPlaying = 1;
1096 StartStaticNoise();
1097 printf("Audio started with static\n");
1098 }
1099}
1100
1101void StopAudio() {
1102 if (g_audio.isPlaying) {
1103 g_audio.isPlaying = 0;
1104 StopBassStreaming();
1105 StopStaticNoise();
1106 printf("Audio stopped\n");
1107 }
1108}
1109
1110RadioStation* FindNearestStation(float frequency) {
1111 RadioStation* nearest = NULL;
1112 float minDistance = 999.0f;
1113
1114 for (int i = 0; i < NUM_STATIONS; i++) {
1115 float distance = fabs(g_stations[i].frequency - frequency);
1116 if (distance < minDistance) {
1117 minDistance = distance;
1118 nearest = &g_stations[i];
1119 }
1120 }
1121
1122 // Only return station if we're close enough (within 0.5 MHz)
1123 if (minDistance <= 0.5f) {
1124 return nearest;
1125 }
1126
1127 return NULL;
1128}
1129
1130float GetStationSignalStrength(RadioStation* station, float currentFreq) {
1131 if (!station) return 0.0f;
1132
1133 float distance = fabs(station->frequency - currentFreq);
1134
1135 // Signal strength drops off with distance from exact frequency
1136 if (distance < 0.05f) {
1137 return 0.9f; // Very strong signal
1138 } else if (distance < 0.1f) {
1139 return 0.7f; // Strong signal
1140 } else if (distance < 0.2f) {
1141 return 0.5f; // Medium signal
1142 } else if (distance < 0.5f) {
1143 return 0.2f; // Weak signal
1144 }
1145
1146 return 0.0f; // No signal
1147}
1148
1149int StartBassStreaming(RadioStation* station) {
1150 if (!station) {
1151 printf("StartBassStreaming failed: no station\n");
1152 return 0;
1153 }
1154
1155 StopBassStreaming();
1156
1157 printf("Attempting to stream: %s at %s\n", station->name, station->streamUrl);
1158
1159 // Check if BASS is initialized
1160 if (!BASS_GetVersion()) {
1161 printf("BASS not initialized - cannot stream\n");
1162 return 0;
1163 }
1164
1165 // Create BASS stream from URL with more options
1166 printf("Creating BASS stream...\n");
1167 g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0,
1168 BASS_STREAM_BLOCK | BASS_STREAM_STATUS | BASS_STREAM_AUTOFREE, NULL, 0);
1169
1170 if (g_audio.currentStream) {
1171 printf("Successfully connected to stream: %s\n", station->name);
1172
1173 // Get stream info
1174 BASS_CHANNELINFO info;
1175 if (BASS_ChannelGetInfo(g_audio.currentStream, &info)) {
1176 printf("Stream info: %lu Hz, %lu channels, type=%lu\n",
1177 info.freq, info.chans, info.ctype);
1178 }
1179
1180 // Set volume based on signal strength and radio volume
1181 float volume = g_radio.volume * (g_radio.signalStrength / 100.0f);
1182 BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume);
1183 printf("Set volume to: %.2f\n", volume);
1184
1185 // Start playing
1186 if (BASS_ChannelPlay(g_audio.currentStream, FALSE)) {
1187 printf("Stream playback started\n");
1188 } else {
1189 DWORD error = BASS_ErrorGetCode();
1190 printf("Failed to start playback (BASS Error: %lu)\n", error);
1191 }
1192
1193 g_audio.currentStation = station;
1194 return 1;
1195 } else {
1196 DWORD error = BASS_ErrorGetCode();
1197 printf("Failed to connect to stream: %s (BASS Error: %lu)\n", station->name, error);
1198 printf("BASS Error meanings:\n");
1199 printf(" 1 = BASS_ERROR_MEM (out of memory)\n");
1200 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL cannot be opened)\n");
1201 printf(" 3 = BASS_ERROR_DRIVER (no audio driver available)\n");
1202 printf(" 6 = BASS_ERROR_FORMAT (unsupported format)\n");
1203 printf(" 7 = BASS_ERROR_POSITION (invalid position)\n");
1204 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n");
1205 printf(" 21 = BASS_ERROR_TIMEOUT (connection timeout)\n");
1206 printf(" 41 = BASS_ERROR_SSL (SSL/HTTPS not supported)\n");
1207 }
1208
1209 return 0;
1210}
1211
1212void StopBassStreaming() {
1213 if (g_audio.currentStream) {
1214 BASS_StreamFree(g_audio.currentStream);
1215 g_audio.currentStream = 0;
1216 printf("Stopped streaming\n");
1217 }
1218
1219 g_audio.currentStation = NULL;
1220}
1221
1222// Static noise generation callback
1223DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user) {
1224 short* samples = (short*)buffer;
1225 DWORD sampleCount = length / sizeof(short);
1226
1227 // Get current time for oscillation
1228 static DWORD startTime = GetTickCount();
1229 DWORD currentTime = GetTickCount();
1230 float timeSeconds = (currentTime - startTime) / 1000.0f;
1231
1232 // Create subtle volume oscillations (5-7% variation)
1233 // Use multiple sine waves at different frequencies for natural variation
1234 float oscillation1 = sin(timeSeconds * 0.7f) * 0.03f; // 3% slow oscillation
1235 float oscillation2 = sin(timeSeconds * 2.3f) * 0.02f; // 2% medium oscillation
1236 float oscillation3 = sin(timeSeconds * 5.1f) * 0.015f; // 1.5% fast oscillation
1237 float volumeVariation = 1.0f + oscillation1 + oscillation2 + oscillation3;
1238
1239 // Generate white noise with volume variation
1240 for (DWORD i = 0; i < sampleCount; i++) {
1241 // Generate random value between -32767 and 32767
1242 short baseNoise = (short)((rand() % 65535) - 32767);
1243
1244 // Apply volume variation
1245 samples[i] = (short)(baseNoise * volumeVariation);
1246 }
1247
1248 return length;
1249}
1250
1251void StartStaticNoise() {
1252 if (!g_audio.staticStream) {
1253 // Create a stream for static noise generation
1254 g_audio.staticStream = BASS_StreamCreate(SAMPLE_RATE, CHANNELS, 0, StaticStreamProc, NULL);
1255
1256 if (g_audio.staticStream) {
1257 // Set initial volume based on signal strength
1258 UpdateStaticVolume(g_radio.signalStrength);
1259
1260 // Start playing static
1261 BASS_ChannelPlay(g_audio.staticStream, FALSE);
1262 printf("Static noise started\n");
1263 } else {
1264 printf("Failed to create static stream\n");
1265 }
1266 }
1267}
1268
1269void StopStaticNoise() {
1270 if (g_audio.staticStream) {
1271 BASS_StreamFree(g_audio.staticStream);
1272 g_audio.staticStream = 0;
1273 printf("Static noise stopped\n");
1274 }
1275}
1276
1277void UpdateStaticVolume(float signalStrength) {
1278 if (g_audio.staticStream) {
1279 // Static volume is inverse of signal strength
1280 // Strong signal = less static, weak signal = more static
1281 float staticLevel = (100.0f - signalStrength) / 100.0f;
1282 float volume = g_radio.volume * staticLevel * g_audio.staticVolume;
1283
1284 // Ensure minimum static when radio is on but no strong signal
1285 if (g_radio.power && signalStrength < 50.0f) {
1286 volume = fmax(volume, g_radio.volume * 0.1f);
1287 }
1288
1289 BASS_ChannelSetAttribute(g_audio.staticStream, BASS_ATTRIB_VOL, volume);
1290 }
1291}
1292
1293void UpdateVULevels() {
1294 // Initialize levels to zero
1295 g_audio.vuLevelLeft = 0.0f;
1296 g_audio.vuLevelRight = 0.0f;
1297
1298 // Get levels from current stream if playing
1299 if (g_audio.currentStream && BASS_ChannelIsActive(g_audio.currentStream) == BASS_ACTIVE_PLAYING) {
1300 DWORD level = BASS_ChannelGetLevel(g_audio.currentStream);
1301 if (level != -1) {
1302 // Extract left and right channel levels
1303 g_audio.vuLevelLeft = (float)LOWORD(level) / 32768.0f;
1304 g_audio.vuLevelRight = (float)HIWORD(level) / 32768.0f;
1305 }
1306 }
1307
1308 // Add static contribution if static is playing
1309 if (g_audio.staticStream && BASS_ChannelIsActive(g_audio.staticStream) == BASS_ACTIVE_PLAYING) {
1310 DWORD staticLevel = BASS_ChannelGetLevel(g_audio.staticStream);
1311 if (staticLevel != -1) {
1312 float staticLeft = (float)LOWORD(staticLevel) / 32768.0f;
1313 float staticRight = (float)HIWORD(staticLevel) / 32768.0f;
1314
1315 // Combine with existing levels (simulate mixing)
1316 g_audio.vuLevelLeft = fmin(1.0f, g_audio.vuLevelLeft + staticLeft * 0.3f);
1317 g_audio.vuLevelRight = fmin(1.0f, g_audio.vuLevelRight + staticRight * 0.3f);
1318 }
1319 }
1320
1321 // Apply some smoothing/decay for more realistic VU behavior
1322 static float lastLeft = 0.0f, lastRight = 0.0f;
1323 g_audio.vuLevelLeft = g_audio.vuLevelLeft * 0.7f + lastLeft * 0.3f;
1324 g_audio.vuLevelRight = g_audio.vuLevelRight * 0.7f + lastRight * 0.3f;
1325 lastLeft = g_audio.vuLevelLeft;
1326 lastRight = g_audio.vuLevelRight;
1327}