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 // Create double buffer to eliminate flicker 197 HDC memDC = CreateCompatibleDC(hdc); 198 HBITMAP memBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom); 199 HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap); 200 201 // Update VU levels before drawing 202 if (g_radio.power) { 203 UpdateVULevels(); 204 } 205 206 DrawRadioInterface(memDC, &rect); 207 208 // Copy from memory DC to screen 209 BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY); 210 211 // Cleanup 212 SelectObject(memDC, oldBitmap); 213 DeleteObject(memBitmap); 214 DeleteDC(memDC); 215 216 EndPaint(hwnd, &ps); 217 return 0; 218 } 219 220 case WM_LBUTTONDOWN: { 221 int mouseX = LOWORD(lParam); 222 int mouseY = HIWORD(lParam); 223 224 // Check if clicking on tuning dial 225 if (IsPointInCircle(mouseX, mouseY, 150, 200, 60)) { 226 g_radio.isDraggingDial = 1; 227 SetCapture(hwnd); 228 UpdateFrequencyFromMouse(mouseX, mouseY); 229 InvalidateRect(hwnd, NULL, TRUE); 230 } 231 // Check if clicking on volume knob 232 else if (IsPointInCircle(mouseX, mouseY, 350, 200, 30)) { 233 g_radio.isDraggingVolume = 1; 234 SetCapture(hwnd); 235 UpdateVolumeFromMouse(mouseX, mouseY); 236 InvalidateRect(hwnd, NULL, TRUE); 237 } 238 // Check if clicking on power button 239 else if (IsPointInCircle(mouseX, mouseY, 500, 120, 25)) { 240 g_radio.power = !g_radio.power; 241 if (g_radio.power) { 242 StartAudio(); 243 } else { 244 StopAudio(); 245 } 246 InvalidateRect(hwnd, NULL, TRUE); 247 } 248 return 0; 249 } 250 251 case WM_LBUTTONUP: { 252 if (g_radio.isDraggingDial || g_radio.isDraggingVolume) { 253 g_radio.isDraggingDial = 0; 254 g_radio.isDraggingVolume = 0; 255 ReleaseCapture(); 256 } 257 return 0; 258 } 259 260 case WM_MOUSEMOVE: { 261 if (g_radio.isDraggingDial) { 262 int mouseX = LOWORD(lParam); 263 int mouseY = HIWORD(lParam); 264 UpdateFrequencyFromMouse(mouseX, mouseY); 265 InvalidateRect(hwnd, NULL, TRUE); 266 } 267 else if (g_radio.isDraggingVolume) { 268 int mouseX = LOWORD(lParam); 269 int mouseY = HIWORD(lParam); 270 UpdateVolumeFromMouse(mouseX, mouseY); 271 InvalidateRect(hwnd, NULL, TRUE); 272 } 273 return 0; 274 } 275 276 case WM_KEYDOWN: { 277 switch (wParam) { 278 case VK_UP: { 279 // Increase frequency by 0.1 MHz (fine tuning) 280 g_radio.frequency += 0.1f; 281 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f; 282 283 // Update signal strength for new frequency 284 RadioStation* station = FindNearestStation(g_radio.frequency); 285 if (station) { 286 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f); 287 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) { 288 StopBassStreaming(); 289 StartBassStreaming(station); 290 } 291 } else { 292 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency)); 293 StopBassStreaming(); 294 } 295 296 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 297 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 298 299 UpdateStaticVolume(g_radio.signalStrength); 300 InvalidateRect(hwnd, NULL, TRUE); 301 break; 302 } 303 304 case VK_DOWN: { 305 // Decrease frequency by 0.1 MHz (fine tuning) 306 g_radio.frequency -= 0.1f; 307 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f; 308 309 // Update signal strength for new frequency 310 RadioStation* station = FindNearestStation(g_radio.frequency); 311 if (station) { 312 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f); 313 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) { 314 StopBassStreaming(); 315 StartBassStreaming(station); 316 } 317 } else { 318 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency)); 319 StopBassStreaming(); 320 } 321 322 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 323 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 324 325 UpdateStaticVolume(g_radio.signalStrength); 326 InvalidateRect(hwnd, NULL, TRUE); 327 break; 328 } 329 330 case VK_RIGHT: { 331 // Increase frequency by 1.0 MHz (coarse tuning) 332 g_radio.frequency += 1.0f; 333 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f; 334 335 // Update signal strength for new frequency 336 RadioStation* station = FindNearestStation(g_radio.frequency); 337 if (station) { 338 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f); 339 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) { 340 StopBassStreaming(); 341 StartBassStreaming(station); 342 } 343 } else { 344 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency)); 345 StopBassStreaming(); 346 } 347 348 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 349 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 350 351 UpdateStaticVolume(g_radio.signalStrength); 352 InvalidateRect(hwnd, NULL, TRUE); 353 break; 354 } 355 356 case VK_LEFT: { 357 // Decrease frequency by 1.0 MHz (coarse tuning) 358 g_radio.frequency -= 1.0f; 359 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f; 360 361 // Update signal strength for new frequency 362 RadioStation* station = FindNearestStation(g_radio.frequency); 363 if (station) { 364 g_radio.signalStrength = (int)(GetStationSignalStrength(station, g_radio.frequency) * 100.0f); 365 if (g_radio.signalStrength > 50 && station != g_audio.currentStation) { 366 StopBassStreaming(); 367 StartBassStreaming(station); 368 } 369 } else { 370 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency)); 371 StopBassStreaming(); 372 } 373 374 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 375 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 376 377 UpdateStaticVolume(g_radio.signalStrength); 378 InvalidateRect(hwnd, NULL, TRUE); 379 break; 380 } 381 } 382 return 0; 383 } 384 385 case WM_COMMAND: 386 switch (LOWORD(wParam)) { 387 case ID_ABOUT: { 388 const char* aboutText = "Shortwave Radio Tuner\n\n" 389 "Version: 1.0.0\n" 390 "Built for Rewind V2 Hackathon\n\n" 391 "A vintage shortwave radio simulator\n" 392 "compatible with Windows XP\n\n" 393 "Features:\n" 394 "- Realistic tuning interface\n" 395 "- Internet radio streaming\n" 396 "- Authentic static noise\n\n" 397 "Controls:\n" 398 "- Drag tuning dial to change frequency\n" 399 "- UP/DOWN arrows: Fine tuning (0.1 MHz)\n" 400 "- LEFT/RIGHT arrows: Coarse tuning (1.0 MHz)\n" 401 "- Click power button to turn on/off\n" 402 "- Drag volume knob to adjust volume"; 403 MessageBox(hwnd, aboutText, "About Shortwave Radio", 404 MB_OK | MB_ICONINFORMATION); 405 break; 406 } 407 case ID_EXIT: 408 PostQuitMessage(0); 409 break; 410 } 411 return 0; 412 413 case WM_TIMER: { 414 // Timer for VU meter updates - only invalidate VU meter area 415 if (g_radio.power) { 416 RECT vuRect = {440, 190, 540, 250}; 417 InvalidateRect(hwnd, &vuRect, FALSE); 418 } 419 return 0; 420 } 421 } 422 423 return DefWindowProc(hwnd, uMsg, wParam, lParam); 424} 425 426void DrawRadioInterface(HDC hdc, RECT* rect) { 427 // Winamp-style dark gradient background 428 HBRUSH darkBrush = CreateSolidBrush(RGB(24, 24, 24)); 429 FillRect(hdc, rect, darkBrush); 430 DeleteObject(darkBrush); 431 432 // Main panel with simplified metallic gradient effect 433 RECT panel = {10, 10, rect->right - 10, rect->bottom - 10}; 434 435 // Simplified gradient effect with fewer steps 436 for (int i = 0; i < 8; i++) { 437 int gray = 45 + i * 3; 438 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray)); 439 RECT gradRect = {panel.left + i, panel.top + i, panel.right - i, panel.bottom - i}; 440 FrameRect(hdc, &gradRect, gradBrush); 441 DeleteObject(gradBrush); 442 } 443 444 // Inner panel with darker metallic look 445 RECT innerPanel = {30, 30, rect->right - 30, rect->bottom - 30}; 446 HBRUSH innerBrush = CreateSolidBrush(RGB(32, 32, 32)); 447 FillRect(hdc, &innerPanel, innerBrush); 448 DeleteObject(innerBrush); 449 450 // Winamp-style beveled border 451 HPEN lightPen = CreatePen(PS_SOLID, 1, RGB(128, 128, 128)); 452 HPEN darkPen = CreatePen(PS_SOLID, 1, RGB(16, 16, 16)); 453 454 SelectObject(hdc, lightPen); 455 MoveToEx(hdc, innerPanel.left, innerPanel.bottom, NULL); 456 LineTo(hdc, innerPanel.left, innerPanel.top); 457 LineTo(hdc, innerPanel.right, innerPanel.top); 458 459 SelectObject(hdc, darkPen); 460 LineTo(hdc, innerPanel.right, innerPanel.bottom); 461 LineTo(hdc, innerPanel.left, innerPanel.bottom); 462 463 DeleteObject(lightPen); 464 DeleteObject(darkPen); 465 466 // Draw frequency display with Winamp-style LCD 467 DrawFrequencyDisplay(hdc, 200, 80, g_radio.frequency); 468 469 // Draw tuning dial with metallic look 470 DrawTuningDial(hdc, 150, 200, 60, g_radio.frequency); 471 472 // Draw volume knob with chrome effect 473 DrawVolumeKnob(hdc, 350, 200, 30, g_radio.volume); 474 475 // Draw signal meter with neon bars 476 DrawSignalMeter(hdc, 450, 170, g_radio.signalStrength); 477 478 // Draw VU meter with classic Winamp style 479 DrawVUMeter(hdc, 450, 200, g_audio.vuLevelLeft, g_audio.vuLevelRight); 480 481 // Draw power button with LED glow 482 DrawPowerButton(hdc, 500, 120, 25, g_radio.power); 483 484 // Draw station info with Winamp-style ticker 485 RadioStation* currentStation = FindNearestStation(g_radio.frequency); 486 if (currentStation && g_radio.signalStrength > 30) { 487 RECT stationRect = {50, 320, 550, 360}; 488 489 // Winamp-style display background 490 HBRUSH displayBrush = CreateSolidBrush(RGB(0, 0, 0)); 491 FillRect(hdc, &stationRect, displayBrush); 492 DeleteObject(displayBrush); 493 494 // Beveled border 495 HPEN lightBorderPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64)); 496 HPEN darkBorderPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); 497 498 SelectObject(hdc, darkBorderPen); 499 MoveToEx(hdc, stationRect.left, stationRect.bottom, NULL); 500 LineTo(hdc, stationRect.left, stationRect.top); 501 LineTo(hdc, stationRect.right, stationRect.top); 502 503 SelectObject(hdc, lightBorderPen); 504 LineTo(hdc, stationRect.right, stationRect.bottom); 505 LineTo(hdc, stationRect.left, stationRect.bottom); 506 507 DeleteObject(lightBorderPen); 508 DeleteObject(darkBorderPen); 509 510 // Winamp-style green text 511 SetTextColor(hdc, RGB(0, 255, 0)); 512 SetBkMode(hdc, TRANSPARENT); 513 HFONT stationFont = CreateFont(14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, 514 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 515 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 516 DEFAULT_PITCH | FF_MODERN, "Tahoma"); 517 SelectObject(hdc, stationFont); 518 519 char stationText[256]; 520 sprintf(stationText, "%.3f MHz - %s: %s", 521 currentStation->frequency, currentStation->name, currentStation->description); 522 523 SetTextAlign(hdc, TA_LEFT); 524 TextOut(hdc, stationRect.left + 10, stationRect.top + 12, stationText, strlen(stationText)); 525 526 DeleteObject(stationFont); 527 } 528} 529 530void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency) { 531 // Winamp-style LCD display with beveled edges 532 RECT display = {x - 100, y - 25, x + 100, y + 25}; 533 534 // Dark background 535 HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0)); 536 FillRect(hdc, &display, blackBrush); 537 DeleteObject(blackBrush); 538 539 // Beveled border effect 540 HPEN lightPen = CreatePen(PS_SOLID, 2, RGB(96, 96, 96)); 541 HPEN darkPen = CreatePen(PS_SOLID, 2, RGB(32, 32, 32)); 542 543 SelectObject(hdc, darkPen); 544 MoveToEx(hdc, display.left, display.bottom, NULL); 545 LineTo(hdc, display.left, display.top); 546 LineTo(hdc, display.right, display.top); 547 548 SelectObject(hdc, lightPen); 549 LineTo(hdc, display.right, display.bottom); 550 LineTo(hdc, display.left, display.bottom); 551 552 DeleteObject(lightPen); 553 DeleteObject(darkPen); 554 555 // Inner shadow 556 RECT innerDisplay = {display.left + 3, display.top + 3, display.right - 3, display.bottom - 3}; 557 HPEN shadowPen = CreatePen(PS_SOLID, 1, RGB(16, 16, 16)); 558 SelectObject(hdc, shadowPen); 559 Rectangle(hdc, innerDisplay.left, innerDisplay.top, innerDisplay.right, innerDisplay.bottom); 560 DeleteObject(shadowPen); 561 562 // Frequency text with glow effect 563 char freqText[32]; 564 sprintf(freqText, "%.3f MHz", frequency); 565 566 SetBkMode(hdc, TRANSPARENT); 567 568 // Create glow effect by drawing text multiple times with slight offsets 569 HFONT lcdFont = CreateFont(20, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, 570 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 571 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 572 FIXED_PITCH | FF_MODERN, "Consolas"); 573 SelectObject(hdc, lcdFont); 574 SetTextAlign(hdc, TA_CENTER); 575 576 // Glow effect (dark green) 577 SetTextColor(hdc, RGB(0, 128, 0)); 578 for (int dx = -1; dx <= 1; dx++) { 579 for (int dy = -1; dy <= 1; dy++) { 580 if (dx != 0 || dy != 0) { 581 TextOut(hdc, x + dx, y - 10 + dy, freqText, strlen(freqText)); 582 } 583 } 584 } 585 586 // Main text (bright green) 587 SetTextColor(hdc, RGB(0, 255, 0)); 588 TextOut(hdc, x, y - 10, freqText, strlen(freqText)); 589 590 DeleteObject(lcdFont); 591} 592 593void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency) { 594 // Simplified metallic dial with fewer gradient steps 595 for (int i = 0; i < 4; i++) { 596 int gray = 80 + i * 20; 597 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray)); 598 SelectObject(hdc, gradBrush); 599 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2); 600 DeleteObject(gradBrush); 601 } 602 603 // Inner dial surface 604 HBRUSH dialBrush = CreateSolidBrush(RGB(160, 160, 160)); 605 SelectObject(hdc, dialBrush); 606 Ellipse(hdc, x - radius + 8, y - radius + 8, x + radius - 8, y + radius - 8); 607 DeleteObject(dialBrush); 608 609 // Outer ring 610 HPEN ringPen = CreatePen(PS_SOLID, 2, RGB(48, 48, 48)); 611 SelectObject(hdc, ringPen); 612 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 613 DeleteObject(ringPen); 614 615 // Tick marks with better contrast 616 HPEN tickPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0)); 617 SelectObject(hdc, tickPen); 618 619 // Draw major tick marks and frequency labels 620 SetTextColor(hdc, RGB(0, 0, 0)); 621 SetBkMode(hdc, TRANSPARENT); 622 HFONT smallFont = CreateFont(9, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, 623 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 624 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 625 DEFAULT_PITCH | FF_SWISS, "Tahoma"); 626 SelectObject(hdc, smallFont); 627 SetTextAlign(hdc, TA_CENTER); 628 629 // 270 degree sweep from -135 to +135 degrees 630 for (int i = 0; i < 12; i++) { 631 float angle = -3.14159f * 0.75f + (float)i * (3.14159f * 1.5f) / 11.0f; 632 633 // Major tick marks 634 int tickStartX = x + (int)((radius - 12) * cos(angle)); 635 int tickStartY = y + (int)((radius - 12) * sin(angle)); 636 int tickEndX = x + (int)((radius - 4) * cos(angle)); 637 int tickEndY = y + (int)((radius - 4) * sin(angle)); 638 MoveToEx(hdc, tickStartX, tickStartY, NULL); 639 LineTo(hdc, tickEndX, tickEndY); 640 641 // Frequency labels 642 int markX = x + (int)((radius - 22) * cos(angle)); 643 int markY = y + (int)((radius - 22) * sin(angle)); 644 char mark[8]; 645 sprintf(mark, "%d", 10 + i * 2); 646 TextOut(hdc, markX, markY - 4, mark, strlen(mark)); 647 } 648 649 // Draw minor tick marks 650 for (int i = 0; i < 11; i++) { 651 float angle = -3.14159f * 0.75f + ((float)i + 0.5f) * (3.14159f * 1.5f) / 11.0f; 652 int tickStartX = x + (int)((radius - 8) * cos(angle)); 653 int tickStartY = y + (int)((radius - 8) * sin(angle)); 654 int tickEndX = x + (int)((radius - 4) * cos(angle)); 655 int tickEndY = y + (int)((radius - 4) * sin(angle)); 656 MoveToEx(hdc, tickStartX, tickStartY, NULL); 657 LineTo(hdc, tickEndX, tickEndY); 658 } 659 660 DeleteObject(tickPen); 661 662 // Simplified pointer 663 float normalizedFreq = (frequency - 10.0f) / 24.0f; 664 float angle = -3.14159f * 0.75f + normalizedFreq * (3.14159f * 1.5f); 665 int pointerX = x + (int)((radius - 15) * cos(angle)); 666 int pointerY = y + (int)((radius - 15) * sin(angle)); 667 668 // Main pointer 669 HPEN pointerPen = CreatePen(PS_SOLID, 3, RGB(255, 64, 64)); 670 SelectObject(hdc, pointerPen); 671 MoveToEx(hdc, x, y, NULL); 672 LineTo(hdc, pointerX, pointerY); 673 DeleteObject(pointerPen); 674 675 // Center dot 676 HBRUSH centerBrush = CreateSolidBrush(RGB(64, 64, 64)); 677 SelectObject(hdc, centerBrush); 678 Ellipse(hdc, x - 4, y - 4, x + 4, y + 4); 679 DeleteObject(centerBrush); 680 681 DeleteObject(smallFont); 682 683 // Label with Winamp style 684 SetTextColor(hdc, RGB(192, 192, 192)); 685 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, 686 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 687 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 688 DEFAULT_PITCH | FF_SWISS, "Tahoma"); 689 SelectObject(hdc, labelFont); 690 SetTextAlign(hdc, TA_CENTER); 691 TextOut(hdc, x, y + radius + 15, "TUNING", 6); 692 DeleteObject(labelFont); 693} 694 695void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume) { 696 // Simplified chrome gradient knob 697 for (int i = 0; i < 3; i++) { 698 int gray = 100 + i * 30; 699 HBRUSH gradBrush = CreateSolidBrush(RGB(gray, gray, gray)); 700 SelectObject(hdc, gradBrush); 701 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2); 702 DeleteObject(gradBrush); 703 } 704 705 // Inner knob surface 706 HBRUSH knobBrush = CreateSolidBrush(RGB(180, 180, 180)); 707 SelectObject(hdc, knobBrush); 708 Ellipse(hdc, x - radius + 6, y - radius + 6, x + radius - 6, y + radius - 6); 709 DeleteObject(knobBrush); 710 711 // Outer ring 712 HPEN ringPen = CreatePen(PS_SOLID, 1, RGB(64, 64, 64)); 713 SelectObject(hdc, ringPen); 714 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 715 DeleteObject(ringPen); 716 717 // Volume indicator 718 float angle = volume * 3.14159f * 1.5f - 3.14159f * 0.75f; 719 int indicatorX = x + (int)((radius - 8) * cos(angle)); 720 int indicatorY = y + (int)((radius - 8) * sin(angle)); 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 // Simplified chrome gradient button 922 for (int i = 0; i < 3; i++) { 923 int intensity = power ? (80 + i * 40) : (60 + i * 20); 924 COLORREF buttonColor = power ? RGB(255 - i * 40, intensity, intensity) : RGB(intensity, intensity, intensity); 925 HBRUSH buttonBrush = CreateSolidBrush(buttonColor); 926 SelectObject(hdc, buttonBrush); 927 Ellipse(hdc, x - radius + i*2, y - radius + i*2, x + radius - i*2, y + radius - i*2); 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 945 if (power) { 946 // Main symbol 947 HPEN symbolPen = CreatePen(PS_SOLID, 3, RGB(255, 255, 255)); 948 SelectObject(hdc, symbolPen); 949 Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6); 950 MoveToEx(hdc, x, y - 10, NULL); 951 LineTo(hdc, x, y - 2); 952 DeleteObject(symbolPen); 953 } else { 954 // Dim power symbol 955 HPEN symbolPen = CreatePen(PS_SOLID, 2, RGB(64, 64, 64)); 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 } 962 963 // Label 964 SetBkMode(hdc, TRANSPARENT); 965 SetTextColor(hdc, power ? RGB(255, 192, 192) : RGB(192, 192, 192)); 966 HFONT labelFont = CreateFont(12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, 967 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 968 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 969 DEFAULT_PITCH | FF_SWISS, "Tahoma"); 970 SelectObject(hdc, labelFont); 971 SetTextAlign(hdc, TA_CENTER); 972 TextOut(hdc, x, y - radius - 18, "POWER", 5); 973 DeleteObject(labelFont); 974} 975 976int IsPointInCircle(int px, int py, int cx, int cy, int radius) { 977 int dx = px - cx; 978 int dy = py - cy; 979 return (dx * dx + dy * dy) <= (radius * radius); 980} 981 982float GetAngleFromPoint(int px, int py, int cx, int cy) { 983 return atan2((float)(py - cy), (float)(px - cx)); 984} 985 986void UpdateFrequencyFromMouse(int mouseX, int mouseY) { 987 float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200); 988 989 // Convert angle to frequency (10-34 MHz range) 990 // Map 270 degree sweep from -135 to +135 degrees 991 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f); 992 993 // Clamp to valid range 994 if (normalizedAngle < 0.0f) normalizedAngle = 0.0f; 995 if (normalizedAngle > 1.0f) normalizedAngle = 1.0f; 996 997 // Map to frequency range 998 g_radio.frequency = 10.0f + normalizedAngle * 24.0f; 999 1000 // Clamp frequency 1001 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f; 1002 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f; 1003 1004 // Calculate signal strength based on nearest station 1005 RadioStation* nearestStation = FindNearestStation(g_radio.frequency); 1006 if (nearestStation) { 1007 g_radio.signalStrength = (int)(GetStationSignalStrength(nearestStation, g_radio.frequency) * 100.0f); 1008 1009 // Start streaming if signal is strong enough and station changed 1010 if (g_radio.signalStrength > 50 && nearestStation != g_audio.currentStation) { 1011 StopBassStreaming(); 1012 StartBassStreaming(nearestStation); 1013 } 1014 } else { 1015 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency)); 1016 StopBassStreaming(); 1017 } 1018 1019 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 1020 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 1021 1022 UpdateStaticVolume(g_radio.signalStrength); 1023} 1024 1025void UpdateVolumeFromMouse(int mouseX, int mouseY) { 1026 float angle = GetAngleFromPoint(mouseX, mouseY, 350, 200); 1027 1028 // Convert angle to volume (0-1 range) 1029 // Map from -135 degrees to +135 degrees 1030 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f); 1031 1032 g_radio.volume = normalizedAngle; 1033 1034 // Clamp volume 1035 if (g_radio.volume < 0.0f) g_radio.volume = 0.0f; 1036 if (g_radio.volume > 1.0f) g_radio.volume = 1.0f; 1037 1038 // Update static volume when main volume changes 1039 UpdateStaticVolume(g_radio.signalStrength); 1040} 1041 1042int InitializeAudio() { 1043 // Initialize BASS with more detailed error reporting 1044 printf("Initializing BASS audio system...\n"); 1045 1046 if (!BASS_Init(-1, 44100, 0, 0, NULL)) { 1047 DWORD error = BASS_ErrorGetCode(); 1048 printf("BASS initialization failed (Error: %lu)\n", error); 1049 1050 // Try alternative initialization methods 1051 printf("Trying alternative audio device...\n"); 1052 if (!BASS_Init(0, 44100, 0, 0, NULL)) { 1053 error = BASS_ErrorGetCode(); 1054 printf("Alternative BASS init also failed (Error: %lu)\n", error); 1055 printf("BASS Error meanings:\n"); 1056 printf(" 1 = BASS_ERROR_MEM (memory error)\n"); 1057 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL error)\n"); 1058 printf(" 3 = BASS_ERROR_DRIVER (no audio driver)\n"); 1059 printf(" 8 = BASS_ERROR_ALREADY (already initialized)\n"); 1060 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n"); 1061 return -1; 1062 } 1063 } 1064 1065 printf("BASS initialized successfully\n"); 1066 1067 // Get BASS version info 1068 DWORD version = BASS_GetVersion(); 1069 printf("BASS version: %d.%d.%d.%d\n", 1070 HIBYTE(HIWORD(version)), LOBYTE(HIWORD(version)), 1071 HIBYTE(LOWORD(version)), LOBYTE(LOWORD(version))); 1072 1073 g_audio.currentStream = 0; 1074 g_audio.staticStream = 0; 1075 g_audio.isPlaying = 0; 1076 g_audio.staticVolume = 0.8f; 1077 g_audio.radioVolume = 0.0f; 1078 g_audio.currentStation = NULL; 1079 g_audio.vuLevelLeft = 0.0f; 1080 g_audio.vuLevelRight = 0.0f; 1081 1082 return 0; 1083} 1084 1085void CleanupAudio() { 1086 StopBassStreaming(); 1087 1088 // Free BASS 1089 BASS_Free(); 1090 printf("BASS cleaned up\n"); 1091} 1092 1093void StartAudio() { 1094 if (!g_audio.isPlaying) { 1095 g_audio.isPlaying = 1; 1096 StartStaticNoise(); 1097 printf("Audio started with static\n"); 1098 } 1099} 1100 1101void StopAudio() { 1102 if (g_audio.isPlaying) { 1103 g_audio.isPlaying = 0; 1104 StopBassStreaming(); 1105 StopStaticNoise(); 1106 printf("Audio stopped\n"); 1107 } 1108} 1109 1110RadioStation* FindNearestStation(float frequency) { 1111 RadioStation* nearest = NULL; 1112 float minDistance = 999.0f; 1113 1114 for (int i = 0; i < NUM_STATIONS; i++) { 1115 float distance = fabs(g_stations[i].frequency - frequency); 1116 if (distance < minDistance) { 1117 minDistance = distance; 1118 nearest = &g_stations[i]; 1119 } 1120 } 1121 1122 // Only return station if we're close enough (within 0.5 MHz) 1123 if (minDistance <= 0.5f) { 1124 return nearest; 1125 } 1126 1127 return NULL; 1128} 1129 1130float GetStationSignalStrength(RadioStation* station, float currentFreq) { 1131 if (!station) return 0.0f; 1132 1133 float distance = fabs(station->frequency - currentFreq); 1134 1135 // Signal strength drops off with distance from exact frequency 1136 if (distance < 0.05f) { 1137 return 0.9f; // Very strong signal 1138 } else if (distance < 0.1f) { 1139 return 0.7f; // Strong signal 1140 } else if (distance < 0.2f) { 1141 return 0.5f; // Medium signal 1142 } else if (distance < 0.5f) { 1143 return 0.2f; // Weak signal 1144 } 1145 1146 return 0.0f; // No signal 1147} 1148 1149int StartBassStreaming(RadioStation* station) { 1150 if (!station) { 1151 printf("StartBassStreaming failed: no station\n"); 1152 return 0; 1153 } 1154 1155 StopBassStreaming(); 1156 1157 printf("Attempting to stream: %s at %s\n", station->name, station->streamUrl); 1158 1159 // Check if BASS is initialized 1160 if (!BASS_GetVersion()) { 1161 printf("BASS not initialized - cannot stream\n"); 1162 return 0; 1163 } 1164 1165 // Create BASS stream from URL with more options 1166 printf("Creating BASS stream...\n"); 1167 g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0, 1168 BASS_STREAM_BLOCK | BASS_STREAM_STATUS | BASS_STREAM_AUTOFREE, NULL, 0); 1169 1170 if (g_audio.currentStream) { 1171 printf("Successfully connected to stream: %s\n", station->name); 1172 1173 // Get stream info 1174 BASS_CHANNELINFO info; 1175 if (BASS_ChannelGetInfo(g_audio.currentStream, &info)) { 1176 printf("Stream info: %lu Hz, %lu channels, type=%lu\n", 1177 info.freq, info.chans, info.ctype); 1178 } 1179 1180 // Set volume based on signal strength and radio volume 1181 float volume = g_radio.volume * (g_radio.signalStrength / 100.0f); 1182 BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume); 1183 printf("Set volume to: %.2f\n", volume); 1184 1185 // Start playing 1186 if (BASS_ChannelPlay(g_audio.currentStream, FALSE)) { 1187 printf("Stream playback started\n"); 1188 } else { 1189 DWORD error = BASS_ErrorGetCode(); 1190 printf("Failed to start playback (BASS Error: %lu)\n", error); 1191 } 1192 1193 g_audio.currentStation = station; 1194 return 1; 1195 } else { 1196 DWORD error = BASS_ErrorGetCode(); 1197 printf("Failed to connect to stream: %s (BASS Error: %lu)\n", station->name, error); 1198 printf("BASS Error meanings:\n"); 1199 printf(" 1 = BASS_ERROR_MEM (out of memory)\n"); 1200 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL cannot be opened)\n"); 1201 printf(" 3 = BASS_ERROR_DRIVER (no audio driver available)\n"); 1202 printf(" 6 = BASS_ERROR_FORMAT (unsupported format)\n"); 1203 printf(" 7 = BASS_ERROR_POSITION (invalid position)\n"); 1204 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n"); 1205 printf(" 21 = BASS_ERROR_TIMEOUT (connection timeout)\n"); 1206 printf(" 41 = BASS_ERROR_SSL (SSL/HTTPS not supported)\n"); 1207 } 1208 1209 return 0; 1210} 1211 1212void StopBassStreaming() { 1213 if (g_audio.currentStream) { 1214 BASS_StreamFree(g_audio.currentStream); 1215 g_audio.currentStream = 0; 1216 printf("Stopped streaming\n"); 1217 } 1218 1219 g_audio.currentStation = NULL; 1220} 1221 1222// Static noise generation callback 1223DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user) { 1224 short* samples = (short*)buffer; 1225 DWORD sampleCount = length / sizeof(short); 1226 1227 // Get current time for oscillation 1228 static DWORD startTime = GetTickCount(); 1229 DWORD currentTime = GetTickCount(); 1230 float timeSeconds = (currentTime - startTime) / 1000.0f; 1231 1232 // Create subtle volume oscillations (5-7% variation) 1233 // Use multiple sine waves at different frequencies for natural variation 1234 float oscillation1 = sin(timeSeconds * 0.7f) * 0.03f; // 3% slow oscillation 1235 float oscillation2 = sin(timeSeconds * 2.3f) * 0.02f; // 2% medium oscillation 1236 float oscillation3 = sin(timeSeconds * 5.1f) * 0.015f; // 1.5% fast oscillation 1237 float volumeVariation = 1.0f + oscillation1 + oscillation2 + oscillation3; 1238 1239 // Generate white noise with volume variation 1240 for (DWORD i = 0; i < sampleCount; i++) { 1241 // Generate random value between -32767 and 32767 1242 short baseNoise = (short)((rand() % 65535) - 32767); 1243 1244 // Apply volume variation 1245 samples[i] = (short)(baseNoise * volumeVariation); 1246 } 1247 1248 return length; 1249} 1250 1251void StartStaticNoise() { 1252 if (!g_audio.staticStream) { 1253 // Create a stream for static noise generation 1254 g_audio.staticStream = BASS_StreamCreate(SAMPLE_RATE, CHANNELS, 0, StaticStreamProc, NULL); 1255 1256 if (g_audio.staticStream) { 1257 // Set initial volume based on signal strength 1258 UpdateStaticVolume(g_radio.signalStrength); 1259 1260 // Start playing static 1261 BASS_ChannelPlay(g_audio.staticStream, FALSE); 1262 printf("Static noise started\n"); 1263 } else { 1264 printf("Failed to create static stream\n"); 1265 } 1266 } 1267} 1268 1269void StopStaticNoise() { 1270 if (g_audio.staticStream) { 1271 BASS_StreamFree(g_audio.staticStream); 1272 g_audio.staticStream = 0; 1273 printf("Static noise stopped\n"); 1274 } 1275} 1276 1277void UpdateStaticVolume(float signalStrength) { 1278 if (g_audio.staticStream) { 1279 // Static volume is inverse of signal strength 1280 // Strong signal = less static, weak signal = more static 1281 float staticLevel = (100.0f - signalStrength) / 100.0f; 1282 float volume = g_radio.volume * staticLevel * g_audio.staticVolume; 1283 1284 // Ensure minimum static when radio is on but no strong signal 1285 if (g_radio.power && signalStrength < 50.0f) { 1286 volume = fmax(volume, g_radio.volume * 0.1f); 1287 } 1288 1289 BASS_ChannelSetAttribute(g_audio.staticStream, BASS_ATTRIB_VOL, volume); 1290 } 1291} 1292 1293void UpdateVULevels() { 1294 // Initialize levels to zero 1295 g_audio.vuLevelLeft = 0.0f; 1296 g_audio.vuLevelRight = 0.0f; 1297 1298 // Get levels from current stream if playing 1299 if (g_audio.currentStream && BASS_ChannelIsActive(g_audio.currentStream) == BASS_ACTIVE_PLAYING) { 1300 DWORD level = BASS_ChannelGetLevel(g_audio.currentStream); 1301 if (level != -1) { 1302 // Extract left and right channel levels 1303 g_audio.vuLevelLeft = (float)LOWORD(level) / 32768.0f; 1304 g_audio.vuLevelRight = (float)HIWORD(level) / 32768.0f; 1305 } 1306 } 1307 1308 // Add static contribution if static is playing 1309 if (g_audio.staticStream && BASS_ChannelIsActive(g_audio.staticStream) == BASS_ACTIVE_PLAYING) { 1310 DWORD staticLevel = BASS_ChannelGetLevel(g_audio.staticStream); 1311 if (staticLevel != -1) { 1312 float staticLeft = (float)LOWORD(staticLevel) / 32768.0f; 1313 float staticRight = (float)HIWORD(staticLevel) / 32768.0f; 1314 1315 // Combine with existing levels (simulate mixing) 1316 g_audio.vuLevelLeft = fmin(1.0f, g_audio.vuLevelLeft + staticLeft * 0.3f); 1317 g_audio.vuLevelRight = fmin(1.0f, g_audio.vuLevelRight + staticRight * 0.3f); 1318 } 1319 } 1320 1321 // Apply some smoothing/decay for more realistic VU behavior 1322 static float lastLeft = 0.0f, lastRight = 0.0f; 1323 g_audio.vuLevelLeft = g_audio.vuLevelLeft * 0.7f + lastLeft * 0.3f; 1324 g_audio.vuLevelRight = g_audio.vuLevelRight * 0.7f + lastRight * 0.3f; 1325 lastLeft = g_audio.vuLevelLeft; 1326 lastRight = g_audio.vuLevelRight; 1327}