PeppyMeter

That PLabs Player looks great and has some interesting solutions which I've never seen before. Those touch buttons look cool :)
I'm making something similar (Peppy player) but completely based on Raspberry Pi platform.

I've checked the syntax of the File Plugin configuration here:
ALSA project - the C library reference: PCM (digital audio) plugins
but don't see anything related to mbuffer. Is there any other documentation or examples showing how I can do that?
Thanks!
 
I have been admiring your woodwork for years. I do not have the equipment to make such fancy enclosures, I have to resort solely to modifying the original PC cases.


The mbuffer or its predecessor buffer is a large fifo with some extra parameters. I mentioned it just as an option in case the python script could not be started to consume samples fast enough for the regular pipe not to become full (8kB?) and block the alsa chain.

Code:
file "! your_script.py -c %c -b %b -f %f"

If the pipe in popen gets full before your_script.py starts reading from it, I would use a simple script instead

Code:
file "! helper_script.sh %c %b %f"

with

Code:
buffer | your_script.py -c $1 -b $2 -f $3

By default buffer uses up to 1MB circular buffer which is plenty for this purpose.
 
These lines from the documentation were confusing:
# Output filename (or shell command the stream
# will be piped to if STR starts with the pipe
# char).
I used to use '|' as pipe character not '!'.

1. Right now if I use ALSA File plugin and I don't start PeppyMeter before starting media player (mpd, vlc or mplayer) then the latter will fill up the buffer and it will be blocked. As a result there is no sound.

2. If I start PeppyMeter before player everything works fine.

What you are suggesting is to configure ALSA File plugin in such a way which will resolve issue #1. For that you are suggesting to use two scripts:
helper_script.sh - this script will be configured in plugin and it will start reading data from player into buffer (is it just reading from stdin $1 ?) and at the same time it will start main Python script which will read data from stdin where File plugin will be sending it.
Please correct me if I misunderstood your proposal.

I think it should work but starting PeppyMeter before player is easier IMHO. For example I use the same code from PeppyMeter as VU Meter screensaver in Peppy Player. To avoid issue #1 I just start pipe reader before starting player. That solves the issue. That reader is always running while player is running.

Probably the best way to fix issues would be to make File plugin working the same way MPD is working with pipe. And another way as I mentioned already would be to write own plugin. I would also prefer to refrain from writing any C/C++ code and do that only if it's really not possible to solve the issues in Python.
 
I am sorry, of course it should be | instead of ! :) Stupid me.

There are many ways, as always, fortunately.

The problem with starting the reader before the stream starts is that you do not know in advance what format you will be receiving. You can make the format fixed in a special branch of your .asoundrc and split the stream into two - one stream going unchanged to the soundcard, the other one with fixed bit width/format feeding your meter.

Or file plugin can start the reader each time the stream is opened/started with stream-specific params. The reader will use these params to read the incoming stream correctly.

Perhaps I would do it like this:

1) Continuously running GUI python script, reading 16/32bit integers from a named pipe FIFO. First byte - right channel value to display, second byte - left channel value


2) Python script started by the file plugin in piping mode, reading incoming bytes from stdin, converting to samples based on args info, calculating the averages, writing the output two-channel integers to the named pipe FIFO in non-blocking mode python Non-block read file - Stack Overflow . I would guess a single thread should be OK.

Should the script 2 fail, all it takes is just play another track and it will be started again. If the GUI script 1 fails, no problem since script 2 will just skip writing to the full FIFO (due to the nonblocking mode).

Of course the best way would be implementing a non-blocking option in the alsa file plugin :)
My 2 cents.
 
Last edited:
I will collect a lot of money from your 2 cents :)

As I mentioned already I use the same PeppyMeter code for VU Meter screensaver in Peppy player. In that player you can switch from Internet radio to audio files, from audio files to audiobooks etc. The sound parameters (rate and size) can be different in each case. Therefore it's hard to use File plugin. I'm not sure if it invokes script each time the sound stream parameters change. MPD has 'format' parameter for pipe audio_output. So you can define fixed rate/size and it looks like MPD will transcode signal into the signal with defined parameters. This solves the issue with arbitrary sound parameters.

As for the File plugin I believe you described a working solution. As far as I understand you are suggesting to use two pipes - one for the File plugin and the other to feed GUI. One Python process could read the first pipe, make all necessary calculations and send just resulting VU levels to another pipe. That should work if File plugin restarts script with new parameters when sound parameters change.

I agree that changing pipe opening mode from blocking to non-blocking would be the best solution. It should also properly handle buffer overflow. But ALSA user base is so large that any minor change can affect too many people. One way could be just to make a custom build of that plugin.

Thanks!
 
The sound parameters (rate and size) can be different in each case. Therefore it's hard to use File plugin. I'm not sure if it invokes script each time the sound stream parameters change.

