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