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