Page 1 of 4

Core Audio Plugin

Posted: Tue Aug 06, 2019 4:05 am
by kgschlosser
Core Audio
Inspired by the AudioEndpoint plugin

EventGhost 0.5.0 and newer

This plugin is very much still a work in progress. If you encounter any problems or undesired behavior please follow the template below for reporting an issue to me.

Trouble Report Template

Code: Select all

Windows Version: {10}
Windows Build: {1806}

Description of problem: Please provide as much detail as possible. 
    If the problem can be recreated then describe the steps needed to reproduce the issue.
    If there is not an error and you are experiencing undesired behavior. Explain what is happening and then tell me what is supposed to be happening.
Error (if any):
[ code ]
If there is an error then copy the error and paste it here. I need all lines of the error. 
Make sure you remove the spaces in the code tag above and below the error.
[ /code ]

    [ code ]
    If you happen to have fixed the issue then please paste the code here. remove the spaces in the tags please.
    [ /code ]
This plugin was inspired by the Audio Endpoint plugin made by sem;colon and Jonib.


CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.{SESSION NAME}.Volume.{0.0000 - 100.0000}

CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.{SESSION NAME}.AudioPlayback.Started
CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.{SESSION NAME}.AudioPlayback.Stopped
CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.{SESSION NAME}.AudioPlayback.Expired

CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.Volume.{0.0000 - 100.0000}
CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.Channel.{CHANNEL NUMBER}.Volume.{0.0000 - 100.0000}

CoreAudio.Device.{DEVICE NAME}.Added
CoreAudio.Device.{DEVICE NAME}.Removed

CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.Default.Render
CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.Default.Capture

This plugin triggers events for the audio starting and stopping, part for the event is going to be the name of the application causing the event.

This is a little twist for ya. This plugin does not have actions in the traditional sense. The actions are not located under the plugin. This plugin extends the System plugin and overrides the volume actions for the system plugin.

Here is a list of the actions again remember these actions are going to show up under the System plugin.

with any of the volume actions you have a relative and an absolute. relative means you want to increase or decrease the volume from it's current level. so say you have the volume at 30% and you set 10% in one of the relative actions. it is going to make the volume 40%

absolute is whatever you set in the action that is going to be the new volume level.


You now have volume control over each individual channel. so if you wanted to only turn down you subwoofer.. you can now do that






I need to go over the importance of the IsDeviceAttached action. This is for the people that are running EG on a remote PC and administer it via RDP.
I need to explain what the problem is and then how this next action fixes that issue.

Here is the setup.
You have a PC running EG that you set up so you can RDP into it for administrative purposes.

you set an action to change the volume. This is all working as it should until you RDP into the computer. Then the bad things start to happen. Let me explain why bad things happen and then you will understand why this action is so important.
So when a single user is signed into Windows that user has direct access to the devices (kind of). When you sign in via RDP because you are not physically at that computer you cannot have direct access to the device. Windows sets up a virtual device that acts as a go between. this is the device that shows up instead of the actual device. Because this takes place when a macro runs to say change the volume you will get an error. The error is a wee bit more complex but that is the quick and dirty version of it.

This action allows you to check to see if the device exists before doing anything to it. This is an example of how to set it up

Code: Select all

macro 1:
    Jump If to Macro 2 with no return
    SetEndpointVolumeAbsolute (Virtual sound device)
macro 2:
     SetEndpointVolumeAbsolute (Physical sound device)
Now you can see how this is going to work to solve that issue!


version 0.3.0a
  • adds action PlaybackEndpointHasOutput - checks if the endpoint is currently outputting audio. there is a low volume threshold you can set
  • adds action RecordingEndpointHasInput - check if a capture endpoint is receiving audio, there is a low volume threshold you can set.
version 0.3.2a version 0.4.0a
  • adds events for audio output detection and input detection events are as follows
    • CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.AudioOutput.Started
    • CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.AudioOutput.Stopped
    • CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.AudioInput.Started
    • CoreAudio.{DEVICE NAME}.{ENDPOINT NAME}.AudioInput.Stopped
this detection process has a 1 second interval between checks and it also has a threshold of 2% to eliminate any kind of static.

version 0.4.1a
  • fixes traceback when a volume event occurs.
  • fixes unicode encoding problem
  • replaces clumsy volume control in the endpoint volume and channel volume actions.
  • fixes traceback ocuring with no default endpoint is available.
