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}