An open alsa device cannot change parameters. For that the player must close and reopen the device, configuring the parameters first.

Each plugin in alsa chain will be called for every alsa "event" - open device, close device, write data etc. In its open-device "hook" the file plugin calls popen syscall popen(3) - Linux manual page which will start the process specified in configuration and return pipe to its stdin. Into this pipe the file plugin writes samples in the write "hook". In close-device hook the plugin closes the pipe, after which the python reader will receive EOF while reading the from pipe and should quit nicely.

Therefore, yes, the file plugin starts the script each time the device is opened by the player, typically every track. Each track can have different params but if you let the file plugin substitute the script args with the actual values of the stream, as described in the posts above, your python code will know the stream params and can handle the incoming bytes accordingly.


MPD has 'format' parameter for pipe audio_output. So you can define fixed rate/size and it looks like MPD will transcode signal into the signal with defined parameters. This solves the issue with arbitrary sound parameters.

You certainly can force MPD convert any input into fixed output. But why resample a rate your soundcard can handle. Perhaps rate is not relevant for your script, but even sample width can change (some soundcards support two different sample widths, e.g. Intel HDA).

It is true that you can force MPD output single sample width (e.g. 32 bits) and no data loss will occur. That would let you ignore the params from file plugin. I personally would make the script be more fault-proof and accept various sample widths, even if they cannot occur in your particular setup.

As for the File plugin I believe you described a working solution. As far as I understand you are suggesting to use two pipes - one for the File plugin and the other to feed GUI. One Python process could read the first pipe, make all necessary calculations and send just resulting VU levels to another pipe.

Yes, the first pipe is blocking (unless file plugin is modified), but the second pipe used from python would be in nonblocking mode - that is what you need to avoid stalling the playback chain.

I agree that changing pipe opening mode from blocking to non-blocking would be the best solution. It should also properly handle buffer overflow.

That is the natural outcome of the nonblocking mode.

But ALSA user base is so large that any minor change can affect too many people. One way could be just to make a custom build of that plugin.

The nonblocking mode would be activated with an optional plugin parameter. Just finding someone to code it. Honestly, this time I do not volunteer, I have too many other undergoing projects to finish :)
 
MPD supports multiple audio outputs with different parameters. I think pipe 'format' parameter should not cause transcoding for all other outputs. It can be used just for VU Meter where you care only about signal amplitude and don't care about sound quality.

I will try the approach with two pipes/Python processes whenever I'll start working on new version. I'll also check if I can modify/rebuild ALSA File plugin. Need to finish some on-going projects too :) New version of one of them (Peppy player) I hope to release this weekend.

Thanks for interesting discussion!
 
Just to recap the discussion - PeppyMeter should work fine with MPD if you define all required parameters in mpd.conf file:
mpd * project-owner/PeppyMeter.doc Wiki * GitHub

In case of any other player (e.g. vlc, mplayer etc) you need to configure ALSA File plugin:
mplayer * project-owner/PeppyMeter.doc Wiki * GitHub
and start PeppyMeter before player. If you start player first it will not play anything as it will be blocked. Also in this use case PeppyMeter can have issues playing audio signal other than 16 bit 44.1K as it currently doesn't handle other formats properly. I hope to fix that in the next release.
 
This post is just to revive the old didcussion and consider some new "pure" ALSA approaches.
To remind the issue - Peppy Meter is currently using the following configuration in .asoundrc file:
pcm.!default {
type file
slave.pcm "hw:0,0"
file /home/pi/myfifo
format raw
}
This is so called ALSA file plugin:
ALSA project - the C library reference: PCM (digital audio) plugins
It allows to send the signal to the sound card (slave) - "hw:0,0" and to the named pipe
at the same time - /home/pi/myfifo

The signal from the named pipe is used by Peppy Meter to show volume levels. The problem
is that input signal can be in different rate and size. Peppy Meter is not clever enough (and probably
should not be) to detect that rate and size. Therefore there is the need to convert the signal into
some fixed rate/size before sending it to the file plugin. The question is how to do that using just
ALSA plugins?

Thanks!
 
Does your asoundrc with file plugin work OK, is the problem only with various rates? Or do you experience too large latency of peppy meter, or sound interruptions when peppy meter stops (the pipe is full and stops the writing thread)?

The reason I am asking is which complexity your case will need. Adding plug plugin with rate specification to your asoundrc is trivial. snd-aloop with resampling is a bit more complicated but would solve the latency problem (if any) and the lockup problem (if any).
 
Let's exclude mpd player from the picture as it has its own way of handling named pipes.

Then for mplayer and vlc players that .asoundrc file works but with some issues. vlc
outputs only one channel and to compensate that in UI I just duplicate the working channel.
So it's basically mono in UI. The signal level in fifo depends on vlc volume level. This issue
not the case for mpd when it's using its own file audio_output. I described vlc issues here:
vlc * project-owner/PeppyMeter.doc Wiki * GitHub