version 0.4.1a
  • hopefully fixes traceback issue with the session id's
  • removes redundant device name from the endpoint name.
    so an event like this one

    Code: Select all

    CoreAudio.Realtek High Definition Audio.Speakers (Realtek High Definition Audio).Volume.29.0000
    now becomes this

    Code: Select all

    CoreAudio.Realtek High Definition Audio.Speakers.Volume.29.0000
  • another crack at the unicode issues

Re: Core Audio Plugin

Posted: Tue Aug 06, 2019 7:42 am
by jonib
Very interesting, hopefully I can try it out soon.
kgschlosser wrote:
Tue Aug 06, 2019 4:05 am
This plugin triggers events for the audio starting and stopping, part for the event is going to be the name of the application causing the event.
This is why I started my own audio library was to get these events, but I never got to that point :cry:


Re: Core Audio Plugin

Posted: Tue Aug 06, 2019 11:52 am
by kgschlosser
Ya know.. the funny thing is.. that is not supposed to work. I am not sure as to why I am getting notifications for the audio playback from other sessions it kind of just happened.

But it is there and I know this is something many people have wanted to have. Before I made this I did quite a bit of research on how to achieve this one specific feature and the only solution I had come up with is to use the loopback so the output comes back in and then listen to the incoming stream for sound. This would only be able to tell you if something was playing and not where it is being played from. But by some hole I managed to find (I still have not figured out where it is) it ended up giving that feature when I didn't expect it.

Now I remember the purpose for the session notifications. An application can have more then a single audio session if it likes. Microsoft put into place the session notifications to make it easier for an application to monitor what is happening in each session. So an application does not have to have a polling loop to get updates from a session. It is not supposed to bleed into other applications like it has. I have something coded wrong (maybe right) somewhere that caused it i'll be damned if i can figure out where. It could be a GUID for an interface. or my method order for an interface is incorrect. or does not have the correct arguments.. who knows.

Re: Core Audio Plugin

Posted: Thu Aug 08, 2019 6:04 pm
by gibman
I remoting my eventghost host using teamview, which does not have the same audio implications as RDP does.

I am also using vbcable drivers as a virtual soundcard.

The PC is used in combination with tuneblade airfoil etc.

no events are received when starting stopping playback in spotify on the EG machine.

when I test the action I get:
Is Playback Endpoint Active: VB-Audio Virtual Cable

I have a custom created console app. written in c# that works ok. But I would like to have it working in EG.
Here it is for reference. it uses win32 api calls.
Essentially what it does is starting tuneblade when audio is active for 3 sec. and killing tuneblade once audio has been silent for X seconds.

Code: Select all

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Configuration;
using System.Diagnostics;

