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