Acquisition
I bought a new TV a year and a half ago. Much to my dismay, I discovered that while the picture quality is great, the audio quality is not, unlike my previous TV. My daily driver Linux PC is connected to my TV, and is my main entertainment hub for TV shows, movies and video games, and also general PC usage, so I figured I had to get an external speaker or sound bar. My dad had a spare speaker which he had replaced with a newer unit, so I inherited the spare - a "Geneva Sound System Model S DAB".
It came with a small caveat: the speaker will not turn on using the remote control. Mind you, it will turn off just fine with the same exact button, and every other button on the remote works as advertised - it just will not turn on using the remote. I figured that this was no big deal, and accepted the speaker with thanks. I get the speaker situated and hooked up, and the audio quality is great - a big upgrade from the built-in TV audio. Great for music, great for movies, great for games, just all-round great...
Perturbation
...until I read some news, or do anything that doesn't produce any sound for a while. This is when the European Commission decides to take a retroactive shit in my cereal. Since 2009, audiovisual equipment such as speakers are required to switch to a low-power mode after a reasonable amount of time. In and of itself, that is a good requirement, and will save power when such devices are not in use. Sometimes it's a bad requirement, and that is when:
a) the automatic standby feature can't be disabled, and
b) the unit can't be powered on with the remote control
Bargaining
This particular set of circumstances means that whenever I'm using the computer, and switch between tasks that produce sound, and those that don't, I risk the speaker powering down, and having to get up, walk over to the speaker underneath the TV, and power it back up manually. That wouldn't be so bad if it was a once-a-day thing, but the auto-standby timeout for the speaker is fixed at 30 minutes, so you might see how this gets real old, real fast. The speaker itself works fine, and it's a shame to generate e-waste from something that's only partially defective, so I decide to stick with it. I wouldn't blame someone for chucking it and replacing it with a new unit though, but I am particularly stubborn.
Upon discovering this, the first thing I do is start searching the web for the manual for the device, to see if I can address point a) above: turning off the auto-standby function, but I come up short. There doesn't seem to be a way to disable this functionality at all. Since I wouldn't know the first thing about fixing the hardware side of this (fixing the remote not being able to turn the unit on any more), I default to trying to work around it through software.
My first attempt to trick the speaker into staying awake is playing a sound on an interval. A sound which is in frequency range of the speaker, but out of range of my ears. According to specs found online, the frequency range for the unit is 75 Hz to 20 kHz, so I try both ends of the spectrum. Using Audacity, I generate 5 second tones ranging from 76 Hz to 19 kHz, and make a script that plays the tone every 10 minutes, and slowly over the course of a few days, work my way through all the generated tones to see if any will keep the speaker awake.
It doesn't work. None of the tones do. They all play (confirmed by dropping in an audible .wav in place of the generated tones), but it seems as though the speaker is ignoring them. The 17 kHz tone had the added bonus of viscerally startling my friend who could hear the sound (I couldn't), asking what the hell that was.
Most likely the internal logic that checks for sound, checks for loudness/dB values, or some other obscure black magic fuckery I don't understand (spoilers: I am not an audio technician, and possess only cursory knowledge of audio specifics).
My second attempt is a much less sophisticated one: play a short, normal, audible sound every 20 minutes or so. I picked out a ~1 second audio file from my OS, and made it play every 20 minutes. This also didn't work. My conclusion is now that the aforementioned internal logic not only checks for loudness/amplitude, but also has a polling rate greater than a second (or at least, greater than the length of the audio file in question). I don't want to play a longer file either since it's on a fixed interval, it will play over any other media I am consuming, and become another point of annoyance.
Acceptance
At this point, I more or less resign to my fate, and continue using the speaker without any software magic to try to keep it alive. I do the whole song and dance of getting up and turning the speaker back on when I need it, several times a day. My record is 7 times in one long day of intensive programming interspersed by YouTube video breaks.
Redemption
A year and a half passes, to the present day. I am working on some other project, when I notice the speaker turning off again, and spontaneously get an idea. What if I continuously monitor my audio output, and only play an audible sound when it's been quiet for a while? I haven't done anything like that in the past, but after a ton of web searching, and trial and error, I come up with a solution. This relies on using PulseAudio (or PipeWire with its backward compatibility with PA).
That's right, there's finally going to be some code in this article!
anti-energy-saving.sh
:
#!/usr/bin/env bash
sinkToMonitor="alsa_output.pci-0000_06_00.1.hdmi-stereo" # find with `pactl list short sinks`
soundToPlay="/usr/share/sounds/freedesktop/stereo/message-new-instant.oga"
tmpOutputSample="/tmp/output-sample.flac" # saves the sample in ram and overwrites it every loop
timeoutSecs=1500 # timeout in seconds - 1500s = 25m
trap quit SIGINT
quit() {
printf "\nSIGINT received, cleaning up and exiting\n"
rm "$tmpOutputSample"
exit
}
secsSinceLastSound=0
while true; do
currDefaultSink=$(pacmd list-sinks | grep "\* index" -A1 | awk '/name:/ {print $2}')
if [[ "$currDefaultSink" = "<$sinkToMonitor>" ]]; then
timeout 5 parecord --channels=1 --file-format=flac --device "$sinkToMonitor.monitor" "$tmpOutputSample"
meanNorm=$(sox "$tmpOutputSample" -n stat 2>&1 | awk '/Mean +norm:/ {print $3}')
if (( $(echo "$meanNorm > 0.000700" | bc -l) )); then
secsSinceLastSound=0
echo "Sound threshold reached, timer reset..."
else
secsSinceLastSound=$((secsSinceLastSound+5))
echo "Sound threshold not reached, timer is at $secsSinceLastSound/$timeoutSecs seconds..."
fi
if [[ "$secsSinceLastSound" -ge $timeoutSecs ]]; then
echo "Timeout reached, playing sound and resetting timer..."
paplay "$soundToPlay"
paplay "$soundToPlay"
secsSinceLastSound=0
fi
else
echo "Active sink changed, sleeping..."
secsSinceLastSound=0
sleep 10
fi
done
To use this on your system, you'll need to change the values of the variables sinkToMonitor
, soundToPlay
and timeoutSecs
to fit your configuration.
sinkToMonitor
needs to hold the name of your default audio sink, which you can find with pactl list short sinks
soundToPlay
is a path to the sound you wish to play when the inactivity timer has been reached
timeoutSecs
is the number of seconds of inactivity to wait before playing the keep-alive-sound
Make the script executable with chmod +x anti-energy-saving.sh
.
You'll also notice that I call paplay
twice to play the same sound twice. If you use a longer audio file, you won't have to do this, I just preferred this short and inoffensive sound to play whenever the speaker is nearing its auto-standby timeout.
If you don't speak Bash, this is a quick run-down of how the script functions:
- Checks whether you're currently using your default audio sink, if not, sleeps for 10 seconds. I added this because I sometimes use a Bluetooth headset, and don't want the script running on that - only the HDMI output.
- If you're using your default audio sink, the script will record 5 seconds' worth of audio that comes out of your speaker to RAM (
/tmp
)
- The audio file is run through
sox -n stat
to get the average volume of the samples in the clip - if it exceeds a set threshold, the script will reset the inactivity timer. The reason this comparison is > 0.0007 instead of just any value > 0 is because of the aforementioned speaker polling rate; we need to make sure enough sound has been played lately that the speaker won't ignore it.
- If the audio clip didn't exceed the threshold, 5 seconds is added to the inactivity timer.
- If the inactivity timer is greater than or equal to the timeout value, a sound is played twice to keep the speaker alive, and the inactivity timer is reset
- Repeats ad infinitum
You can remove all echo
es if you'd like, they are just a remnant of me debugging this in an interactive terminal. The script is designed to run non-interactively in the background without any user input.
Conclusion
The script works great for me and my situation, and I haven't had a single incident of speaker snooze since implementation. I've set it to run on start-up, and it uses very little CPU and RAM. It also won't interrupt any other audio I am playing, since there's a little more thought to it beyond a simple sleep 900
.