namespace SilenceMonitor
    class Program
        static void Main(string[] args)
           int silenceTimeOutSec = Convert.ToInt32(ConfigurationManager.AppSettings["silenceTimeOutSec"]);
           int soundInitialPeriodSec = Convert.ToInt32(ConfigurationManager.AppSettings["soundInitialPeriodSec"]);

           string killProcess = ConfigurationManager.AppSettings["killProcess"];
           string startProcess = ConfigurationManager.AppSettings["startProcess"];

           DateTime? silenceSince = null;
           DateTime? soundSince = null;
           bool silenceDetectedHandled = false;
           bool soundDetectedHandled = false;

            while (true)
                bool isplaying = IsWindowsPlayingSound();
                if (isplaying)
                    silenceSince = null;
                    if (soundSince == null)
                        soundSince = DateTime.Now;

                    Console.WriteLine("playing sound");

                    soundSince = null;
                    if (silenceSince == null)
                        silenceSince = DateTime.Now;



                var now = DateTime.Now;
                if (!silenceDetectedHandled)
                    if (silenceSince != null && silenceSince.Value.AddSeconds(silenceTimeOutSec) < now)
                        Console.WriteLine($"silence for {silenceTimeOutSec} sec detected");
                        var procesess = Process.GetProcessesByName(killProcess);
                        if (procesess?.Any() == true)
                            foreach (var p in procesess)
                                    Console.WriteLine($"killing: {p.ProcessName} (pid:{p.Id})");
                                catch (Exception e)


                            Console.WriteLine($"process not killed (not found): {killProcess}");

                        silenceDetectedHandled = true;
                        soundDetectedHandled = false;


                if (!soundDetectedHandled)
                    if (soundSince != null && soundSince.Value.AddSeconds(soundInitialPeriodSec) < now)
                        Console.WriteLine($"sound for {soundInitialPeriodSec} sec detected");
                        Console.WriteLine($"starting: {startProcess}");
                        Process externalCmdProcess = new Process();

                        externalCmdProcess.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
                        externalCmdProcess.StartInfo.Verb = "runas";
                        externalCmdProcess.StartInfo.UseShellExecute = false;
                        //externalCmdProcess.StartInfo.Arguments = args;
                        externalCmdProcess.StartInfo.FileName = startProcess;
                        //externalCmdProcess.StartInfo.RedirectStandardOutput = true;
                        //externalCmdProcess.StartInfo.RedirectStandardError = true;
                        //externalCmdProcess.StartInfo.RedirectStandardInput = true;

                        //externalCmdProcess.OutputDataReceived += process_OutputDataReceived;
                        //externalCmdProcess.ErrorDataReceived += process_OutputDataReceived;

                        soundDetectedHandled = true;
                        silenceDetectedHandled = false;



        public static bool IsWindowsPlayingSound()
            var enumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
            var speakers = enumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
            var meter = (IAudioMeterInformation)speakers.Activate(typeof(IAudioMeterInformation).GUID, 0, IntPtr.Zero);
            var value = meter.GetPeakValue();

            // this is a bit tricky. 0 is the official "no sound" value
            // but for example, if you open a video and plays/stops with it (w/o killing the app/window/stream),
            // the value will not be zero, but something really small (around 1E-09)
            // so, depending on your context, it is up to you to decide
            // if you want to test for 0 or for a small value
            return value > 1E-08;

        [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
        private class MMDeviceEnumerator

        private enum EDataFlow

        private enum ERole

        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
        private interface IMMDeviceEnumerator
            void NotNeeded();
            IMMDevice GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role);
            // the rest is not defined/needed

        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("D666063F-1587-4E43-81F1-B948E807363F")]
        private interface IMMDevice
            [return: MarshalAs(UnmanagedType.IUnknown)]
            object Activate([MarshalAs(UnmanagedType.LPStruct)] Guid iid, int dwClsCtx, IntPtr pActivationParams);
            // the rest is not defined/needed

        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064")]
        private interface IAudioMeterInformation
            float GetPeakValue();
            // the rest is not defined/needed


Re: Core Audio Plugin

Posted: Thu Aug 08, 2019 9:42 pm
by kgschlosser
Hey that's a pretty nifty way to go about it. I like it. never thought about using the peak meters to get that information. the only thing is I will not be able to grab the program name that is creating the audio. I will only be able to spit out the peak meter values overall.

below is a outline of some of the available mechanisms in the CoreAudio plugin. You can gain access to them by using the Get Default Playback Endpoint or the Get Default Recording Endpoint actions. how to do this is explained below the outline

if the returned value class (bit after the last ".") begins with an "I" then it is a ctypes interface it is a connection to C code and the passing of parameters needs to be done a specific way.

All properties you can get from if there is a set available it is noted for that property

    • data_flow = str
    • name = str
    • description = str
    • form_factor = str
    • type = dont remember
    • full_range_speakers = CoreAudio.pyWinCoreAudio.speakers.AudioSpeakers
    • guid = str
    • physical_speakers = CoreAudio.pyWinCoreAudio.speakers.AudioSpeakers
    • system_effects = bool
    • jack_descriptions = CoreAudio.pyWinCoreAudio.jack.AudioJackDescription
    • jack_information = CoreAudio.pyWinCoreAudio.jack.AudioJackSinkInformation
    • auto_gain_control = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioAutoGainControl
    • bass = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioBass
    • channel_config = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioChannelConfig
    • input = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioInputSelector
    • loudness = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioLoudness
    • midrange = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioMidrange
    • output = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioOutputSelector
    • peak_meter = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioPeakMeter
    • treble = CoreAudio.pyWinCoreAudio.__core_audio.devicetopologyapi.IAudioTreble
    • volume = CoreAudio.pyWinCoreAudio.volume.AudioVolume
    • is_default = bool
    • session_manager = CoreAudio.pyWinCoreAudio.session.AudioSessionManager
    • audio_client = IAudioClient, IAudioClient2 or IAudioClient3 depending on your windows version
    • id = str
    • device = CoreAudio.pyWinCoreAudio.device.AudioDevice
    • icon = str
    • set_default()

