···
{15.770f, "Radio Paradise", "Eclectic music mix", "http://stream.radioparadise.com/mp3-128"},
{17.895f, "Jazz Radio", "Smooth jazz", "http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3"},
{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"}
#define NUM_STATIONS (sizeof(g_stations) / sizeof(RadioStation))
···
RadioStation* currentStation;
54
+
// VU meter levels (0.0 to 1.0)
···
void DrawTuningDial(HDC hdc, int x, int y, int radius, float frequency);
void DrawFrequencyDisplay(HDC hdc, int x, int y, float frequency);
void DrawSignalMeter(HDC hdc, int x, int y, int strength);
77
+
void DrawVUMeter(HDC hdc, int x, int y, float leftLevel, float rightLevel);
void DrawVolumeKnob(HDC hdc, int x, int y, int radius, float volume);
void DrawPowerButton(HDC hdc, int x, int y, int radius, int power);
int IsPointInCircle(int px, int py, int cx, int cy, int radius);
···
int StartBassStreaming(RadioStation* station);
void StopBassStreaming();
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);
103
+
// VU meter functions
104
+
void UpdateVULevels();
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
// Allocate console for debugging
···
ShowWindow(hwnd, nCmdShow);
163
+
// Set timer for VU meter updates (30 FPS)
164
+
SetTimer(hwnd, 1, 33, NULL);
while (GetMessage(&msg, NULL, 0, 0)) {
···
GetClientRect(hwnd, &rect);
196
+
// Update VU levels before drawing
197
+
if (g_radio.power) {
DrawRadioInterface(hdc, &rect);
···
if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
286
+
UpdateStaticVolume(g_radio.signalStrength);
InvalidateRect(hwnd, NULL, TRUE);
···
if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
312
+
UpdateStaticVolume(g_radio.signalStrength);
InvalidateRect(hwnd, NULL, TRUE);
···
if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
338
+
UpdateStaticVolume(g_radio.signalStrength);
InvalidateRect(hwnd, NULL, TRUE);
···
if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
364
+
UpdateStaticVolume(g_radio.signalStrength);
InvalidateRect(hwnd, NULL, TRUE);
···
401
+
// Timer for VU meter updates
402
+
if (g_radio.power) {
403
+
InvalidateRect(hwnd, NULL, FALSE);
return DefWindowProc(hwnd, uMsg, wParam, lParam);
···
DrawSignalMeter(hdc, 450, 150, g_radio.signalStrength);
453
+
DrawVUMeter(hdc, 450, 180, g_audio.vuLevelLeft, g_audio.vuLevelRight);
DrawPowerButton(hdc, 500, 120, 25, g_radio.power);
···
Ellipse(hdc, x - radius, y - radius, x + radius, y + radius);
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);
553
+
// Draw major tick marks and frequency labels
SetTextColor(hdc, RGB(0, 0, 0));
HFONT smallFont = CreateFont(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
···
SelectObject(hdc, smallFont);
SetTextAlign(hdc, TA_CENTER);
562
+
// 270 degree sweep from -135 to +135 degrees
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;
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);
574
+
// Frequency labels
575
+
int markX = x + (int)((radius - 18) * cos(angle));
576
+
int markY = y + (int)((radius - 18) * sin(angle));
sprintf(mark, "%d", 10 + i * 2);
TextOut(hdc, markX, markY - 5, mark, strlen(mark));
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);
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);
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);
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);
615
+
DeleteObject(tickPen);
616
+
DeleteObject(limitPen);
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);
int pointerX = x + (int)((radius - 10) * cos(angle));
int pointerY = y + (int)((radius - 10) * sin(angle));
···
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);
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);
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);
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);
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);
734
+
// Draw channel labels
735
+
TextOut(hdc, x + 77, y + 10, "L", 1);
736
+
TextOut(hdc, x + 77, y + 20, "R", 1);
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);
746
+
DeleteObject(scalePen);
747
+
DeleteObject(smallFont);
void DrawPowerButton(HDC hdc, int x, int y, int radius, int power) {
// Draw button background
COLORREF buttonColor = power ? RGB(255, 0, 0) : RGB(100, 100, 100);
···
float angle = GetAngleFromPoint(mouseX, mouseY, 150, 200);
// 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);
795
+
// Clamp to valid range
796
+
if (normalizedAngle < 0.0f) normalizedAngle = 0.0f;
797
+
if (normalizedAngle > 1.0f) normalizedAngle = 1.0f;
// Map to frequency range
g_radio.frequency = 10.0f + normalizedAngle * 24.0f;
···
if (g_radio.signalStrength < 0) g_radio.signalStrength = 0;
if (g_radio.signalStrength > 100) g_radio.signalStrength = 100;
824
+
UpdateStaticVolume(g_radio.signalStrength);
void UpdateVolumeFromMouse(int mouseX, int mouseY) {
···
if (g_radio.volume < 0.0f) g_radio.volume = 0.0f;
if (g_radio.volume > 1.0f) g_radio.volume = 1.0f;
840
+
// Update static volume when main volume changes
841
+
UpdateStaticVolume(g_radio.signalStrength);
// Initialize BASS with more detailed error reporting
printf("Initializing BASS audio system...\n");
if (!BASS_Init(-1, 44100, 0, 0, NULL)) {
DWORD error = BASS_ErrorGetCode();
printf("BASS initialization failed (Error: %lu)\n", error);
// Try alternative initialization methods
printf("Trying alternative audio device...\n");
if (!BASS_Init(0, 44100, 0, 0, NULL)) {
···
printf("BASS initialized successfully\n");
DWORD version = BASS_GetVersion();
714
-
printf("BASS version: %d.%d.%d.%d\n",
871
+
printf("BASS version: %d.%d.%d.%d\n",
HIBYTE(HIWORD(version)), LOBYTE(HIWORD(version)),
HIBYTE(LOWORD(version)), LOBYTE(LOWORD(version)));
g_audio.currentStream = 0;
g_audio.staticStream = 0;
g_audio.staticVolume = 0.8f;
g_audio.radioVolume = 0.0f;
g_audio.currentStation = NULL;
881
+
g_audio.vuLevelLeft = 0.0f;
882
+
g_audio.vuLevelRight = 0.0f;
printf("BASS cleaned up\n");
···
if (!g_audio.isPlaying) {
739
-
printf("Audio started\n");
898
+
StartStaticNoise();
899
+
printf("Audio started with static\n");
···
printf("Audio stopped\n");
···
// Create BASS stream from URL with more options
printf("Creating BASS stream...\n");
808
-
g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0,
969
+
g_audio.currentStream = BASS_StreamCreateURL(station->streamUrl, 0,
BASS_STREAM_BLOCK | BASS_STREAM_STATUS | BASS_STREAM_AUTOFREE, NULL, 0);
if (g_audio.currentStream) {
printf("Successfully connected to stream: %s\n", station->name);
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",
info.freq, info.chans, info.ctype);
// Set volume based on signal strength and radio volume
float volume = g_radio.volume * (g_radio.signalStrength / 100.0f);
BASS_ChannelSetAttribute(g_audio.currentStream, BASS_ATTRIB_VOL, volume);
printf("Set volume to: %.2f\n", volume);
if (BASS_ChannelPlay(g_audio.currentStream, FALSE)) {
printf("Stream playback started\n");
···
DWORD error = BASS_ErrorGetCode();
printf("Failed to start playback (BASS Error: %lu)\n", error);
g_audio.currentStation = station;
···
g_audio.currentStation = NULL;
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);
1029
+
// Get current time for oscillation
1030
+
static DWORD startTime = GetTickCount();
1031
+
DWORD currentTime = GetTickCount();
1032
+
float timeSeconds = (currentTime - startTime) / 1000.0f;
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;
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);
1046
+
// Apply volume variation
1047
+
samples[i] = (short)(baseNoise * volumeVariation);
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);
1058
+
if (g_audio.staticStream) {
1059
+
// Set initial volume based on signal strength
1060
+
UpdateStaticVolume(g_radio.signalStrength);
1062
+
// Start playing static
1063
+
BASS_ChannelPlay(g_audio.staticStream, FALSE);
1064
+
printf("Static noise started\n");
1066
+
printf("Failed to create static stream\n");
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");
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;
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);
1091
+
BASS_ChannelSetAttribute(g_audio.staticStream, BASS_ATTRIB_VOL, volume);
1095
+
void UpdateVULevels() {
1096
+
// Initialize levels to zero
1097
+
g_audio.vuLevelLeft = 0.0f;
1098
+
g_audio.vuLevelRight = 0.0f;
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;
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;
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);
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;