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