CoreAudio.pyWinCoreAudio.jack.AudioJackSinkInformation: the names of the properties are pretty self descriptive.
    • manufacturer_id = str
    • product_id = str
    • audio_latency = ???
    • hdcp_capable = bool
    • ai_capable = bool
    • description = str
    • port_id = str
    • connection_type = str

    • channel_mapping = CoreAudio.pyWinCoreAudio.speakers.AudioSpeakers
    • color = (red, green, blue)
    • type = str
    • location = str
    • port = str
    • is_connected = bool
    • presence_detection = bool
    • dynamic_format_change = ???
CoreAudio.pyWinCoreAudio.speakers.AudioSpeakers: this lets you know what speakers are connected and what speakers are not.
    • front_left = bool
    • front_right = bool
    • center = bool
    • high_left = bool
    • high_right = bool
    • side_left = bool
    • side_right = bool
    • back_left = bool
    • back_right = bool
    • back_center = bool
    • subwoofer = bool
    • string = str

CoreAudio.pyWinCoreAudio.session.AudioSessionManager: This is for generating the sesion events and there is nothing really usable here

    • endpoint = CoreAudio.pyWinCoreAudio.endpoint.AudioEndpoint
    • channels = CoreAudio.pyWinCoreAudio.volume.AudioVolumeChannels
    • master = volume level from 0-100 percent based value as a float
    • master_scalar = volume level percentage / 100.0 so 0.0 = 0% and 1.0 = 100%
    • mute = get/set the mute True or False
    • min = the lowest the volume can go
    • max = the highest the volume can go
    • step = how much you can increase or decrease the volume by
    • peak_meter = CoreAudio.pyWinCoreAudio.volume.AudioPeakMeter
    • up() = volume up one step
    • down() = volume down one step

    • count = the number of channels
you use this class as a list. so channels[0] is channel 1 and channels[1] is channel 2 and so on and so forth. the "channel" is
and instance of CoreAudio.pyWinCoreAudio.volume.AudioVolumeChannel

    • level = get/set the volume for that channel 0.0-100.0%
    • level_scalar = get/set the scalar volume level for the channel 0.0 - 1.0
    • min = minimum allowed volume level as a float
    • max = maximum allowed volume level as a float
    • step = get the amount you can increase or decrease the level by as a float
    • channel_peak_values = list of the peak channel values
    • channel_count = number of channels
    • peak_value = averaged peak of all channels (I think that is what it is)

Now for a quick and dirty way of doing this until i add the feature into the plugin.

I am going to assume you have that cable thing set to either the default rendered or default capture (recording) endpoint. either or you will be able to use this mechanism and it is going to expose all of the Windows API functions to you.

if you hold a reference to this object and for some reason the device gets removed from the system nd the added back in again if you perform a command on the object it is going to pitch a fit about it. So be sure to get a new object each and every single time.

create a macro.
In that macro add in either the Get Default Playback Endpoint or the Get Default Recording Endpoint depending on which one you are using.

this is going to return a reference to CoreAudio.pyWinCoreAudio.endpoint.AudioDefaultEndpoint this reference is an extension of CoreAudio.pyWinCoreAudio.endpoint.AudioEndpoint

if you paste this code into a python script action in that macro you made under the Get action then execute the macro (right click on the macro and then click on execute) it should spit out the peak values. from there you can make any adjustments you need. I do not know what your cutoff point is for determining if there is audio playing or not.

Code: Select all

# we are not using the peak_meter that is available directly in the endpoint because
# that is the C interface version of it. I have done all of the legwork for you and the 
# data type conversions. so use this peak meter instead.

# get the reference to the endpoint. eg.result gets set by the action above this script.
endpoint = eg.result

# now we get a reference to the peak meter. The reason why we set this as a 
# variable is so it will only execute the c code a single time instead of over and 
# over again
peak_meter = endpoint.volume.peak_meter
average = peak_meter.peak_value

print 'AVERAGE PEAK:', average

# enumerate the channel peak values.
for i, value in enumerate(peak_meter.channel_peak_values):
    print 'CHANNEL', i, 'PEAK VALUE:', value

# this ensures the reference to the object is removed and can be garbage collected.
del peak_meter
del endpoint 
eg.result = None

Let me know if this works or not.

Re: Core Audio Plugin

Posted: Thu Aug 08, 2019 9:43 pm
by kgschlosser
i wanted to mention this is an almost complete Python API for accessing the Audio features of Windows. there are several bits I still have to hammer out but all in all it is the most complete python API available.

Re: Core Audio Plugin

Posted: Thu Aug 08, 2019 10:17 pm
by kgschlosser
OK so i went ahead an added that feature already. it is available on the first port. I added 2 new actions.

