Wednesday, August 23, 2017

Getting pulseaudio volume for default sink, once and for all

This was done with pulseaudio 8.0

So, here it is:

echo $(( ($(pacmd list-sinks | sed -n -e "/$(pacmd stat | grep "Default sink name" | cut -d ' ' -f4)/,\$p" | grep -m1 volume | cut -d '/' -f2,4 | tr -d '% ' | tr '/' '+')) / 2 ))

Wow.. that's a mouthful. Let's break it down...

First, we know the information we want is found by running pacmd list-sinks. Unfortunately, there's a bunch of extra info in there we don't need. Sure, we can grep for volume to filter it out, but what if there's more than one sink listed here? Or worse, what if the default sink isn't the first, or second everytime? Okay, we'll tackle that in a minute, but it's clear that we need to know the default sink.

The default sink is shown by running pacmd stat. So to extract it we first run the command, grep for 'Default sink name' and then finally clean up the output line with cut (split on spaces and take the fourth field):

# get default sink name
pacmd stat | grep "Default sink name" | cut -d ' ' -f4

Now that we have our default sink name in our back pocket, we can get back to our first question: How do we get the volume for the default sink (even if it's not the only one in the list and we don't know the order)?

Let's look at some example output:

1 sink(s) available.
  * index: 74
 name: 
 driver: 
 flags: HARDWARE DECIBEL_VOLUME LATENCY DYNAMIC_LATENCY
 state: RUNNING
 suspend cause: 
 priority: 9950
 volume: front-left: 45837 /  70% / -9.32 dB,   front-right: 45837 /  70% / -9.32 dB
         balance 0.00
 base volume: 65536 / 100% / 0.00 dB
 volume steps: 65537
 muted: no
 ... and many more ...

First we notice that I'm lucky and I only have one sink, but let's pretend for a second that this is somewhere in the middle of 45 odd available sinks.. The indexes change, but that name will always be the same — and as luck would have it, it's the first attribute in the list!

So, we just need a way to skip to the line with our sink name, then find the first line containing of 'volume:' after that. The following uses sed to print all lines after the matched line, then a pipe to grep with a maximum of 1 match gets us the line we need.

# get volume line of default sink
pacmd list-sinks |
    sed -n -e "/$(
        pacmd stat | grep "Default sink name" | cut -d ' ' -f4
    )/,\$p" |
    grep -m1 volume

Note that we're using process substitution here to basically paste the output of our command to find the default sink name right in the middle of our other command. If you haven't heard of process substitution in bash, look it up, it will change your life.

Awesome! Only problem is, we've got two volumes here. Most people assume that they'll always be the same and just take the first. We could do that, but we're so close here.. let's take the average. First, if you haven't heard of arithmetic expansion in bash, go look it up.

Let's get started, I'll just go through the commands and there outputs iteratively. I'll save the output of the our command so far and pipe it in to save from cluttering the commands for now:


# first we'll save our output so far
VOL_LINE=$(pacmd list-sinks |
    sed -n -e "/$(
        pacmd stat | grep "Default sink name" | cut -d ' ' -f4
    )/,\$p" |
    grep -m1 volume)

# now our friend cut to the rescue again to split on '/' and get fields 2 and 4:
echo "$VOL_LINE" | cut -d '/' -f2,4
# returns: '  70% /  70% '

# ok, close, we just need to lose the percent signs and extra spaces:
echo "$VOL_LINE" | cut -d '/' -f2,4 | tr -d '% '
# returns: '70/70'

# ok, but to get the average, we need to add these, not divide:
echo "$VOL_LINE" | cut -d '/' -f2,4 | tr -d '% ' | tr '/' '+'
#returns: '70+70'

# perfect! now we can use process substitution and arithmetic substitution to
# evaluate this and divide by 2:
# (note the extra parenthesis for order of operations)
echo $(( ($(echo "$VOL_LINE" | cut -d '/' -f2,4 | tr -d '% ' | tr '/' '+')) / 2 ))
# returns: '70'

# Great! Now we just need to paste in our command from earlier to lose the
# VOL_LINE variable and make this a one-liner (well, single command)!
echo $(( ($(
    pacmd list-sinks |
    sed -n -e "/$(pacmd stat | grep "Default sink name" | cut -d ' ' -f4)/,\$p" |
    grep -m1 volume |
    cut -d '/' -f2,4 |
    tr -d '% ' |
    tr '/' '+')) / 2 ))
# still returns: '70'

If you haven't noticed, my volume was set to 70% the whole time, your values will differ if your volume is different, but the values should still work out ;)

And there it is.. hope you found this helpful!

No comments :