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(101, 67, 33)); // Wood grain brown 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 // Create vintage radio background 414 HBRUSH woodBrush = CreateSolidBrush(RGB(101, 67, 33)); 415 FillRect(hdc, rect, woodBrush); 416 DeleteObject(woodBrush); 417 418 // Draw radio panel (darker inset) 419 RECT panel = {50, 50, rect->right - 50, rect->bottom - 50}; 420 HBRUSH panelBrush = CreateSolidBrush(RGB(80, 50, 25)); 421 FillRect(hdc, &panel, panelBrush); 422 DeleteObject(panelBrush); 423 424 // Draw panel border (raised effect) 425 HPEN lightPen = CreatePen(PS_SOLID, 2, RGB(140, 100, 60)); 426 HPEN darkPen = CreatePen(PS_SOLID, 2, RGB(40, 25, 15)); 427 428 SelectObject(hdc, lightPen); 429 MoveToEx(hdc, panel.left, panel.bottom, NULL); 430 LineTo(hdc, panel.left, panel.top); 431 LineTo(hdc, panel.right, panel.top); 432 433 SelectObject(hdc, darkPen); 434 LineTo(hdc, panel.right, panel.bottom); 435 LineTo(hdc, panel.left, panel.bottom); 436 437 DeleteObject(lightPen); 438 DeleteObject(darkPen); 439 440 // Draw frequency display 441 DrawFrequencyDisplay(hdc, 200, 80, g_radio.frequency); 442 443 // Draw tuning dial 444 DrawTuningDial(hdc, 150, 200, 60, g_radio.frequency); 445 446 // Draw volume knob 447 DrawVolumeKnob(hdc, 350, 200, 30, g_radio.volume); 448 449 // Draw signal meter 450 DrawSignalMeter(hdc, 450, 150, g_radio.signalStrength); 451 452 // Draw VU meter 453 DrawVUMeter(hdc, 450, 180, g_audio.vuLevelLeft, g_audio.vuLevelRight); 454 455 // Draw power button 456 DrawPowerButton(hdc, 500, 120, 25, g_radio.power); 457 458 // Draw station info if tuned to a station 459 RadioStation* currentStation = FindNearestStation(g_radio.frequency); 460 if (currentStation && g_radio.signalStrength > 30) { 461 RECT stationRect = {80, 320, 520, 360}; 462 HBRUSH stationBrush = CreateSolidBrush(RGB(0, 0, 0)); 463 FillRect(hdc, &stationRect, stationBrush); 464 DeleteObject(stationBrush); 465 466 HPEN stationPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100)); 467 SelectObject(hdc, stationPen); 468 Rectangle(hdc, stationRect.left, stationRect.top, stationRect.right, stationRect.bottom); 469 DeleteObject(stationPen); 470 471 SetTextColor(hdc, RGB(0, 255, 0)); 472 HFONT stationFont = CreateFont(12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, 473 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 474 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 475 DEFAULT_PITCH | FF_SWISS, "Arial"); 476 SelectObject(hdc, stationFont); 477 478 char stationText[256]; 479 sprintf(stationText, "%.3f MHz - %s: %s", 480 currentStation->frequency, currentStation->name, currentStation->description); 481 482 SetTextAlign(hdc, TA_LEFT); 483 TextOut(hdc, stationRect.left + 5, stationRect.top + 5, stationText, strlen(stationText)); 484 485 DeleteObject(stationFont); 486 } 487 488 // Draw labels 489 SetBkMode(hdc, TRANSPARENT); 490 SetTextColor(hdc, RGB(255, 255, 255)); 491 HFONT font = CreateFont(14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, 492 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 493 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 494 DEFAULT_PITCH | FF_SWISS, "Arial"); 495 SelectObject(hdc, font); 496 497 TextOut(hdc, 180, 300, "TUNING", 6); 498 TextOut(hdc, 330, 260, "VOLUME", 6); 499 TextOut(hdc, 430, 200, "SIGNAL", 6); 500 TextOut(hdc, 485, 160, "POWER", 5); 501 502 DeleteObject(font); 503} 504 505void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency) { 506 // Draw display background (black LCD style) 507 RECT display = {x - 80, y - 20, x + 80, y + 20}; 508 HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0)); 509 FillRect(hdc, &display, blackBrush); 510 DeleteObject(blackBrush); 511 512 // Draw display border 513 HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100)); 514 SelectObject(hdc, borderPen); 515 Rectangle(hdc, display.left, display.top, display.right, display.bottom); 516 DeleteObject(borderPen); 517 518 // Draw frequency text 519 char freqText[32]; 520 sprintf(freqText, "%.3f MHz", frequency); 521 522 SetBkMode(hdc, TRANSPARENT); 523 SetTextColor(hdc, RGB(0, 255, 0)); // Green LCD color 524 HFONT lcdFont = CreateFont(16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, 525 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 526 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 527 FIXED_PITCH | FF_MODERN, "Courier New"); 528 SelectObject(hdc, lcdFont); 529 530 SetTextAlign(hdc, TA_CENTER); 531 TextOut(hdc, x, y - 8, freqText, strlen(freqText)); 532 533 DeleteObject(lcdFont); 534} 535 536void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency) { 537 // Draw dial background 538 HBRUSH dialBrush = CreateSolidBrush(RGB(160, 120, 80)); 539 SelectObject(hdc, dialBrush); 540 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 541 DeleteObject(dialBrush); 542 543 // Draw dial border 544 HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(60, 40, 20)); 545 SelectObject(hdc, borderPen); 546 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 547 DeleteObject(borderPen); 548 549 // Draw tick marks and frequency markings (270 degree sweep) 550 HPEN tickPen = CreatePen(PS_SOLID, 1, RGB(60, 40, 20)); 551 SelectObject(hdc, tickPen); 552 553 // Draw major tick marks and frequency labels 554 SetTextColor(hdc, RGB(0, 0, 0)); 555 HFONT smallFont = CreateFont(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, 556 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 557 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 558 DEFAULT_PITCH | FF_SWISS, "Arial"); 559 SelectObject(hdc, smallFont); 560 SetTextAlign(hdc, TA_CENTER); 561 562 // 270 degree sweep from -135 to +135 degrees 563 for (int i = 0; i < 12; i++) { 564 float angle = -3.14159f * 0.75f + (float)i * (3.14159f * 1.5f) / 11.0f; 565 566 // Major tick marks 567 int tickStartX = x + (int)((radius - 8) * cos(angle)); 568 int tickStartY = y + (int)((radius - 8) * sin(angle)); 569 int tickEndX = x + (int)((radius - 2) * cos(angle)); 570 int tickEndY = y + (int)((radius - 2) * sin(angle)); 571 MoveToEx(hdc, tickStartX, tickStartY, NULL); 572 LineTo(hdc, tickEndX, tickEndY); 573 574 // Frequency labels 575 int markX = x + (int)((radius - 18) * cos(angle)); 576 int markY = y + (int)((radius - 18) * sin(angle)); 577 char mark[8]; 578 sprintf(mark, "%d", 10 + i * 2); 579 TextOut(hdc, markX, markY - 5, mark, strlen(mark)); 580 } 581 582 // Draw minor tick marks between major ones 583 for (int i = 0; i < 11; i++) { 584 float angle = -3.14159f * 0.75f + ((float)i + 0.5f) * (3.14159f * 1.5f) / 11.0f; 585 int tickStartX = x + (int)((radius - 5) * cos(angle)); 586 int tickStartY = y + (int)((radius - 5) * sin(angle)); 587 int tickEndX = x + (int)((radius - 2) * cos(angle)); 588 int tickEndY = y + (int)((radius - 2) * sin(angle)); 589 MoveToEx(hdc, tickStartX, tickStartY, NULL); 590 LineTo(hdc, tickEndX, tickEndY); 591 } 592 593 // Draw range limit markers at start and end positions 594 HPEN limitPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0)); 595 SelectObject(hdc, limitPen); 596 597 // Start position (-135 degrees, 10 MHz) 598 float startAngle = -3.14159f * 0.75f; 599 int startX = x + (int)((radius - 12) * cos(startAngle)); 600 int startY = y + (int)((radius - 12) * sin(startAngle)); 601 int startEndX = x + (int)(radius * cos(startAngle)); 602 int startEndY = y + (int)(radius * sin(startAngle)); 603 MoveToEx(hdc, startX, startY, NULL); 604 LineTo(hdc, startEndX, startEndY); 605 606 // End position (+135 degrees, 34 MHz) 607 float endAngle = 3.14159f * 0.75f; 608 int endX = x + (int)((radius - 12) * cos(endAngle)); 609 int endY = y + (int)((radius - 12) * sin(endAngle)); 610 int endEndX = x + (int)(radius * cos(endAngle)); 611 int endEndY = y + (int)(radius * sin(endAngle)); 612 MoveToEx(hdc, endX, endY, NULL); 613 LineTo(hdc, endEndX, endEndY); 614 615 DeleteObject(tickPen); 616 DeleteObject(limitPen); 617 618 // Draw pointer based on frequency (270 degree sweep) 619 float normalizedFreq = (frequency - 10.0f) / 24.0f; 620 float angle = -3.14159f * 0.75f + normalizedFreq * (3.14159f * 1.5f); 621 int pointerX = x + (int)((radius - 10) * cos(angle)); 622 int pointerY = y + (int)((radius - 10) * sin(angle)); 623 624 HPEN pointerPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0)); 625 SelectObject(hdc, pointerPen); 626 MoveToEx(hdc, x, y, NULL); 627 LineTo(hdc, pointerX, pointerY); 628 DeleteObject(pointerPen); 629 630 DeleteObject(smallFont); 631} 632 633void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume) { 634 // Draw knob background 635 HBRUSH knobBrush = CreateSolidBrush(RGB(140, 100, 60)); 636 SelectObject(hdc, knobBrush); 637 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 638 DeleteObject(knobBrush); 639 640 // Draw knob border 641 HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(60, 40, 20)); 642 SelectObject(hdc, borderPen); 643 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 644 DeleteObject(borderPen); 645 646 // Draw volume indicator 647 float angle = volume * 3.14159f * 1.5f - 3.14159f * 0.75f; 648 int indicatorX = x + (int)((radius - 5) * cos(angle)); 649 int indicatorY = y + (int)((radius - 5) * sin(angle)); 650 651 HPEN indicatorPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255)); 652 SelectObject(hdc, indicatorPen); 653 MoveToEx(hdc, x, y, NULL); 654 LineTo(hdc, indicatorX, indicatorY); 655 DeleteObject(indicatorPen); 656} 657 658void DrawSignalMeter(HDC hdc, int x, int y, int strength) { 659 // Draw meter background 660 RECT meter = {x, y, x + 80, y + 20}; 661 HBRUSH meterBrush = CreateSolidBrush(RGB(0, 0, 0)); 662 FillRect(hdc, &meter, meterBrush); 663 DeleteObject(meterBrush); 664 665 // Draw meter border 666 HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100)); 667 SelectObject(hdc, borderPen); 668 Rectangle(hdc, meter.left, meter.top, meter.right, meter.bottom); 669 DeleteObject(borderPen); 670 671 // Draw signal bars 672 int barWidth = 8; 673 int numBars = strength / 10; 674 for (int i = 0; i < numBars && i < 10; i++) { 675 RECT bar = {x + 2 + i * barWidth, y + 2, 676 x + 2 + (i + 1) * barWidth - 1, y + 18}; 677 678 COLORREF barColor; 679 if (i < 3) barColor = RGB(0, 255, 0); // Green 680 else if (i < 7) barColor = RGB(255, 255, 0); // Yellow 681 else barColor = RGB(255, 0, 0); // Red 682 683 HBRUSH barBrush = CreateSolidBrush(barColor); 684 FillRect(hdc, &bar, barBrush); 685 DeleteObject(barBrush); 686 } 687} 688 689void DrawVUMeter(HDC hdc, int x, int y, float leftLevel, float rightLevel) { 690 // Draw VU meter background 691 RECT meterBg = {x, y, x + 80, y + 40}; 692 HBRUSH bgBrush = CreateSolidBrush(RGB(20, 20, 20)); 693 FillRect(hdc, &meterBg, bgBrush); 694 DeleteObject(bgBrush); 695 696 // Draw border 697 HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 100)); 698 SelectObject(hdc, borderPen); 699 Rectangle(hdc, meterBg.left, meterBg.top, meterBg.right, meterBg.bottom); 700 DeleteObject(borderPen); 701 702 // Draw "VU" label 703 SetTextColor(hdc, RGB(200, 200, 200)); 704 SetBkMode(hdc, TRANSPARENT); 705 HFONT smallFont = CreateFont(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, 706 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 707 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, 708 DEFAULT_PITCH | FF_SWISS, "Arial"); 709 SelectObject(hdc, smallFont); 710 TextOut(hdc, x + 2, y + 2, "VU", 2); 711 712 // Draw left channel meter 713 int leftWidth = (int)(leftLevel * 70); 714 if (leftWidth > 0) { 715 RECT leftBar = {x + 5, y + 12, x + 5 + leftWidth, y + 18}; 716 COLORREF leftColor = leftLevel > 0.8f ? RGB(255, 0, 0) : 717 leftLevel > 0.6f ? RGB(255, 255, 0) : RGB(0, 255, 0); 718 HBRUSH leftBrush = CreateSolidBrush(leftColor); 719 FillRect(hdc, &leftBar, leftBrush); 720 DeleteObject(leftBrush); 721 } 722 723 // Draw right channel meter 724 int rightWidth = (int)(rightLevel * 70); 725 if (rightWidth > 0) { 726 RECT rightBar = {x + 5, y + 22, x + 5 + rightWidth, y + 28}; 727 COLORREF rightColor = rightLevel > 0.8f ? RGB(255, 0, 0) : 728 rightLevel > 0.6f ? RGB(255, 255, 0) : RGB(0, 255, 0); 729 HBRUSH rightBrush = CreateSolidBrush(rightColor); 730 FillRect(hdc, &rightBar, rightBrush); 731 DeleteObject(rightBrush); 732 } 733 734 // Draw channel labels 735 TextOut(hdc, x + 77, y + 10, "L", 1); 736 TextOut(hdc, x + 77, y + 20, "R", 1); 737 738 // Draw scale marks 739 HPEN scalePen = CreatePen(PS_SOLID, 1, RGB(80, 80, 80)); 740 SelectObject(hdc, scalePen); 741 for (int i = 1; i < 10; i++) { 742 int markX = x + 5 + (i * 7); 743 MoveToEx(hdc, markX, y + 30, NULL); 744 LineTo(hdc, markX, y + 32); 745 } 746 DeleteObject(scalePen); 747 DeleteObject(smallFont); 748} 749 750void DrawPowerButton(HDC hdc, int x, int y, int radius, int power) { 751 // Draw button background 752 COLORREF buttonColor = power ? RGB(255, 0, 0) : RGB(100, 100, 100); 753 HBRUSH buttonBrush = CreateSolidBrush(buttonColor); 754 SelectObject(hdc, buttonBrush); 755 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 756 DeleteObject(buttonBrush); 757 758 // Draw button border 759 HPEN borderPen = CreatePen(PS_SOLID, 2, RGB(60, 60, 60)); 760 SelectObject(hdc, borderPen); 761 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 762 DeleteObject(borderPen); 763 764 // Draw power symbol 765 if (power) { 766 HPEN symbolPen = CreatePen(PS_SOLID, 3, RGB(255, 255, 255)); 767 SelectObject(hdc, symbolPen); 768 769 // Draw power symbol (circle with line) 770 Arc(hdc, x - 8, y - 8, x + 8, y + 8, x + 6, y - 6, x - 6, y - 6); 771 MoveToEx(hdc, x, y - 10, NULL); 772 LineTo(hdc, x, y - 2); 773 774 DeleteObject(symbolPen); 775 } 776} 777 778int IsPointInCircle(int px, int py, int cx, int cy, int radius) { 779 int dx = px - cx; 780 int dy = py - cy; 781 return (dx * dx + dy * dy) <= (radius * radius); 782} 783 784float GetAngleFromPoint(int px, int py, int cx, int cy) { 785 return atan2((float)(py - cy), (float)(px - cx)); 786} 787 788void UpdateFrequencyFromMouse(int mouseX, int mouseY) { 789 float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200); 790 791 // Convert angle to frequency (10-34 MHz range) 792 // Map 270 degree sweep from -135 to +135 degrees 793 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f); 794 795 // Clamp to valid range 796 if (normalizedAngle < 0.0f) normalizedAngle = 0.0f; 797 if (normalizedAngle > 1.0f) normalizedAngle = 1.0f; 798 799 // Map to frequency range 800 g_radio.frequency = 10.0f + normalizedAngle * 24.0f; 801 802 // Clamp frequency 803 if (g_radio.frequency < 10.0f) g_radio.frequency = 10.0f; 804 if (g_radio.frequency > 34.0f) g_radio.frequency = 34.0f; 805 806 // Calculate signal strength based on nearest station 807 RadioStation* nearestStation = FindNearestStation(g_radio.frequency); 808 if (nearestStation) { 809 g_radio.signalStrength = (int)(GetStationSignalStrength(nearestStation, g_radio.frequency) * 100.0f); 810 811 // Start streaming if signal is strong enough and station changed 812 if (g_radio.signalStrength > 50 && nearestStation != g_audio.currentStation) { 813 StopBassStreaming(); 814 StartBassStreaming(nearestStation); 815 } 816 } else { 817 g_radio.signalStrength = 5 + (int)(15.0f * sin(g_radio.frequency)); 818 StopBassStreaming(); 819 } 820 821 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 822 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 823 824 UpdateStaticVolume(g_radio.signalStrength); 825} 826 827void UpdateVolumeFromMouse(int mouseX, int mouseY) { 828 float angle = GetAngleFromPoint(mouseX, mouseY, 350, 200); 829 830 // Convert angle to volume (0-1 range) 831 // Map from -135 degrees to +135 degrees 832 float normalizedAngle = (angle + 3.14159f * 0.75f) / (3.14159f * 1.5f); 833 834 g_radio.volume = normalizedAngle; 835 836 // Clamp volume 837 if (g_radio.volume < 0.0f) g_radio.volume = 0.0f; 838 if (g_radio.volume > 1.0f) g_radio.volume = 1.0f; 839 840 // Update static volume when main volume changes 841 UpdateStaticVolume(g_radio.signalStrength); 842} 843 844int InitializeAudio() { 845 // Initialize BASS with more detailed error reporting 846 printf("Initializing BASS audio system...\n"); 847 848 if (!BASS_Init(-1, 44100, 0, 0, NULL)) { 849 DWORD error = BASS_ErrorGetCode(); 850 printf("BASS initialization failed (Error: %lu)\n", error); 851 852 // Try alternative initialization methods 853 printf("Trying alternative audio device...\n"); 854 if (!BASS_Init(0, 44100, 0, 0, NULL)) { 855 error = BASS_ErrorGetCode(); 856 printf("Alternative BASS init also failed (Error: %lu)\n", error); 857 printf("BASS Error meanings:\n"); 858 printf(" 1 = BASS_ERROR_MEM (memory error)\n"); 859 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL error)\n"); 860 printf(" 3 = BASS_ERROR_DRIVER (no audio driver)\n"); 861 printf(" 8 = BASS_ERROR_ALREADY (already initialized)\n"); 862 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n"); 863 return -1; 864 } 865 } 866 867 printf("BASS initialized successfully\n"); 868 869 // Get BASS version info 870 DWORD version = BASS_GetVersion(); 871 printf("BASS version: %d.%d.%d.%d\n", 872 HIBYTE(HIWORD(version)), LOBYTE(HIWORD(version)), 873 HIBYTE(LOWORD(version)), LOBYTE(LOWORD(version))); 874 875 g_audio.currentStream = 0; 876 g_audio.staticStream = 0; 877 g_audio.isPlaying = 0; 878 g_audio.staticVolume = 0.8f; 879 g_audio.radioVolume = 0.0f; 880 g_audio.currentStation = NULL; 881 g_audio.vuLevelLeft = 0.0f; 882 g_audio.vuLevelRight = 0.0f; 883 884 return 0; 885} 886 887void CleanupAudio() { 888 StopBassStreaming(); 889 890 // Free BASS 891 BASS_Free(); 892 printf("BASS cleaned up\n"); 893} 894 895void StartAudio() { 896 if (!g_audio.isPlaying) { 897 g_audio.isPlaying = 1; 898 StartStaticNoise(); 899 printf("Audio started with static\n"); 900 } 901} 902 903void StopAudio() { 904 if (g_audio.isPlaying) { 905 g_audio.isPlaying = 0; 906 StopBassStreaming(); 907 StopStaticNoise(); 908 printf("Audio stopped\n"); 909 } 910} 911 912RadioStation* FindNearestStation(float frequency) { 913 RadioStation* nearest = NULL; 914 float minDistance = 999.0f; 915 916 for (int i = 0; i < NUM_STATIONS; i++) { 917 float distance = fabs(g_stations[i].frequency - frequency); 918 if (distance < minDistance) { 919 minDistance = distance; 920 nearest = &g_stations[i]; 921 } 922 } 923 924 // Only return station if we're close enough (within 0.5 MHz) 925 if (minDistance <= 0.5f) { 926 return nearest; 927 } 928 929 return NULL; 930} 931 932float GetStationSignalStrength(RadioStation* station, float currentFreq) { 933 if (!station) return 0.0f; 934 935 float distance = fabs(station->frequency - currentFreq); 936 937 // Signal strength drops off with distance from exact frequency 938 if (distance < 0.05f) { 939 return 0.9f; // Very strong signal 940 } else if (distance < 0.1f) { 941 return 0.7f; // Strong signal 942 } else if (distance < 0.2f) { 943 return 0.5f; // Medium signal 944 } else if (distance < 0.5f) { 945 return 0.2f; // Weak signal 946 } 947 948 return 0.0f; // No signal 949} 950 951int StartBassStreaming(RadioStation* station) { 952 if (!station) { 953 printf("StartBassStreaming failed: no station\n"); 954 return 0; 955 } 956 957 StopBassStreaming(); 958 959 printf("Attempting to stream: %s at %s\n", station->name, station->streamUrl); 960 961 // Check if BASS is initialized 962 if (!BASS_GetVersion()) { 963 printf("BASS not initialized - cannot stream\n"); 964 return 0; 965 } 966 967 // Create BASS stream from URL with more options 968 printf("Creating BASS stream...\n"); 969 g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0, 970 BASS_STREAM_BLOCK | BASS_STREAM_STATUS | BASS_STREAM_AUTOFREE, NULL, 0); 971 972 if (g_audio.currentStream) { 973 printf("Successfully connected to stream: %s\n", station->name); 974 975 // Get stream info 976 BASS_CHANNELINFO info; 977 if (BASS_ChannelGetInfo(g_audio.currentStream, &info)) { 978 printf("Stream info: %lu Hz, %lu channels, type=%lu\n", 979 info.freq, info.chans, info.ctype); 980 } 981 982 // Set volume based on signal strength and radio volume 983 float volume = g_radio.volume * (g_radio.signalStrength / 100.0f); 984 BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume); 985 printf("Set volume to: %.2f\n", volume); 986 987 // Start playing 988 if (BASS_ChannelPlay(g_audio.currentStream, FALSE)) { 989 printf("Stream playback started\n"); 990 } else { 991 DWORD error = BASS_ErrorGetCode(); 992 printf("Failed to start playback (BASS Error: %lu)\n", error); 993 } 994 995 g_audio.currentStation = station; 996 return 1; 997 } else { 998 DWORD error = BASS_ErrorGetCode(); 999 printf("Failed to connect to stream: %s (BASS Error: %lu)\n", station->name, error); 1000 printf("BASS Error meanings:\n"); 1001 printf(" 1 = BASS_ERROR_MEM (out of memory)\n"); 1002 printf(" 2 = BASS_ERROR_FILEOPEN (file/URL cannot be opened)\n"); 1003 printf(" 3 = BASS_ERROR_DRIVER (no audio driver available)\n"); 1004 printf(" 6 = BASS_ERROR_FORMAT (unsupported format)\n"); 1005 printf(" 7 = BASS_ERROR_POSITION (invalid position)\n"); 1006 printf(" 14 = BASS_ERROR_DEVICE (invalid device)\n"); 1007 printf(" 21 = BASS_ERROR_TIMEOUT (connection timeout)\n"); 1008 printf(" 41 = BASS_ERROR_SSL (SSL/HTTPS not supported)\n"); 1009 } 1010 1011 return 0; 1012} 1013 1014void StopBassStreaming() { 1015 if (g_audio.currentStream) { 1016 BASS_StreamFree(g_audio.currentStream); 1017 g_audio.currentStream = 0; 1018 printf("Stopped streaming\n"); 1019 } 1020 1021 g_audio.currentStation = NULL; 1022} 1023 1024// Static noise generation callback 1025DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user) { 1026 short* samples = (short*)buffer; 1027 DWORD sampleCount = length / sizeof(short); 1028 1029 // Get current time for oscillation 1030 static DWORD startTime = GetTickCount(); 1031 DWORD currentTime = GetTickCount(); 1032 float timeSeconds = (currentTime - startTime) / 1000.0f; 1033 1034 // Create subtle volume oscillations (5-7% variation) 1035 // Use multiple sine waves at different frequencies for natural variation 1036 float oscillation1 = sin(timeSeconds * 0.7f) * 0.03f; // 3% slow oscillation 1037 float oscillation2 = sin(timeSeconds * 2.3f) * 0.02f; // 2% medium oscillation 1038 float oscillation3 = sin(timeSeconds * 5.1f) * 0.015f; // 1.5% fast oscillation 1039 float volumeVariation = 1.0f + oscillation1 + oscillation2 + oscillation3; 1040 1041 // Generate white noise with volume variation 1042 for (DWORD i = 0; i < sampleCount; i++) { 1043 // Generate random value between -32767 and 32767 1044 short baseNoise = (short)((rand() % 65535) - 32767); 1045 1046 // Apply volume variation 1047 samples[i] = (short)(baseNoise * volumeVariation); 1048 } 1049 1050 return length; 1051} 1052 1053void StartStaticNoise() { 1054 if (!g_audio.staticStream) { 1055 // Create a stream for static noise generation 1056 g_audio.staticStream = BASS_StreamCreate(SAMPLE_RATE, CHANNELS, 0, StaticStreamProc, NULL); 1057 1058 if (g_audio.staticStream) { 1059 // Set initial volume based on signal strength 1060 UpdateStaticVolume(g_radio.signalStrength); 1061 1062 // Start playing static 1063 BASS_ChannelPlay(g_audio.staticStream, FALSE); 1064 printf("Static noise started\n"); 1065 } else { 1066 printf("Failed to create static stream\n"); 1067 } 1068 } 1069} 1070 1071void StopStaticNoise() { 1072 if (g_audio.staticStream) { 1073 BASS_StreamFree(g_audio.staticStream); 1074 g_audio.staticStream = 0; 1075 printf("Static noise stopped\n"); 1076 } 1077} 1078 1079void UpdateStaticVolume(float signalStrength) { 1080 if (g_audio.staticStream) { 1081 // Static volume is inverse of signal strength 1082 // Strong signal = less static, weak signal = more static 1083 float staticLevel = (100.0f - signalStrength) / 100.0f; 1084 float volume = g_radio.volume * staticLevel * g_audio.staticVolume; 1085 1086 // Ensure minimum static when radio is on but no strong signal 1087 if (g_radio.power && signalStrength < 50.0f) { 1088 volume = fmax(volume, g_radio.volume * 0.1f); 1089 } 1090 1091 BASS_ChannelSetAttribute(g_audio.staticStream, BASS_ATTRIB_VOL, volume); 1092 } 1093} 1094 1095void UpdateVULevels() { 1096 // Initialize levels to zero 1097 g_audio.vuLevelLeft = 0.0f; 1098 g_audio.vuLevelRight = 0.0f; 1099 1100 // Get levels from current stream if playing 1101 if (g_audio.currentStream && BASS_ChannelIsActive(g_audio.currentStream) == BASS_ACTIVE_PLAYING) { 1102 DWORD level = BASS_ChannelGetLevel(g_audio.currentStream); 1103 if (level != -1) { 1104 // Extract left and right channel levels 1105 g_audio.vuLevelLeft = (float)LOWORD(level) / 32768.0f; 1106 g_audio.vuLevelRight = (float)HIWORD(level) / 32768.0f; 1107 } 1108 } 1109 1110 // Add static contribution if static is playing 1111 if (g_audio.staticStream && BASS_ChannelIsActive(g_audio.staticStream) == BASS_ACTIVE_PLAYING) { 1112 DWORD staticLevel = BASS_ChannelGetLevel(g_audio.staticStream); 1113 if (staticLevel != -1) { 1114 float staticLeft = (float)LOWORD(staticLevel) / 32768.0f; 1115 float staticRight = (float)HIWORD(staticLevel) / 32768.0f; 1116 1117 // Combine with existing levels (simulate mixing) 1118 g_audio.vuLevelLeft = fmin(1.0f, g_audio.vuLevelLeft + staticLeft * 0.3f); 1119 g_audio.vuLevelRight = fmin(1.0f, g_audio.vuLevelRight + staticRight * 0.3f); 1120 } 1121 } 1122 1123 // Apply some smoothing/decay for more realistic VU behavior 1124 static float lastLeft = 0.0f, lastRight = 0.0f; 1125 g_audio.vuLevelLeft = g_audio.vuLevelLeft * 0.7f + lastLeft * 0.3f; 1126 g_audio.vuLevelRight = g_audio.vuLevelRight * 0.7f + lastRight * 0.3f; 1127 lastLeft = g_audio.vuLevelLeft; 1128 lastRight = g_audio.vuLevelRight; 1129}