Re: Core Audio Plugin

Posted: Fri Aug 09, 2019 9:16 am
by gibman
thanks for the update.

I just tested it.
No events received on based on action: "Playback Endpoint Has Output: VB-Audio Virtual Cable"
when I hit the test button it just replies back to EG:
11:15:19 Playback Endpoint Has Output: VB-Audio Virtual Cable

regardless of audio being outputted or not.
opening the sound controls (windows control panel) I can see the vbcable happily outputting stuff as spotify is actively running.

btw. when I tried turning the vol up/down within the systay area icon EG caused a bucketload of exceptions:

11:08:19 Exception in thread Thread-17:
11:08:19 Traceback (most recent call last):
11:08:19 File "threading.pyc", line 801, in __bootstrap_inner
11:08:19 File "threading.pyc", line 754, in run
11:08:19 File "C:\ProgramData\EventGhost\plugins\CoreAudio\pyWinCoreAudio\", line 67, in do
11:08:19 mute
11:08:19 File "C:\ProgramData\EventGhost\plugins\CoreAudio\", line 357, in endpoint_volume_change
11:08:19 vol_diff = new_vol - old_vol
11:08:19 TypeError: unsupported operand type(s) for -: 'float' and 'NoneType'

Re: Core Audio Plugin

Posted: Fri Aug 09, 2019 12:39 pm
by kgschlosser
thanks for the report of the exceptions..

Actions will not necessarily cause events. they may simply return information about what is going on.

create a macro. add the action to it. then add the EventGhost/Dump Result Action. then you will see in the log the returned data.

If you use one of the new actions you will want to place after it the jump if action. this allows you to run 2 different sets of actions based on what the return value is.

Re: Core Audio Plugin

Posted: Fri Aug 09, 2019 12:53 pm
by gibman
thanks for the info.

I get this in the log window:

14:48:23 Playback Endpoint Has Output: VB-Audio Virtual Cable
14:48:23 Playback Endpoint Has Output: VB-Audio Virtual Cable
14:48:23 Dump Result to Log
14:48:23 True
14:48:28 Task.Deactivated.EventGhost
14:48:28 Task.Activated.Spotify
14:48:32 Task.Deactivated.Spotify
14:48:32 Task.Activated.EventGhost
14:48:38 Playback Endpoint Has Output: VB-Audio Virtual Cable
14:48:38 Playback Endpoint Has Output: VB-Audio Virtual Cable
14:48:38 Dump Result to Log
14:48:38 False

so the result is true when audio is active and false when inactive.
So it works :)

although no events are thrown :(

Re: Core Audio Plugin

Posted: Fri Aug 09, 2019 1:01 pm
by kgschlosser
OK i updated the plugin to fix you traceback issue.

Re: Core Audio Plugin

Posted: Fri Aug 09, 2019 1:39 pm
by kgschlosser
OK sir... you have your events.
plugin has been updated.

Re: Core Audio Plugin

Posted: Fri Aug 09, 2019 2:10 pm
by gibman
thank you my friend !!

It works now.

Is there any way to accomplish something like this:

1) throw stopped event after a user definable amount of time has passed with silence
2) the same for the started event, raise the event once audio has been active for x seconds.

I couldnt fine a builtin method within EG that could handle this.
But then again Im not too knowledgeable about EG either .)

Re: Core Audio Plugin

Posted: Fri Aug 09, 2019 7:51 pm
by kgschlosser
how long are we talking for a stop time?

you could do something like this

event for stopped comes in
action wait(duration)
action check output
jump if to empty macro with no return
actions that you want to run if there is no audio

Re: Core Audio Plugin

Posted: Sat Sep 14, 2019 8:14 pm
by z3us
I´m getting this error when EG loads plugins:

Code: Select all

      Error iniciando el plugin: Core Audio
      Traceback (most recent call last) (0.5.0-rc6):
        File "C:\Program Files (x86)\EventGhost\eg\Classes\", line 196, in Start
        File "C:\ProgramData\EventGhost\plugins\CoreAudio3\", line 759, in __start__
          self.callbacks = Callbacks(self)
        File "C:\ProgramData\EventGhost\plugins\CoreAudio3\", line 265, in __init__
        File "C:\ProgramData\EventGhost\plugins\CoreAudio3\", line 282, in __map_device
      UnicodeEncodeError: 'ascii' codec can't encode character u'\xf3' in position 4: ordinal not in range(128)
EG 0.5.0 rc6
Windows 10