Skip to content

Commit b5a3995

Browse files
Merge pull request #7 from YimingZhanshen/copilot/optimize-volume-control-and-bug-fixes
Add conversational awareness volume control and fix volume state management
2 parents db0f958 + 87a2583 commit b5a3995

18 files changed

+165
-33
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ cmake_minimum_required(VERSION 3.20)
2020

2121
project(
2222
AirPodsWindows
23-
VERSION 1.1.0 # Don't forget to bump the version in `vcpkg.json` as well
23+
VERSION 1.2.0 # Don't forget to bump the version in `vcpkg.json` as well
2424
LANGUAGES C CXX
2525
DESCRIPTION "AirPods desktop user experience enhancement program"
2626
HOMEPAGE_URL "https://github.com/YimingZhanshen/AirPodsWindows"

README-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
- **操作系统**: Windows 10/11
4848
- **蓝牙**: 需要蓝牙 4.0+ 适配器
4949
- **AirPods**: 支持所有 AirPods 型号
50-
- **ANC 功能**: 需要安装 [MagicAAP 驱动](https://github.com/kavishdevar/librepods)(仅 AirPods Pro/Max/AirPods 4 ANC)
50+
- **ANC 功能**: 需要安装 [MagicAAP 驱动](https://magicpods.app/magicaap/)(仅 AirPods Pro/Max/AirPods 4 ANC)
5151

5252
## 📦 安装
5353

README-TW.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
- **作業系統**:Windows 10/11
4848
- **藍牙**:需要藍牙 4.0+ 介面卡
4949
- **AirPods**:支援所有 AirPods 型號
50-
- **ANC 功能**:需要 [MagicAAP 驅動程式](https://github.com/kavishdevar/librepods)(僅 AirPods Pro/Max/AirPods 4 ANC)
50+
- **ANC 功能**:需要 [MagicAAP 驅動程式](https://magicpods.app/magicaap/)(僅 AirPods Pro/Max/AirPods 4 ANC)
5151

5252
## 📦 安裝
5353

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
- **OS**: Windows 10/11
4848
- **Bluetooth**: Requires Bluetooth 4.0+ adapter
4949
- **AirPods**: All AirPods models supported
50-
- **ANC Features**: Requires [MagicAAP driver](https://github.com/kavishdevar/librepods) (AirPods Pro/Max/AirPods 4 ANC only)
50+
- **ANC Features**: Requires [MagicAAP driver](https://magicpods.app/magicaap/) (AirPods Pro/Max/AirPods 4 ANC only)
5151

5252
## 📦 Installation
5353

Source/Core/AirPods.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,14 @@ void Manager::OnConversationalAwarenessChanged(bool enable)
865865
}
866866
}
867867

868+
void Manager::OnConversationalAwarenessVolumePercentChanged(uint8_t percent)
869+
{
870+
std::lock_guard<std::mutex> lock{_mutex};
871+
// Clamp the value to valid range (10-100) matching UI slider constraints
872+
_conversationalAwarenessVolumePercent = std::clamp(percent, uint8_t{10}, uint8_t{100});
873+
LOG(Info, "Conversational awareness volume percent changed to {}%", _conversationalAwarenessVolumePercent);
874+
}
875+
868876
void Manager::OnPersonalizedVolumeChanged(bool enable)
869877
{
870878
std::lock_guard<std::mutex> lock{_mutex};
@@ -910,6 +918,9 @@ void Manager::OnNoiseControlModeNotification(AAP::NoiseControlMode mode)
910918
{
911919
LOG(Info, "Noise control mode changed to: {}", Helper::ToString(mode).toStdString());
912920

921+
// Track the current noise control mode
922+
_currentNoiseControlMode = mode;
923+
913924
// Update the cached state in the state manager if we have a current state
914925
auto state = _stateMgr.GetCurrentState();
915926
if (state.has_value()) {
@@ -968,20 +979,29 @@ void Manager::OnHeadTrackingData(AAP::HeadTrackingData data)
968979
}
969980

970981
// Volume levels for conversational awareness
971-
constexpr int kConversationalAwarenessVolumePercent = 40; // Volume when user is speaking
972-
constexpr int kFullVolumePercent = 100; // Normal volume when not speaking
982+
// kFullVolumePercent (100) signals to GlobalMedia::SetVolume to restore the saved pre-speaking volume
983+
// The actual restoration logic is in GlobalMedia_win.cpp which restores to the saved volume, not literally 100%
984+
constexpr int kFullVolumePercent = 100; // Signal value to restore to original volume
973985

974986
void Manager::OnSpeakingLevelChanged(AAP::SpeakingLevel level)
975987
{
976988
if (!_conversationalAwarenessEnabled) {
977989
return;
978990
}
979991

992+
// Disable conversational awareness in transparency mode to avoid volume restoration bugs
993+
// The AAP firmware in transparency mode doesn't reliably send restoration events
994+
if (_currentNoiseControlMode.has_value() &&
995+
_currentNoiseControlMode.value() == AAP::NoiseControlMode::Transparency) {
996+
LOG(Info, "Conversational awareness disabled in transparency mode to avoid volume bugs");
997+
return;
998+
}
999+
9801000
switch (level) {
9811001
case AAP::SpeakingLevel::StartedSpeaking_GreatlyReduce:
9821002
case AAP::SpeakingLevel::StartedSpeaking_GreatlyReduce2:
983-
LOG(Info, "User started speaking - reducing media volume to {}%", kConversationalAwarenessVolumePercent);
984-
Core::GlobalMedia::SetVolume(kConversationalAwarenessVolumePercent);
1003+
LOG(Info, "User started speaking - reducing media volume to {}%", _conversationalAwarenessVolumePercent);
1004+
Core::GlobalMedia::SetVolume(_conversationalAwarenessVolumePercent);
9851005
break;
9861006

9871007
case AAP::SpeakingLevel::StoppedSpeaking:
@@ -992,7 +1012,12 @@ void Manager::OnSpeakingLevelChanged(AAP::SpeakingLevel level)
9921012
break;
9931013

9941014
default:
995-
// Intermediate levels - could implement gradual volume adjustment
1015+
// Intermediate levels (0x04-0x07) - restore volume to be safe
1016+
// This ensures volume is restored even if final event is missed
1017+
if (static_cast<uint8_t>(level) >= 0x04 && static_cast<uint8_t>(level) <= 0x07) {
1018+
LOG(Info, "Intermediate speaking level detected - restoring media volume");
1019+
Core::GlobalMedia::SetVolume(kFullVolumePercent);
1020+
}
9961021
break;
9971022
}
9981023
}

Source/Core/AirPods.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ class Manager
166166
void OnAutomaticEarDetectionChanged(bool enable);
167167
void OnBoundDeviceAddressChanged(uint64_t address);
168168
void OnConversationalAwarenessChanged(bool enable);
169+
void OnConversationalAwarenessVolumePercentChanged(uint8_t percent);
169170
void OnPersonalizedVolumeChanged(bool enable);
170171
void OnLoudSoundReductionChanged(bool enable);
171172
void OnAdaptiveTransparencyLevelChanged(uint8_t level);
@@ -203,9 +204,11 @@ class Manager
203204
bool _deviceConnected{false};
204205
bool _automaticEarDetection{false};
205206
bool _conversationalAwarenessEnabled{false};
207+
uint8_t _conversationalAwarenessVolumePercent{40};
206208
bool _personalizedVolumeEnabled{false};
207209
bool _loudSoundReductionEnabled{false};
208210
uint8_t _adaptiveTransparencyLevel{25};
211+
std::optional<AAP::NoiseControlMode> _currentNoiseControlMode;
209212

210213
// AAP Manager for L2CAP protocol communication
211214
AAP::Manager _aapMgr;

Source/Core/GlobalMedia.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,10 @@ inline int GetVolume()
4343
{
4444
return Controller::GetInstance().GetVolume();
4545
}
46+
47+
inline void ClearVolumeReductionState()
48+
{
49+
Controller::GetInstance().ClearVolumeReductionState();
50+
}
51+
4652
} // namespace Core::GlobalMedia

Source/Core/GlobalMedia_win.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -561,9 +561,10 @@ void Controller::SetVolume(int percent)
561561
bool isReducing = percent < 100;
562562
bool hasVolumeReduction = _savedVolume > 0;
563563

564-
// Save current volume before reducing (if this is the first reduction)
565-
if (isReducing && !hasVolumeReduction) {
566-
// Get actual current volume
564+
// Save current volume before reducing
565+
// ALWAYS re-read the actual volume to ensure we capture user's manual changes
566+
if (isReducing && !_inReductionSession) {
567+
// Get actual current volume from the system
567568
int currentVolume = 100;
568569
try {
569570
OS::Windows::Com::UniquePtr<IMMDeviceEnumerator> deviceEnumerator;
@@ -588,18 +589,23 @@ void Controller::SetVolume(int percent)
588589
}
589590
} catch (...) {}
590591
_savedVolume = currentVolume;
592+
_inReductionSession = true;
593+
LOG(Trace, "SetVolume: Saved current volume {}% before reduction, starting session", _savedVolume);
591594
}
592595

593596
// Calculate target volume
594597
int targetPercent;
595598
if (percent == 100 && hasVolumeReduction) {
596599
// Restoring - use saved volume
600+
// Keep _savedVolume to handle duplicate restoration calls
601+
// It will be cleared when the next reduction cycle starts
597602
targetPercent = _savedVolume;
598-
// Clear saved volume after restoring
599-
_savedVolume = 0;
603+
_inReductionSession = false;
604+
LOG(Trace, "SetVolume: Restoring to saved volume {}%, ending session", targetPercent);
600605
} else if (isReducing && _savedVolume > 0) {
601606
// Reducing - calculate relative to saved volume
602607
targetPercent = (percent * _savedVolume) / 100;
608+
LOG(Trace, "SetVolume: Reducing to {}% ({}% of saved {}%)", targetPercent, percent, _savedVolume);
603609
} else {
604610
// No reduction state - just set the percent
605611
targetPercent = percent;
@@ -687,4 +693,13 @@ int Controller::GetVolume() const
687693
return 100;
688694
}
689695
}
696+
697+
void Controller::ClearVolumeReductionState()
698+
{
699+
std::lock_guard<std::mutex> lock{_mutex};
700+
_savedVolume = 0;
701+
_inReductionSession = false;
702+
LOG(Trace, "ClearVolumeReductionState: Cleared saved volume state");
703+
}
704+
690705
} // namespace Core::GlobalMedia

Source/Core/GlobalMedia_win.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ class Controller final : public Helper::Singleton<Controller>, public Details::C
7878
void Pause() override;
7979
void SetVolume(int percent);
8080
int GetVolume() const;
81+
void ClearVolumeReductionState(); // Explicitly clear saved volume state
8182

8283
private:
8384
std::mutex _mutex;
8485
std::vector<std::unique_ptr<Details::MediaProgramAbstract>> _pausedPrograms;
8586
int _savedVolume{0}; // Saved volume before reduction; 0 means no active reduction
87+
bool _inReductionSession{false}; // Track if we're currently in a volume reduction session
8688
};
8789
} // namespace Core::GlobalMedia

Source/Core/Settings.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ void OnApply_conversational_awareness(const Fields &newFields)
9292
newFields.conversational_awareness);
9393
}
9494

95+
void OnApply_conversational_awareness_volume_percent(const Fields &newFields)
96+
{
97+
LOG(Info, "OnApply_conversational_awareness_volume_percent: {}", newFields.conversational_awareness_volume_percent);
98+
99+
ApdApp->GetMainWindow()->GetApdMgr().OnConversationalAwarenessVolumePercentChanged(
100+
newFields.conversational_awareness_volume_percent);
101+
}
102+
95103
void OnApply_personalized_volume(const Fields &newFields)
96104
{
97105
LOG(Info, "OnApply_personalized_volume: {}", newFields.personalized_volume);

0 commit comments

Comments
 (0)