a mega cool windows xp app

feat: add static and vu meter

dunkirk.sh 049705d2 f0b056e6

verified
.cache/clangd/index/main.cpp.47C66B394CD74271.idx

This is a binary file and will not be displayed.

.cache/clangd/index/qr.h.B46974D52593BF02.idx

This is a binary file and will not be displayed.

+1
.gitignore
··· 1 1 .crush 2 + .cache 2 3 result 3 4 .direnv 4 5 .clangd
+7
crush.json
··· 1 + { 2 + "lsp": { 3 + "C++": { 4 + "command": "clangd" 5 + } 6 + } 7 + }
+6 -58
flake.nix
··· 40 40 41 41 buildPhase = '' 42 42 export LDFLAGS="-static -static-libgcc -static-libstdc++" 43 - 43 + 44 44 # Check if BASS files exist in libs directory 45 45 if [ -f "libs/bass.h" ] && [ -f "libs/bass.lib" ]; then 46 46 echo "BASS files found in libs/ - building with audio integration" ··· 50 50 sed -i 's|#include "libs/bass.h"|// #include "libs/bass.h"|' main.cpp 51 51 sed -i 's|libs/bass.lib||' CMakeLists.txt 52 52 fi 53 - 53 + 54 54 cmake -DCMAKE_BUILD_TYPE=Release \ 55 55 -DCMAKE_SYSTEM_NAME=Windows \ 56 56 -DCMAKE_C_COMPILER=$CC \ ··· 66 66 }; 67 67 68 68 deploy-to-xp = pkgs.writeShellScriptBin "deploy-to-xp" '' 69 + echo "rebuilding program" 70 + nix build --rebuild 71 + 69 72 XP_DIR="$HOME/Documents/xp-drive" 70 73 mkdir -p "$XP_DIR" 71 74 72 75 echo "Deploying Shortwave Radio to $XP_DIR..." 73 76 74 77 # Copy executable with force overwrite 75 - if cp -f ${self'.packages.shortwave}/bin/Shortwave.exe "$XP_DIR/"; then 78 + if cp -f result/bin/Shortwave.exe "$XP_DIR/"; then 76 79 echo "✓ Copied Shortwave.exe" 77 80 else 78 81 echo "✗ Failed to copy Shortwave.exe" ··· 96 99 ls -la "$XP_DIR"/*.{exe,dll} 2>/dev/null || echo "No files found" 97 100 ''; 98 101 99 - setup-dev = pkgs.writeShellScriptBin "setup-dev" '' 100 - echo "Setting up development environment for Shortwave Radio..." 101 - 102 - # Get dynamic paths from nix packages 103 - GCC_BASE="${pkgs.pkgsCross.mingw32.buildPackages.gcc}/i686-w64-mingw32" 104 - SYS_INCLUDE="$GCC_BASE/sys-include" 105 - MINGW_MAIN_INCLUDE="${pkgs.pkgsCross.mingw32.windows.mingw_w64}/include" 106 - CPP_INCLUDE="${pkgs.lib.getDev pkgs.pkgsCross.mingw32.buildPackages.gcc}/include/c++/10.3.0" 107 - CPP_TARGET_INCLUDE="$CPP_INCLUDE/i686-w64-mingw32" 108 - 109 - # Create .clangd config with correct paths 110 - cat > .clangd << EOF 111 - CompileFlags: 112 - Add: 113 - - -target 114 - - i686-w64-mingw32 115 - - -DWINVER=0x0501 116 - - -D_WIN32_WINNT=0x0501 117 - - -DWIN32_LEAN_AND_MEAN 118 - - -D_WIN32 119 - - -DWIN32 120 - - -std=c++17 121 - - -fno-builtin 122 - - -isystem 123 - - $SYS_INCLUDE 124 - - -isystem 125 - - $MINGW_MAIN_INCLUDE 126 - - -isystem 127 - - $CPP_INCLUDE 128 - - -isystem 129 - - $CPP_TARGET_INCLUDE 130 - Remove: 131 - - -I*/gcc/*/include 132 - EOF 133 - 134 - # Create compile_commands.json 135 - cat > compile_commands.json << EOF 136 - [ 137 - { 138 - "directory": "$(pwd)", 139 - "command": "i686-w64-mingw32-g++ -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -DWIN32_LEAN_AND_MEAN -D_WIN32 -DWIN32 -std=c++17 -isystem \"$SYS_INCLUDE\" -isystem \"$MINGW_MAIN_INCLUDE\" -isystem \"$CPP_INCLUDE\" -isystem \"$CPP_TARGET_INCLUDE\" -c main.cpp", 140 - "file": "main.cpp" 141 - } 142 - ] 143 - EOF 144 - 145 - echo "Generated .clangd config and compile_commands.json with include paths" 146 - echo "Development environment ready for Shortwave Radio development" 147 - echo "Include paths:" 148 - echo " C standard library: $SYS_INCLUDE" 149 - echo " MinGW headers: $MINGW_MAIN_INCLUDE" 150 - echo " C++ headers: $CPP_INCLUDE" 151 - ''; 152 - 153 102 default = self'.packages.shortwave; 154 103 }; 155 104 ··· 159 108 cmake 160 109 pkgsCross.mingw32.buildPackages.gcc 161 110 self'.packages.deploy-to-xp 162 - self'.packages.setup-dev 163 111 ]; 164 112 165 113 shellHook = ''
+291 -24
main.cpp
··· 30 30 {15.770f, "Radio Paradise", "Eclectic music mix", "http://stream.radioparadise.com/mp3-128"}, 31 31 {17.895f, "Jazz Radio", "Smooth jazz", "http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3"}, 32 32 {21.500f, "Classical Music", "Classical radio", "http://stream.wqxr.org/wqxr"}, 33 - {31.100f, "Chillout Lounge", "Relaxing music", "http://air.radiorecord.ru:805/chil_320"} 34 33 }; 35 34 36 35 #define NUM_STATIONS (sizeof(g_stations) / sizeof(RadioStation)) ··· 48 47 int isPlaying; 49 48 float staticVolume; 50 49 float radioVolume; 51 - 50 + 52 51 // Station tracking 53 52 RadioStation* currentStation; 53 + 54 + // VU meter levels (0.0 to 1.0) 55 + float vuLevelLeft; 56 + float vuLevelRight; 54 57 } AudioState; 55 58 56 59 // Radio state ··· 71 74 void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency); 72 75 void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency); 73 76 void DrawSignalMeter(HDC hdc, int x, int y, int strength); 77 + void DrawVUMeter(HDC hdc, int x, int y, float leftLevel, float rightLevel); 74 78 void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume); 75 79 void DrawPowerButton(HDC hdc, int x, int y, int radius, int power); 76 80 int IsPointInCircle(int px, int py, int cx, int cy, int radius); ··· 90 94 int StartBassStreaming(RadioStation* station); 91 95 void StopBassStreaming(); 92 96 97 + // Static noise functions 98 + DWORD CALLBACK StaticStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user); 99 + void StartStaticNoise(); 100 + void StopStaticNoise(); 101 + void UpdateStaticVolume(float signalStrength); 102 + 103 + // VU meter functions 104 + void UpdateVULevels(); 105 + 93 106 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { 94 107 // Allocate console for debugging 95 108 AllocConsole(); ··· 146 159 147 160 ShowWindow(hwnd, nCmdShow); 148 161 UpdateWindow(hwnd); 162 + 163 + // Set timer for VU meter updates (30 FPS) 164 + SetTimer(hwnd, 1, 33, NULL); 149 165 150 166 MSG msg = {}; 151 167 while (GetMessage(&msg, NULL, 0, 0)) { ··· 177 193 RECT rect; 178 194 GetClientRect(hwnd, &rect); 179 195 196 + // Update VU levels before drawing 197 + if (g_radio.power) { 198 + UpdateVULevels(); 199 + } 200 + 180 201 DrawRadioInterface(hdc, &rect); 181 202 182 203 EndPaint(hwnd, &ps); ··· 262 283 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 263 284 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 264 285 286 + UpdateStaticVolume(g_radio.signalStrength); 265 287 InvalidateRect(hwnd, NULL, TRUE); 266 288 break; 267 289 } ··· 287 309 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 288 310 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 289 311 312 + UpdateStaticVolume(g_radio.signalStrength); 290 313 InvalidateRect(hwnd, NULL, TRUE); 291 314 break; 292 315 } ··· 312 335 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 313 336 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 314 337 338 + UpdateStaticVolume(g_radio.signalStrength); 315 339 InvalidateRect(hwnd, NULL, TRUE); 316 340 break; 317 341 } ··· 337 361 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 338 362 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 339 363 364 + UpdateStaticVolume(g_radio.signalStrength); 340 365 InvalidateRect(hwnd, NULL, TRUE); 341 366 break; 342 367 } ··· 371 396 break; 372 397 } 373 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 + } 374 407 } 375 408 376 409 return DefWindowProc(hwnd, uMsg, wParam, lParam); ··· 415 448 416 449 // Draw signal meter 417 450 DrawSignalMeter(hdc, 450, 150, g_radio.signalStrength); 451 + 452 + // Draw VU meter 453 + DrawVUMeter(hdc, 450, 180, g_audio.vuLevelLeft, g_audio.vuLevelRight); 418 454 419 455 // Draw power button 420 456 DrawPowerButton(hdc, 500, 120, 25, g_radio.power); ··· 510 546 Ellipse(hdc, x - radius, y - radius, x + radius, y + radius); 511 547 DeleteObject(borderPen); 512 548 513 - // Draw frequency markings 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 514 554 SetTextColor(hdc, RGB(0, 0, 0)); 515 555 HFONT smallFont = CreateFont(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, 516 556 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, ··· 519 559 SelectObject(hdc, smallFont); 520 560 SetTextAlign(hdc, TA_CENTER); 521 561 562 + // 270 degree sweep from -135 to +135 degrees 522 563 for (int i = 0; i < 12; i++) { 523 - float angle = (float)i * 3.14159f / 6.0f; 524 - int markX = x + (int)((radius - 15) * cos(angle)); 525 - int markY = y + (int)((radius - 15) * sin(angle)); 564 + float angle = -3.14159f * 0.75f + (float)i * (3.14159f * 1.5f) / 11.0f; 526 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)); 527 577 char mark[8]; 528 578 sprintf(mark, "%d", 10 + i * 2); 529 579 TextOut(hdc, markX, markY - 5, mark, strlen(mark)); 530 580 } 531 581 532 - // Draw pointer based on frequency 533 - float angle = (frequency - 10.0f) / 24.0f * 3.14159f; 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); 534 621 int pointerX = x + (int)((radius - 10) * cos(angle)); 535 622 int pointerY = y + (int)((radius - 10) * sin(angle)); 536 623 ··· 599 686 } 600 687 } 601 688 689 + void 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 + 602 750 void DrawPowerButton(HDC hdc, int x, int y, int radius, int power) { 603 751 // Draw button background 604 752 COLORREF buttonColor = power ? RGB(255, 0, 0) : RGB(100, 100, 100); ··· 641 789 float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200); 642 790 643 791 // Convert angle to frequency (10-34 MHz range) 644 - // Normalize angle from -PI to PI to 0-1 range 645 - float normalizedAngle = (angle + 3.14159f) / (2.0f * 3.14159f); 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; 646 798 647 799 // Map to frequency range 648 800 g_radio.frequency = 10.0f + normalizedAngle * 24.0f; ··· 668 820 669 821 if (g_radio.signalStrength < 0) g_radio.signalStrength = 0; 670 822 if (g_radio.signalStrength > 100) g_radio.signalStrength = 100; 823 + 824 + UpdateStaticVolume(g_radio.signalStrength); 671 825 } 672 826 673 827 void UpdateVolumeFromMouse(int mouseX, int mouseY) { ··· 682 836 // Clamp volume 683 837 if (g_radio.volume < 0.0f) g_radio.volume = 0.0f; 684 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); 685 842 } 686 843 687 844 int InitializeAudio() { 688 845 // Initialize BASS with more detailed error reporting 689 846 printf("Initializing BASS audio system...\n"); 690 - 847 + 691 848 if (!BASS_Init(-1, 44100, 0, 0, NULL)) { 692 849 DWORD error = BASS_ErrorGetCode(); 693 850 printf("BASS initialization failed (Error: %lu)\n", error); 694 - 851 + 695 852 // Try alternative initialization methods 696 853 printf("Trying alternative audio device...\n"); 697 854 if (!BASS_Init(0, 44100, 0, 0, NULL)) { ··· 706 863 return -1; 707 864 } 708 865 } 709 - 866 + 710 867 printf("BASS initialized successfully\n"); 711 - 868 + 712 869 // Get BASS version info 713 870 DWORD version = BASS_GetVersion(); 714 - printf("BASS version: %d.%d.%d.%d\n", 871 + printf("BASS version: %d.%d.%d.%d\n", 715 872 HIBYTE(HIWORD(version)), LOBYTE(HIWORD(version)), 716 873 HIBYTE(LOWORD(version)), LOBYTE(LOWORD(version))); 717 - 874 + 718 875 g_audio.currentStream = 0; 719 876 g_audio.staticStream = 0; 720 877 g_audio.isPlaying = 0; 721 878 g_audio.staticVolume = 0.8f; 722 879 g_audio.radioVolume = 0.0f; 723 880 g_audio.currentStation = NULL; 881 + g_audio.vuLevelLeft = 0.0f; 882 + g_audio.vuLevelRight = 0.0f; 724 883 725 884 return 0; 726 885 } 727 886 728 887 void CleanupAudio() { 729 888 StopBassStreaming(); 730 - 889 + 731 890 // Free BASS 732 891 BASS_Free(); 733 892 printf("BASS cleaned up\n"); ··· 736 895 void StartAudio() { 737 896 if (!g_audio.isPlaying) { 738 897 g_audio.isPlaying = 1; 739 - printf("Audio started\n"); 898 + StartStaticNoise(); 899 + printf("Audio started with static\n"); 740 900 } 741 901 } 742 902 ··· 744 904 if (g_audio.isPlaying) { 745 905 g_audio.isPlaying = 0; 746 906 StopBassStreaming(); 907 + StopStaticNoise(); 747 908 printf("Audio stopped\n"); 748 909 } 749 910 } ··· 805 966 806 967 // Create BASS stream from URL with more options 807 968 printf("Creating BASS stream...\n"); 808 - g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0, 969 + g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0, 809 970 BASS_STREAM_BLOCK | BASS_STREAM_STATUS | BASS_STREAM_AUTOFREE, NULL, 0); 810 971 811 972 if (g_audio.currentStream) { 812 973 printf("Successfully connected to stream: %s\n", station->name); 813 - 974 + 814 975 // Get stream info 815 976 BASS_CHANNELINFO info; 816 977 if (BASS_ChannelGetInfo(g_audio.currentStream, &info)) { 817 - printf("Stream info: %lu Hz, %lu channels, type=%lu\n", 978 + printf("Stream info: %lu Hz, %lu channels, type=%lu\n", 818 979 info.freq, info.chans, info.ctype); 819 980 } 820 - 981 + 821 982 // Set volume based on signal strength and radio volume 822 983 float volume = g_radio.volume * (g_radio.signalStrength / 100.0f); 823 984 BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume); 824 985 printf("Set volume to: %.2f\n", volume); 825 - 986 + 826 987 // Start playing 827 988 if (BASS_ChannelPlay(g_audio.currentStream, FALSE)) { 828 989 printf("Stream playback started\n"); ··· 830 991 DWORD error = BASS_ErrorGetCode(); 831 992 printf("Failed to start playback (BASS Error: %lu)\n", error); 832 993 } 833 - 994 + 834 995 g_audio.currentStation = station; 835 996 return 1; 836 997 } else { ··· 860 1021 g_audio.currentStation = NULL; 861 1022 } 862 1023 1024 + // Static noise generation callback 1025 + DWORD 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 + 1053 + void 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 + 1071 + void 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 + 1079 + void 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 + 1095 + void 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 + }