├── .gitattributes ├── .gitignore ├── COMPILING.md ├── EarTrumpet-WithAddons.sln ├── EarTrumpet.HardwareControls ├── Addon.cs ├── AddonManifest.json ├── AddonResources.xaml ├── DataModel │ └── PlaybackDataModelHost.cs ├── EarTrumpet.HardwareControls.csproj ├── Interop │ ├── Deej │ │ ├── DeejAppBinding.cs │ │ ├── DeejConfiguration.cs │ │ ├── DeejIn.cs │ │ └── MonotonicTimestamp.cs │ ├── Hardware │ │ ├── CommandControlMappingElement.cs │ │ ├── HardwareAppBinding.cs │ │ ├── HardwareConfiguration.cs │ │ └── HardwareManager.cs │ └── MIDI │ │ ├── MidiAppBinding.cs │ │ ├── MidiConfiguration.cs │ │ ├── MidiIn.cs │ │ └── MidiInDevice.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.de-de.resx │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── ViewModels │ ├── DeejControlWizardViewModel.cs │ ├── EarTrumpetHardwareControlsPageViewModel.cs │ ├── HardwareSettingsViewModel.cs │ ├── MIDIControlWizardViewModel.cs │ ├── OSDViewModel.cs │ └── OSDWindowViewModel.cs └── Views │ ├── DeejControlWizardWindow.xaml │ ├── DeejControlWizardWindow.xaml.cs │ ├── HardwareSettingsWindow.xaml │ ├── HardwareSettingsWindow.xaml.cs │ ├── MIDIControlWizardWindow.xaml │ ├── MIDIControlWizardWindow.xaml.cs │ ├── OSDWindow.xaml │ └── OSDWindow.xaml.cs ├── Graphics ├── screenshot_hardware_controls_page.PNG ├── screenshot_new_midi_control.PNG └── usage_animation.gif ├── LICENSE ├── README.md └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | [core] 2 | * text eol=crlf 3 | 4 | *.png binary 5 | *.jpg binary 6 | *.jpeg binary 7 | *.gif binary 8 | *.ico binary 9 | *.mov binary 10 | *.mp4 binary 11 | *.mp3 binary 12 | *.flv binary 13 | *.fla binary 14 | *.swf binary 15 | *.gz binary 16 | *.zip binary 17 | *.7z binary 18 | *.ttf binary 19 | *.eot binary 20 | *.woff binary 21 | *.pyc binary 22 | *.pdf binary 23 | *.ez binary 24 | *.bz2 binary 25 | *.swp binary 26 | *.pfx binary 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | #*.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | #!**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | 198 | # Visual Studio databases 199 | EarTrumpet.VC.db 200 | EarTrumpet.VC.VC.opendb 201 | EarTrumpet.vs15.VC.db 202 | EarTrumpet.vs15.VC.VC.opendb 203 | 204 | EarTrumpet.Appx/layout/UWP/ 205 | EarTrumpet.BackgroundTasks/project.lock.json 206 | EarTrumpet.UWP/nn-no/EarTrumpet.resources.dll 207 | EarTrumpet.UWP/de/EarTrumpet.resources.dll 208 | EarTrumpet.UWP/Windows.winmd 209 | EarTrumpet.UWP/EarTrumpet.exe.config 210 | EarTrumpet.UWP/EarTrumpet.exe 211 | EarTrumpet.UWP/EarTrumpet.Interop.lib 212 | EarTrumpet.UWP/EarTrumpet.Interop.exp 213 | EarTrumpet.UWP/EarTrumpet.Interop.dll 214 | EarTrumpet.UWP/Interop.MMDeviceAPI.dll 215 | EarTrumpet.UWP/Interop.SoundControlAPI.dll 216 | EarTrumpet.UWP/nb-no/EarTrumpet.resources.dll 217 | EarTrumpet.UWP/fr/EarTrumpet.resources.dll 218 | EarTrumpet.UWP/zh-Hant/EarTrumpet.resources.dll 219 | EarTrumpet.UWP/zh-Hans/EarTrumpet.resources.dll 220 | EarTrumpet.BackgroundTasks/EarTrumpet.BackgroundTasks.nuget.props 221 | *.lastcodeanalysissucceeded 222 | *.CodeAnalysisLog.xml 223 | *.filters 224 | 225 | # Auto-generated files 226 | EarTrumpet.BackgroundTasks/EarTrumpet.BackgroundTasks.nuget.targets 227 | EarTrumpet.Interop/mmdeviceapi.h 228 | /EarTrumpet.Package/BundleArtifacts 229 | /EarTrumpet.Package/_pkginfo.txt 230 | -------------------------------------------------------------------------------- /COMPILING.md: -------------------------------------------------------------------------------- 1 | # Compiling EarTrumpet and the HardwareSupportAddon 2 | 3 | 1. Follow the instructions on how to compile the EarTrumpet application (https://github.com/File-New-Project/EarTrumpet/blob/master/COMPILING.md). Make sure that you can compile and run EarTrumpet. 4 | 5 | 2. Clone this repository to the same parent directory as the EarTrumpet repository. The directory structure with the .sln files should look like this: 6 | 7 | > EarTrumpet/EarTrumpet.vs15.sln 8 | > EarTrumpet.HardwareSupportAddon/EarTrumpet-WithAddons.sln 9 | 10 | 3. Open the solution `EarTrumpet-WithAddons.sln` in Visual Studio. 11 | 12 | 4. Build the project folder with both projects (EarTrumpet and EarTrumpet.HardwareControls). 13 | 14 | **Note:** Currently, addons are only loaded in **Debug** builds. 15 | 16 | 5. Run the application. 17 | -------------------------------------------------------------------------------- /EarTrumpet-WithAddons.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EarTrumpet", "..\EarTrumpet\EarTrumpet\EarTrumpet.csproj", "{BA3C7B42-84B0-468C-8640-217E2A24CF81}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EarTrumpet.HardwareControls", "EarTrumpet.HardwareControls\EarTrumpet.HardwareControls.csproj", "{89404C1F-FD59-4A02-9AC7-F7BF2AE1B177}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x86 = Debug|x86 13 | Release|x86 = Release|x86 14 | VSDebug|x86 = VSDebug|x86 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Debug|x86.ActiveCfg = Debug|x86 18 | {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Debug|x86.Build.0 = Debug|x86 19 | {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Release|x86.ActiveCfg = Release|x86 20 | {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Release|x86.Build.0 = Release|x86 21 | {BA3C7B42-84B0-468C-8640-217E2A24CF81}.VSDebug|x86.ActiveCfg = VSDebug|x86 22 | {BA3C7B42-84B0-468C-8640-217E2A24CF81}.VSDebug|x86.Build.0 = VSDebug|x86 23 | {89404C1F-FD59-4A02-9AC7-F7BF2AE1B177}.Debug|x86.ActiveCfg = Debug|x86 24 | {89404C1F-FD59-4A02-9AC7-F7BF2AE1B177}.Debug|x86.Build.0 = Debug|x86 25 | {89404C1F-FD59-4A02-9AC7-F7BF2AE1B177}.Release|x86.ActiveCfg = Release|x86 26 | {89404C1F-FD59-4A02-9AC7-F7BF2AE1B177}.Release|x86.Build.0 = Release|x86 27 | {89404C1F-FD59-4A02-9AC7-F7BF2AE1B177}.VSDebug|x86.ActiveCfg = Debug|x86 28 | {89404C1F-FD59-4A02-9AC7-F7BF2AE1B177}.VSDebug|x86.Build.0 = Debug|x86 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {228AD06A-DAC2-406B-8F21-3E7B980B1B37} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /EarTrumpet.HardwareControls/Addon.cs: -------------------------------------------------------------------------------- 1 | using EarTrumpet.DataModel.Audio; 2 | using EarTrumpet.DataModel.WindowsAudio; 3 | using EarTrumpet.Extensibility; 4 | using EarTrumpet.HardwareControls.DataModel; 5 | using EarTrumpet.HardwareControls.Interop.Hardware; 6 | using EarTrumpet.HardwareControls.ViewModels; 7 | using EarTrumpet.HardwareControls.Views; 8 | using EarTrumpet.UI.ViewModels; 9 | using System.Collections.Generic; 10 | using System.ComponentModel.Composition; 11 | using System.Linq; 12 | using System.Windows; 13 | 14 | namespace EarTrumpet.HardwareControls 15 | { 16 | [Export(typeof(EarTrumpetAddon))] 17 | class Addon : EarTrumpetAddon, IEarTrumpetAddonEvents, IEarTrumpetAddonSettingsPage 18 | { 19 | public static Addon Current { get; private set; } 20 | 21 | public DeviceCollectionViewModel DeviceCollection { get; private set; } 22 | 23 | private HardwareManager m_hardwareManager; 24 | private OSDWindow _osdWindow; 25 | private OSDWindowViewModel _osdWindowViewModel; 26 | private FlyoutViewModel _flyoutViewModel; 27 | 28 | public Addon() : base() 29 | { 30 | DisplayName = Properties.Resources.HardwareControlsTitle; 31 | } 32 | 33 | public void OnAddonEvent(AddonEventKind evt) 34 | { 35 | if (evt == AddonEventKind.InitializeAddon) 36 | { 37 | Current = this; 38 | 39 | DeviceCollection = ((App)App.Current).CollectionViewModel; 40 | m_hardwareManager = new HardwareManager(DeviceCollection, WindowsAudioFactory.Create(AudioDeviceKind.Playback)); 41 | 42 | 43 | // Monitor the EarTrumpet flyout so we don't show when the flyout is showing. 44 | _flyoutViewModel = (FlyoutViewModel)((App)Application.Current).FlyoutWindow.DataContext; 45 | _flyoutViewModel.StateChanged += OnFlyoutViewModelStateChanged; 46 | 47 | // Create a window to use as OSD. 48 | _osdWindowViewModel = new OSDWindowViewModel(); 49 | _osdWindow = new OSDWindow(_osdWindowViewModel); 50 | _osdWindow.Initialize(); 51 | 52 | // Listen to events and then present the active ViewModel to the OSD window. 53 | PlaybackDataModelHost.Current.AppPropertyChanged += OnAppPropertyChanged; 54 | PlaybackDataModelHost.Current.DevicePropertyChanged += OnDevicePropertyChanged; 55 | } 56 | } 57 | 58 | public SettingsCategoryViewModel GetSettingsCategory() 59 | { 60 | LoadAddonResources(); 61 | return new SettingsCategoryViewModel( 62 | "Hardware Support", 63 | "\xE9A1", 64 | "MIDI and other hardware devices", 65 | Manifest.Id, new List { 66 | new EarTrumpetHardwareControlsPageViewModel(), 67 | new OSDViewModel(), 68 | }); 69 | } 70 | 71 | private void OnFlyoutViewModelStateChanged(object sender, object e) 72 | { 73 | if (_flyoutViewModel.State != UI.Helpers.FlyoutViewState.Hidden) 74 | { 75 | // Any Flyout state change except the final hide should end OSD. 76 | _osdWindowViewModel.ImmediateClose(); 77 | } 78 | } 79 | 80 | private void OnDevicePropertyChanged(IAudioDevice device, string propertyName) 81 | { 82 | if (propertyName == nameof(device.Volume) || 83 | propertyName == nameof(device.IsMuted)) 84 | { 85 | // Trace.WriteLine($"{device.DisplayName}: {device.Volume} {device.IsMuted}"); 86 | TriggerOSDForDevice(device.Id); 87 | } 88 | } 89 | 90 | private void OnAppPropertyChanged(IAudioDeviceSession session, string propertyName) 91 | { 92 | if (propertyName == nameof(session.Volume) || 93 | propertyName == nameof(session.IsMuted)) 94 | { 95 | // Trace.WriteLine($"{session.DisplayName}: {session.Volume} {session.IsMuted}"); 96 | TriggerOSDForApp(session.Parent.Id, session.AppId); 97 | } 98 | } 99 | 100 | private void TriggerOSDForApp(string deviceId, string appId) 101 | { 102 | if (CanShowOSD()) 103 | { 104 | var device = DeviceCollection.AllDevices.FirstOrDefault(d => d.Id == deviceId); 105 | var app = device.Apps.FirstOrDefault(a => a.AppId == appId); 106 | _osdWindowViewModel.ShowForContent(app); 107 | } 108 | } 109 | 110 | private void TriggerOSDForDevice(string id) 111 | { 112 | if (CanShowOSD()) 113 | { 114 | _osdWindowViewModel.ShowForContent(DeviceCollection.AllDevices.FirstOrDefault(d => d.Id == id)); 115 | } 116 | } 117 | 118 | private bool CanShowOSD() 119 | { 120 | // Don't appear when EarTrumpet flyout is open. 121 | return _flyoutViewModel.State == UI.Helpers.FlyoutViewState.Hidden; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /EarTrumpet.HardwareControls/AddonManifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Id": "EarTrumpet.HardwareControls", 3 | "PublisherName": "applapp", 4 | "Version": "1.0.0.0", 5 | "HelpLink": "https://github.com/applapp/EarTrumpet.HardwareSupportAddon" 6 | } -------------------------------------------------------------------------------- /EarTrumpet.HardwareControls/AddonResources.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |