diff --git a/Artemis/Artemis/Managers/AudioCaptureManager.cs b/Artemis/Artemis/Managers/AudioCaptureManager.cs index 52e063838..161448bc9 100644 --- a/Artemis/Artemis/Managers/AudioCaptureManager.cs +++ b/Artemis/Artemis/Managers/AudioCaptureManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Timers; using Artemis.Events; +using Artemis.Profiles.Layers.Types.Audio; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using CSCore.CoreAudioAPI; using Ninject.Extensions.Logging; @@ -19,8 +20,8 @@ namespace Artemis.Managers { Logger = logger; _audioCaptures = new List(); - _lastDefaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - _lastDefaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + _lastDefaultPlayback = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + _lastDefaultRecording = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); var defaultDeviceTimer = new Timer(1000); defaultDeviceTimer.Elapsed += DefaultDeviceTimerOnElapsed; @@ -31,11 +32,11 @@ namespace Artemis.Managers private void DefaultDeviceTimerOnElapsed(object sender, ElapsedEventArgs e) { - var defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - var defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + var defaultPlayback = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + var defaultRecording = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); - if (defaultPlayback.DeviceID == _lastDefaultPlayback.DeviceID && - defaultRecording.DeviceID == _lastDefaultRecording.DeviceID) + if (defaultPlayback?.DeviceID == _lastDefaultPlayback?.DeviceID && + defaultRecording?.DeviceID == _lastDefaultRecording?.DeviceID) return; _lastDefaultPlayback = defaultPlayback; @@ -43,7 +44,7 @@ namespace Artemis.Managers OnAudioDeviceChanged(new AudioDeviceChangedEventArgs(_lastDefaultPlayback, _lastDefaultRecording)); } - public AudioCapture GetAudioCapture(MMDevice device) + public AudioCapture GetAudioCapture(MMDevice device, MmDeviceType type) { // Return existing audio capture if found var audioCapture = _audioCaptures.FirstOrDefault(a => a.Device.DeviceID == device.DeviceID); @@ -51,7 +52,7 @@ namespace Artemis.Managers return audioCapture; // Else create a new one and return that - var newAudioCapture = new AudioCapture(Logger, device); + var newAudioCapture = new AudioCapture(Logger, device, type); _audioCaptures.Add(newAudioCapture); return newAudioCapture; } diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index 524dd1608..7176fbbaf 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -91,7 +91,7 @@ namespace Artemis.Modules.Abstract private void UpdatedEnabledSetting() { - if (!ModuleModel.IsGeneral || !_moduleManager.ActiveModule.IsGeneral || Settings.IsEnabled == IsModuleActive) + if (!ModuleModel.IsGeneral || (_moduleManager.ActiveModule != null && !_moduleManager.ActiveModule.IsGeneral || Settings.IsEnabled == IsModuleActive)) return; Settings.IsEnabled = IsModuleActive; diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs index ecb0e0873..4f7bea4e5 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs @@ -80,29 +80,32 @@ namespace Artemis.Modules.General.GeneralProfile private void SetupAudio() { - _defaultRecording = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); - _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); - _defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); + _defaultRecording = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia); + _defaultPlayback = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + + if (_defaultRecording != null) + _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); + if (_defaultPlayback != null) + _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); } private void AudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) { _defaultRecording = e.DefaultRecording; - _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); _defaultPlayback = e.DefaultPlayback; - _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); + + if (_defaultRecording != null) + _recordingInfo = AudioMeterInformation.FromDevice(_defaultRecording); + if (_defaultPlayback != null) + _playbackInfo = AudioMeterInformation.FromDevice(_defaultPlayback); } private void UpdateAudio(GeneralProfileDataModel dataModel) { // Update microphone, only bother with OverallPeak if (_defaultRecording != null) - { - dataModel.Audio.Recording.OverallPeak = _recordingInfo.PeakValue; - } - + if (_defaultPlayback == null) return; diff --git a/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs b/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs index 2a6c05f2b..72414dfef 100644 --- a/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs +++ b/Artemis/Artemis/Modules/Overlays/OverlayProfile/OverlayProfileModel.cs @@ -17,8 +17,10 @@ namespace Artemis.Modules.Overlays.OverlayProfile Settings = SettingsProvider.Load(); DataModel = new OverlayProfileDataModel(); - var defaultPlayback = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - _endPointVolume = AudioEndpointVolume.FromDevice(defaultPlayback); + var defaultPlayback = MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + if (defaultPlayback != null) + _endPointVolume = AudioEndpointVolume.FromDevice(defaultPlayback); + audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; Enable(); @@ -30,7 +32,8 @@ namespace Artemis.Modules.Overlays.OverlayProfile private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) { - _endPointVolume = AudioEndpointVolume.FromDevice(e.DefaultPlayback); + if (e.DefaultPlayback != null) + _endPointVolume = AudioEndpointVolume.FromDevice(e.DefaultPlayback); } public override void Update() diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index 29d488632..8ad1700de 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -5,6 +5,7 @@ using CSCore; using CSCore.CoreAudioAPI; using CSCore.DSP; using CSCore.SoundIn; +using CSCore.SoundOut; using CSCore.Streams; using Ninject.Extensions.Logging; @@ -18,16 +19,17 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing private readonly Timer _disableTimer; private bool _mayStop; private SingleSpectrum _singleSpectrum; - private WasapiLoopbackCapture _soundIn; + private ISoundIn _soundIn; private GainSource _source; private BasicSpectrumProvider _spectrumProvider; private GainSource _volume; private int _volumeIndex; - public AudioCapture(ILogger logger, MMDevice device) + public AudioCapture(ILogger logger, MMDevice device, MmDeviceType type) { Logger = logger; Device = device; + Type = type; DesiredAverage = 0.75; _volumeValues = new double[5]; @@ -40,6 +42,7 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public ILogger Logger { get; } public MMDevice Device { get; } + public MmDeviceType Type { get; } public double DesiredAverage { get; set; } public float Volume @@ -95,6 +98,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public LineSpectrum GetLineSpectrum(int barCount, ScalingStrategy scalingStrategy) { + if (_spectrumProvider == null) + return null; return new LineSpectrum(FftSize) { SpectrumProvider = _spectrumProvider, @@ -135,11 +140,20 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing { Stop(); - _soundIn = new WasapiLoopbackCapture(); + if (Type == MmDeviceType.Input) + { + _soundIn = Device != null + ? new WasapiCapture {Device = Device} + : new WasapiCapture(); + } + else + { + _soundIn = Device != null + ? new WasapiLoopbackCapture {Device = Device} + : new WasapiLoopbackCapture(); + } + _soundIn.Initialize(); - // Not sure if this null check is needed but doesnt hurt - if (Device != null) - _soundIn.Device = Device; var soundInSource = new SoundInSource(_soundIn); _source = soundInSource.ToSampleSource().AppendSource(x => new GainSource(x), out _volume); @@ -182,7 +196,7 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing { Running = false; - + if (_soundIn != null) { _soundIn.Stop(); diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesView.xaml b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesView.xaml index e6bfb99ef..00ec05a65 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesView.xaml +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesView.xaml @@ -11,6 +11,13 @@ d:DesignHeight="600" d:DesignWidth="500"> + + + + + @@ -54,22 +61,23 @@ TickPlacement="None" TickFrequency="0.05" Value="{Binding Path=LayerModel.Properties.AnimationSpeed, Mode=TwoWay}" Minimum="0.05" Maximum="3" SmallChange="0" IsSnapToTickEnabled="True" Margin="10,12,10,2" Height="24" /> - - - - - - + + + + + + + + + + - + diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs index 079b386b4..23ba6c280 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs @@ -2,8 +2,8 @@ using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.ViewModels; -using Artemis.ViewModels.Profiles; using Caliburn.Micro; +using CSCore.CoreAudioAPI; namespace Artemis.Profiles.Layers.Types.Audio { @@ -14,12 +14,18 @@ namespace Artemis.Profiles.Layers.Types.Audio public AudioPropertiesViewModel(LayerEditorViewModel editorVm) : base(editorVm) { LayerAnimations = new BindableCollection(editorVm.LayerAnimations); + Devices = new BindableCollection(); SelectedLayerAnimation = LayerAnimations.FirstOrDefault(l => l.Name == editorVm.ProposedLayer.LayerAnimation?.Name) ?? LayerAnimations.First(l => l.Name == "None"); + + SetupAudioSelection(); + if (SelectedDevice == null) + SelectedDevice = Devices.First(); } public BindableCollection LayerAnimations { get; set; } + public BindableCollection Devices { get; set; } public ILayerAnimation SelectedLayerAnimation { @@ -32,6 +38,45 @@ namespace Artemis.Profiles.Layers.Types.Audio } } + public MmDeviceType DeviceType + { + get { return ((AudioPropertiesModel) LayerModel.Properties).DeviceType; } + set + { + if (value == ((AudioPropertiesModel) LayerModel.Properties).DeviceType) return; + ((AudioPropertiesModel) LayerModel.Properties).DeviceType = value; + SetupAudioSelection(); + SelectedDevice = Devices.First(); + NotifyOfPropertyChange(() => DeviceType); + } + } + + public string SelectedDevice + { + get { return ((AudioPropertiesModel) LayerModel.Properties).Device; } + set + { + if (value == ((AudioPropertiesModel) LayerModel.Properties).Device) return; + ((AudioPropertiesModel) LayerModel.Properties).Device = value; + NotifyOfPropertyChange(() => SelectedDevice); + } + } + + private void SetupAudioSelection() + { + var properties = (AudioPropertiesModel) LayerModel.Properties; + + Devices.Clear(); + Devices.Add("Default"); + + // Select the proper devices and make sure they are unique + Devices.AddRange(properties.DeviceType == MmDeviceType.Input + ? MMDeviceEnumerator.EnumerateDevices(DataFlow.Capture, DeviceState.Active) + .Select(d => d.FriendlyName).GroupBy(d => d).Select(g => g.First()) + : MMDeviceEnumerator.EnumerateDevices(DataFlow.Render, DeviceState.Active) + .Select(d => d.FriendlyName).GroupBy(d => d).Select(g => g.First())); + } + public override void ApplyProperties() { LayerModel.Properties.Brush = Brush; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index 058b2ca65..eb0b6ef5d 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; +using System.Collections.Generic; +using System.Linq; using System.Windows; using System.Windows.Media; using Artemis.Events; @@ -15,7 +13,6 @@ using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Properties; using Artemis.Utilities; using Artemis.ViewModels; -using Artemis.ViewModels.Profiles; using CSCore.CoreAudioAPI; namespace Artemis.Profiles.Layers.Types.Audio @@ -27,22 +24,21 @@ namespace Artemis.Profiles.Layers.Types.Audio private int _lines; private LineSpectrum _lineSpectrum; private List _lineValues; + private AudioPropertiesModel _properties; + private bool _subscribed; public AudioType(AudioCaptureManager audioCaptureManager) { _audioCaptureManager = audioCaptureManager; - - // TODO: Setup according to settings - _audioCapture = _audioCaptureManager.GetAudioCapture(MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)); - _audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; } - private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) + private void SubscribeToAudioChange() { - // TODO: Check if layer must use default - // TODO: Check recording type - _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultPlayback); - _lines = 0; + if (_subscribed) + return; + + _audioCaptureManager.AudioDeviceChanged += OnAudioDeviceChanged; + _subscribed = true; } public string Name => "Keyboard - Audio visualization"; @@ -73,7 +69,7 @@ namespace Artemis.Profiles.Layers.Types.Audio var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; // Create a geometry that will be formed by all the bars - GeometryGroup barGeometry = new GeometryGroup(); + var barGeometry = new GeometryGroup(); switch (direction) { @@ -121,6 +117,25 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) { layerModel.ApplyProperties(true); + var newProperties = (AudioPropertiesModel) layerModel.Properties; + if (_properties == null) + _properties = newProperties; + + SubscribeToAudioChange(); + + if (_audioCapture == null || newProperties.Device != _properties.Device || + newProperties.DeviceType != _properties.DeviceType) + { + var device = GetMmDevice(); + if (device != null) + _audioCapture = _audioCaptureManager.GetAudioCapture(device, newProperties.DeviceType); + } + + _properties = newProperties; + + if (_audioCapture == null) + return; + _audioCapture.Pulse(); var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; @@ -142,6 +157,8 @@ namespace Artemis.Profiles.Layers.Types.Audio { _lines = currentLines; _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); + if (_lineSpectrum == null) + return; } var newLineValues = _lineSpectrum?.GetLineValues(currentHeight); @@ -183,8 +200,8 @@ namespace Artemis.Profiles.Layers.Types.Audio // Otherwise draw the rectangle with its layer.AppliedProperties dimensions and brush var rect = layerModel.Properties.Contain ? layerModel.LayerRect() - : new Rect(layerModel.Properties.X*4, layerModel.Properties.Y*4, - layerModel.Properties.Width*4, layerModel.Properties.Height*4); + : new Rect(layerModel.Properties.X * 4, layerModel.Properties.Y * 4, + layerModel.Properties.Width * 4, layerModel.Properties.Height * 4); var clip = layerModel.LayerRect(DrawScale); @@ -196,5 +213,40 @@ namespace Artemis.Profiles.Layers.Types.Audio c.DrawRectangle(brush, null, rect); c.Pop(); } + + private void OnAudioDeviceChanged(object sender, AudioDeviceChangedEventArgs e) + { + if (_properties == null || _properties.Device != "Default") + return; + + if (_properties.DeviceType == MmDeviceType.Input) + { + if (e.DefaultRecording != null) + _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultRecording, MmDeviceType.Input); + } + else + { + if (e.DefaultPlayback != null) + _audioCapture = _audioCaptureManager.GetAudioCapture(e.DefaultPlayback, MmDeviceType.Ouput); + } + + _lines = 0; + } + + private MMDevice GetMmDevice() + { + if (_properties == null) + return null; + + if (_properties.DeviceType == MmDeviceType.Input) + return _properties.Device == "Default" + ? MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia) + : MMDeviceEnumerator.EnumerateDevices(DataFlow.Capture) + .FirstOrDefault(d => d.FriendlyName == _properties.Device); + return _properties.Device == "Default" + ? MMDeviceEnumerator.TryGetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia) + : MMDeviceEnumerator.EnumerateDevices(DataFlow.Render) + .FirstOrDefault(d => d.FriendlyName == _properties.Device); + } } } \ No newline at end of file