mplayer outputs stereo and signal level in fifo also depends on mplayer volume level:
mplayer * project-owner/PeppyMeter.doc Wiki * GitHub

In all cases (except mpd) the pipe reader (PeppyMeter) should be started before player.
Otherwise player will crash. We discussed this issue already in this thread.

I suspect that all issues which I described above for vlc caused by different input signal format.
There are no such issues in case of mpd because when you configure file ouput in mpd you can
define signal format. Therefore it always comes to PeppyMeter in the same format 44100:16:2
mpd * project-owner/PeppyMeter.doc Wiki * GitHub

That's why I thought that just specifying uniform output from file plugin could solve the issues.

Sometimes I expirienced the latency but I believe that's mostly the question of tuning such
parameters as buffer size and polling interval on PeppyMeter side.
 
Then for mplayer and vlc players that .asoundrc file works but with some issues. vlc outputs only one channel and to compensate that in UI I just duplicate the working channel.

If vlc output only one channel, you would hear only one channel in your speakers. I do not think this is the case. IMO vlc outputs a different format than mplayer (e.g. S32_LE vs. S16_LE) and your reading thread does not parse the incoming stream correctly. VLC can output any format your soundcard supports.
Current format entering your soundcard driver is listed in /proc/asound/card0/pcm0p/sub0/hw_params when the device is opened (i.e. playing). Since your slave device is the directly the driver (hw:0,0), the same stream sent to the driver is copied to the FIFO, no other conversions.

In alsa you can configure fixed format - look at plug plugin. If you extract the file plugin into a separate stream branch using plugin multi (look at e.g. Output to Multiple Audio Devices with Alsa - Eliot Eshelman for syntax of the multi plugin, slaves.a/b branches, their pcm names and bindings routing the channels) , you can fix the rate and format in the file branch - just specify required rate and format. Play with it a bit, the correct syntax is sometimes a bit tricky.

That way alsa will always reformat and resample the stream for your FIFO (slaves.b) while the stream for your soundcard (slaves.a) will not be affected.

Resampling algorithm can be specified with defaults.pcm.rate_converter alsa-plugins-rsound/samplerate.txt at master * Themaister/alsa-plugins-rsound * GitHub . Look at this Talk:Advanced Linux Sound Architecture - ArchWiki , IMO you would be fine with "linear" . Please note - the configured algorithm will be used anywhere in alsa-lib (for your user), do not use dmix or plug plugin in your chain for the soundcard since the respective resampling would use the poor quality algorithm for listening.


My opinion:

As you noticed FIFO must be emptied regularly or the whole chain gets stuck. Also you cannot control latency easily since fifos are involved.

Personally I would use the alsa virtual soundcard loopback aloop instead. Here is a simple example Capture/ALSA – FFmpeg . Into the loopin section I would add rate and format specifiers of the slave (for details see ALSA project - the C library reference: PCM (digital audio) plugins ) + specify the linear resampling algorithm. Latency would be controlled by specifying buffer_time / period_time in the slave section https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html#pcm_plugins_slave . Again the aforementioned hw_params proc file will give you current settings, you can check your config has the desired effect.

In python I would not read from FIFO, but use the simple pyalsa library http://larsimmisch.github.io/pyalsaaudio/ , example of capture being https://github.com/larsimmisch/pyalsaaudio/blob/master/recordtest.py . Pyalsa is available through pip/pip3 and works OK, I use it for controlling a volume mixer in my project.

Rate and format params should be the same as in the asoundrc config, otherwise the plug plugin in the loopout device configuration will have to kick in and resample/reformat as requested. Then you can skip the plug plugin alltogether and open "hw:Loopback,1,0" directly in the device param of

Code:
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, device=device)

Since the incoming part of loop reformats/resamples to fixed values, you do not need the second plug plugin in loopout and request these fixed values in python directly.

Also I would use blocking read alsaaudio.PCM_NORMAL to avoid the CPU-consuming polling and let the snd-aloop module wakeup my reading thread when 1 period_size of data is available.
 
Another alternative could be Pyaudio which is a binding for cross-platform PortAudio library. That could also allow to make PeppyMeter available on Windows platform as well. This would be helpful as Windows is my main development platform. Also VLC is using PortAudio and Google Voice Assistant as well. So I hope that I just need to install the Python binding (Pyaudio).
 
I thought they just wrote custom ALSA plugin. Where do you see the native API?
You are right, the question of delivering that signal to Python program is still open.

'ameter' ALSA plugin shows VU meter UI using SDL. As you probably know Pygame
library is a wrapper for SDL. So, I'm thinking to modify that 'pivumeter' plugin and add
one more output "device" - 'SDL event' and subscribe to that event in a Python code
using Pygame. The frequency of that event should not be more than 100 Hz which is
regular display refresh rate. That would be the best way IMHO.