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