diff --git a/Desktop/Application/MaxMix/MaxMix.csproj b/Desktop/Application/MaxMix/MaxMix.csproj
index 25d45f52..ae5aef01 100644
--- a/Desktop/Application/MaxMix/MaxMix.csproj
+++ b/Desktop/Application/MaxMix/MaxMix.csproj
@@ -111,6 +111,7 @@
+
diff --git a/Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs b/Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs
index 03714645..3d03fc69 100644
--- a/Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs
+++ b/Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs
@@ -6,7 +6,9 @@
namespace MaxMix.Services.Audio
{
+ public delegate void AudioEndpointDelegate(object sender, string displayName, int volume, bool isMuted);
+ public delegate void AudioEndpointVolumeDelegate(object sender, int volume, bool isMuted);
public delegate void AudioSessionDelegate(object sender, int pid, string displayName, int volume, bool isMuted);
- public delegate void AudioSessionVolumeDelegate(object sender, int pid, int volume, bool isMuted);
public delegate void AudioSessionNameDelegate(object sender, int pid, string displayName);
+ public delegate void AudioSessionVolumeDelegate(object sender, int pid, int volume, bool isMuted);
}
diff --git a/Desktop/Application/MaxMix/Services/Audio/AudioEndpointVolumeWrapper.cs b/Desktop/Application/MaxMix/Services/Audio/AudioEndpointVolumeWrapper.cs
new file mode 100644
index 00000000..3762112b
--- /dev/null
+++ b/Desktop/Application/MaxMix/Services/Audio/AudioEndpointVolumeWrapper.cs
@@ -0,0 +1,103 @@
+using CSCore.CoreAudioAPI;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MaxMix.Services.Audio
+{
+ ///
+ /// Provides a facade with a simpler interface over the AudioSessionControl
+ /// CSCore class.
+ ///
+ public class AudioEndpointVolumeWrapper : IDisposable
+ {
+ #region Constructor
+ public AudioEndpointVolumeWrapper(AudioEndpointVolume endpointVolume)
+ {
+ _endpointVolume = endpointVolume;
+
+ _callback = new AudioEndpointVolumeCallback();
+ _callback.NotifyRecived += OnEndpointNotifyReceived;
+ _endpointVolume.RegisterControlChangeNotify(_callback);
+
+ _isNotifyEnabled = true;
+ }
+ #endregion
+
+ #region Events
+ ///
+ /// Raised when the volume of this session has changed.
+ ///
+ public event EventHandler VolumeChanged;
+ #endregion
+
+ #region Fields
+ private AudioEndpointVolume _endpointVolume;
+ private AudioEndpointVolumeCallback _callback;
+ private bool _isNotifyEnabled;
+ #endregion
+
+ #region Properties
+ ///
+ /// The display name of the process that created this session.
+ ///
+ public string DisplayName
+ {
+ get => "Master";
+ }
+
+ ///
+ /// Current volume of this session (0-100).
+ ///
+ public int Volume
+ {
+ get => (int)(_endpointVolume.MasterVolumeLevelScalar * 100);
+ set
+ {
+ _isNotifyEnabled = false;
+ _endpointVolume.MasterVolumeLevelScalar = value / 100f;
+ }
+ }
+
+ ///
+ /// Current mute state of this session.
+ ///
+ public bool IsMuted
+ {
+ get => _endpointVolume.IsMuted;
+ set
+ {
+ _isNotifyEnabled = false;
+ _endpointVolume.IsMuted = value;
+ }
+ }
+ #endregion
+
+ #region Event Handlers
+ private void OnEndpointNotifyReceived(object sender, AudioEndpointVolumeCallbackEventArgs e)
+ {
+ if (!_isNotifyEnabled)
+ {
+ _isNotifyEnabled = true;
+ return;
+ }
+
+ VolumeChanged?.Invoke(this, e);
+ }
+ #endregion
+
+ #region IDisposable
+ public void Dispose()
+ {
+ if (_callback != null)
+ _endpointVolume.UnregisterControlChangeNotify(_callback);
+
+ _endpointVolume = null;
+ _callback = null;
+ }
+ #endregion
+ }
+}
diff --git a/Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs b/Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs
index eb92a30b..ab7b83a3 100644
--- a/Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs
+++ b/Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs
@@ -26,10 +26,21 @@ public AudioSessionService()
#region Fields
private readonly SynchronizationContext _synchronizationContext;
private AudioSessionManager2 _sessionManager;
+ private AudioEndpointVolumeWrapper _endpointVolumeWrapper;
private IDictionary _wrappers;
#endregion
#region Events
+ ///
+ /// Raised when the volume for an active session has changed.
+ ///
+ public event AudioEndpointDelegate EndpointCreated;
+
+ ///
+ /// Raised when the volume for an active session has changed.
+ ///
+ public event AudioEndpointVolumeDelegate EndpointVolumeChanged;
+
///
/// Raised when a new audio session has been created.
///
@@ -53,7 +64,7 @@ public AudioSessionService()
public void Start()
{
_wrappers = new Dictionary();
- new Thread(() => InitializeEvents()).Start();
+ new Thread(() => InitializeWrappers()).Start();
}
///
@@ -67,6 +78,11 @@ public void Stop()
_sessionManager.Dispose();
}
+ if(_endpointVolumeWrapper != null)
+ {
+ _endpointVolumeWrapper.Dispose();
+ }
+
if (_wrappers != null)
{
foreach (var wrapper in _wrappers.Values)
@@ -77,39 +93,53 @@ public void Stop()
}
///
- /// Sets the volume of an audio session.
+ /// Sets the volume of the endpoint (master volume).
///
- /// The process Id of the target session.
/// The desired volume.
- public void SetVolume(int pid, int volume)
+ /// The mute state of the endpoint.
+ public void SetEndpointVolume(int volume, bool isMuted)
{
- if (!_wrappers.ContainsKey(pid))
- return;
-
- _wrappers[pid].Volume = volume;
+ _endpointVolumeWrapper.Volume = volume;
+ _endpointVolumeWrapper.IsMuted = isMuted;
}
///
- /// Sets the mute state of an audio session.
+ /// Sets the volume of an audio session.
///
/// The process Id of the target session.
- /// The mute state.
- public void SetMute(int pid, bool isMuted)
+ /// The desired volume.
+ public void SetSessionVolume(int pid, int volume, bool isMuted)
{
if (!_wrappers.ContainsKey(pid))
return;
+ _wrappers[pid].Volume = volume;
_wrappers[pid].IsMuted = isMuted;
}
#endregion
#region Private Methods
- private void InitializeEvents()
+ private void InitializeWrappers()
{
- _sessionManager = GetDefaultAudioSessionManager(DataFlow.Render);
- _sessionManager.SessionCreated += OnSessionCreated;
+ AudioEndpointVolume endpointVolume;
+ GetDefaultEndpointObjects(DataFlow.Render, out _sessionManager, out endpointVolume);
- using (var sessionEnumerator = _sessionManager.GetSessionEnumerator())
+ InitializeEndpointWrapper(endpointVolume);
+ InitializeSessionWrappers(_sessionManager);
+ }
+
+ private void InitializeEndpointWrapper(AudioEndpointVolume endpointVolume)
+ {
+ _endpointVolumeWrapper = new AudioEndpointVolumeWrapper(endpointVolume);
+ _endpointVolumeWrapper.VolumeChanged += OnEndpointVolumeChanged;
+ RaiseEndpointCreated(_endpointVolumeWrapper.DisplayName, _endpointVolumeWrapper.Volume, _endpointVolumeWrapper.IsMuted);
+ }
+
+ private void InitializeSessionWrappers(AudioSessionManager2 sessionManager)
+ {
+ sessionManager.SessionCreated += OnSessionCreated;
+
+ using (var sessionEnumerator = sessionManager.GetSessionEnumerator())
foreach (var session in sessionEnumerator)
{
var session2 = session.QueryInterface();
@@ -141,18 +171,26 @@ private void UnregisterSession(AudioSessionWrapper wrapper)
wrapper.Dispose();
}
- private AudioSessionManager2 GetDefaultAudioSessionManager(DataFlow dataFlow)
+ private void GetDefaultEndpointObjects(DataFlow dataFlow, out AudioSessionManager2 sessionManager, out AudioEndpointVolume endpointVolume)
{
- AudioSessionManager2 sessionManager;
using (var enumerator = new MMDeviceEnumerator())
+ {
using (var device = enumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia))
+ {
sessionManager = AudioSessionManager2.FromMMDevice(device);
-
- return sessionManager;
+ endpointVolume = AudioEndpointVolume.FromDevice(device);
+ }
+ }
}
#endregion
#region Event Handlers
+ private void OnEndpointVolumeChanged(object sender, AudioEndpointVolumeCallbackEventArgs e)
+ {
+ var wrapper = (AudioEndpointVolumeWrapper)sender;
+ RaiseEndpointVolumeChanged(wrapper.Volume, wrapper.IsMuted);
+ }
+
private void OnSessionCreated(object sender, SessionCreatedEventArgs e)
{
var session = e.NewSession;
@@ -175,6 +213,22 @@ private void OnSimpleVolumeChanged(object sender, AudioSessionSimpleVolumeChange
#endregion
#region Event Dispatchers
+ private void RaiseEndpointCreated(string displayName, int volume, bool isMuted)
+ {
+ if (SynchronizationContext.Current != _synchronizationContext)
+ _synchronizationContext.Post(o => EndpointCreated?.Invoke(this, displayName, volume, isMuted), null);
+ else
+ EndpointCreated.Invoke(this, displayName, volume, isMuted);
+ }
+
+ private void RaiseEndpointVolumeChanged(int volume, bool isMuted)
+ {
+ if (SynchronizationContext.Current != _synchronizationContext)
+ _synchronizationContext.Post(o => EndpointVolumeChanged?.Invoke(this, volume, isMuted), null);
+ else
+ EndpointVolumeChanged?.Invoke(this, volume, isMuted);
+ }
+
private void RaiseSessionCreated(int pid, string displayName, int volume, bool isMuted)
{
if (SynchronizationContext.Current != _synchronizationContext)
diff --git a/Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs b/Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs
index f57ac389..d27a9204 100644
--- a/Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs
+++ b/Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs
@@ -115,14 +115,18 @@ public int Volume
public bool IsMuted
{
get => _simpleAudio.IsMuted;
- set => _simpleAudio.IsMuted = value;
+ set
+ {
+ _isNotifyEnabled = false;
+ _simpleAudio.IsMuted = value;
+ }
}
#endregion
#region Event Handlers
private void OnSimpleVolumeChanged(object sender, AudioSessionSimpleVolumeChangedEventArgs e)
{
- if(!_isNotifyEnabled)
+ if (!_isNotifyEnabled)
{
_isNotifyEnabled = true;
return;
@@ -133,7 +137,7 @@ private void OnSimpleVolumeChanged(object sender, AudioSessionSimpleVolumeChange
private void OnStateChanged(object sender, AudioSessionStateChangedEventArgs e)
{
- if(e.NewState == AudioSessionState.AudioSessionStateExpired)
+ if (e.NewState == AudioSessionState.AudioSessionStateExpired)
{
SessionDisconnected?.Invoke(this, new AudioSessionDisconnectedEventArgs(AudioSessionDisconnectReason.DisconnectReasonSessionDisconnected));
}
@@ -154,7 +158,7 @@ public void Dispose()
_session2 = null;
_simpleAudio = null;
_events = null;
- }
+ }
#endregion
}
}
diff --git a/Desktop/Application/MaxMix/Services/Audio/IAudioSessionService.cs b/Desktop/Application/MaxMix/Services/Audio/IAudioSessionService.cs
index 0116b73f..ccb1f7fd 100644
--- a/Desktop/Application/MaxMix/Services/Audio/IAudioSessionService.cs
+++ b/Desktop/Application/MaxMix/Services/Audio/IAudioSessionService.cs
@@ -11,9 +11,11 @@ internal interface IAudioSessionService
{
void Start();
void Stop();
- void SetVolume(int pid, int volume);
- void SetMute(int pid, bool isMuted);
+ void SetEndpointVolume(int volume, bool isMuted);
+ void SetSessionVolume(int pid, int volume, bool isMuted);
+ event AudioEndpointDelegate EndpointCreated;
+ event AudioEndpointVolumeDelegate EndpointVolumeChanged;
event AudioSessionDelegate SessionCreated;
event EventHandler SessionRemoved;
event AudioSessionVolumeDelegate SessionVolumeChanged;
diff --git a/Desktop/Application/MaxMix/Services/Communication/CommunicationService.cs b/Desktop/Application/MaxMix/Services/Communication/CommunicationService.cs
index b82b8ba0..a6db3e03 100644
--- a/Desktop/Application/MaxMix/Services/Communication/CommunicationService.cs
+++ b/Desktop/Application/MaxMix/Services/Communication/CommunicationService.cs
@@ -25,7 +25,7 @@ public CommunicationService(ISerializationService serializationService)
#region Consts
private const int _checkPortInterval = 500;
- private const int _timeout = 100;
+ private const int _timeout = 1000;
#endregion
#region Fields
@@ -95,6 +95,12 @@ public void Send(IMessage message)
{
var message_ = _serializationService.Serialize(message);
_serialPort.Write(message_, 0, message_.Length);
+
+ // TODO: Temporary hack to prevent messages being sent too quickly.
+ // When the application is first run or the device is first connected, if there are
+ // multiple audio sessions active, the device does only receive the first message.
+ // Serial communication on the device side needs to be made more robust.
+ Thread.Sleep(50);
}
catch(Exception e)
{
diff --git a/Desktop/Application/MaxMix/ViewModels/MainViewModel.cs b/Desktop/Application/MaxMix/ViewModels/MainViewModel.cs
index ff39475e..193ba4fb 100644
--- a/Desktop/Application/MaxMix/ViewModels/MainViewModel.cs
+++ b/Desktop/Application/MaxMix/ViewModels/MainViewModel.cs
@@ -36,6 +36,8 @@ public MainViewModel()
_serializationService.RegisterType(5);
_audioSessionService = new AudioSessionService();
+ _audioSessionService.EndpointCreated += OnAudioEndpointCreated;
+ _audioSessionService.EndpointVolumeChanged += OnAudioEndpointVolumeChanged;
_audioSessionService.SessionCreated += OnAudioSessionCreated;
_audioSessionService.SessionRemoved += OnAudioSessionRemoved;
_audioSessionService.SessionVolumeChanged += OnAudioSessionVolumeChanged;
@@ -180,6 +182,18 @@ private void RaiseExitRequested()
#endregion
#region EventHandlers
+ private void OnAudioEndpointCreated(object sender, string displayName, int volume, bool isMuted)
+ {
+ var message = new MessageAddSession(int.MinValue, displayName, volume, isMuted);
+ _communicationService.Send(message);
+ }
+
+ private void OnAudioEndpointVolumeChanged(object sender, int volume, bool isMuted)
+ {
+ var message = new MessageUpdateVolumeSession(int.MinValue, volume, isMuted);
+ _communicationService.Send(message);
+ }
+
private void OnAudioSessionCreated(object sender, int pid, string displayName, int volume, bool isMuted)
{
var message = new MessageAddSession(pid, displayName, volume, isMuted);
@@ -217,8 +231,11 @@ private void OnMessageReceived(object sender, IMessage message)
if (message.GetType() == typeof(MessageUpdateVolumeSession))
{
var message_ = message as MessageUpdateVolumeSession;
- _audioSessionService.SetVolume(message_.Id, message_.Volume);
- _audioSessionService.SetMute(message_.Id, message_.IsMuted);
+
+ if(message_.Id == int.MinValue)
+ _audioSessionService.SetEndpointVolume(message_.Volume, message_.IsMuted);
+ else
+ _audioSessionService.SetSessionVolume(message_.Id, message_.Volume, message_.IsMuted);
}
}
diff --git a/Embedded/MaxMix/MaxMix.ino b/Embedded/MaxMix/MaxMix.ino
index 0a646a6c..52234c14 100644
--- a/Embedded/MaxMix/MaxMix.ino
+++ b/Embedded/MaxMix/MaxMix.ino
@@ -162,9 +162,7 @@ void loop()
//---------------------------------------------------------
void ClearReceive()
{
- receiveIndex = 0;
- ClearBuffer(receiveBuffer, RECEIVE_BUFFER_SIZE);
- ClearBuffer(decodeBuffer, RECEIVE_BUFFER_SIZE);
+ receiveIndex = 0;
}
//---------------------------------------------------------
@@ -172,8 +170,6 @@ void ClearReceive()
void ClearSend()
{
sendIndex = 0;
- ClearBuffer(sendBuffer, SEND_BUFFER_SIZE);
- ClearBuffer(encodeBuffer, SEND_BUFFER_SIZE);
}
//---------------------------------------------------------