pimoroni / pivumeter

ALSA plugin for displaying VU meters on various Raspberry Pi add-ons - derived from ameter
GNU General Public License v3.0
59 stars 22 forks source link

Support for X ( please read if you want support for a new device ) #20

Closed Gadgetoid closed 1 year ago

Gadgetoid commented 6 years ago

C-specific modes for Pi VU Meter are considered EOL- there wont be any new device support built-in. However, the library is still transitioning to a socket based method of communication where a Python script will be responsible for displaying VU data on the output device.

Currently the following output devices have been requested. I'm keen to see Python implementations of these submitted to this repository once #15 is merged, but I wont necessarily have the time to do these myself.

For the time being, if you have a request please post it below. Thank you!

roblan commented 6 years ago

Could you give some more info how to? Doing implementation based on existing ones seems to be pretty straightforward, but a little more info about arguments passing to display_vu and display_fft would be useful (or maybe link to some site that could clarify them a little) - max values, why they are 17 bins (?) and what value of each of them “mean”? What frequencies does each bin then represent?

Gadgetoid commented 6 years ago

Good point! Let's get the "what" out of the way first, then I'll deal with the "how" in documentation and, briefly, in another post here:

A lot of the choices are fairly arbitrary, subjective, and based around borrowed code so it would be difficult to rationalise them without making stuff up off the top of my head.

The magic 17 number of FFT bins appears to stem from trying to hit an arbitrary 20 int packet size. Since I wanted a low overhead on the protocol (both in throughput, and complexity), I chose fixed-sized packets rather than coming up with packet framing that would allow them to be variable.

Therefore, 17 is simply the number of bins you can fit into a 20 int packet (presumably 80bytes although I should be more specific about datatype in my code) after reserving space for the Left/Right audio channel meter levels and the count of bins to expect.

https://github.com/pimoroni/pivumeter/blob/e92b53b1f9a9a2769ddfe48d6a3d6a68bbc09f7d/src/devices/socket.c#L156-L168

It's actually possible for Pi VU Meter to send more bins, or no bins at all, and the Python code will use this count to figure out how big the packet size should be:

https://github.com/pimoroni/pivumeter/blob/e92b53b1f9a9a2769ddfe48d6a3d6a68bbc09f7d/python_server/library/pivumeter/__init__.py#L109-L116

So yeah, the madness begins to unfold!

From what I recall, fftw accepts a buffer of samples n, and returns a series of complex numbers n/2 + 1 which are scaled from DC or 0hz to the Nyquist frequency.

A magnitude is then calculated from these complex numbers, and scaled against fft_max which is a simply monumental series of magic numbers which I don't fully comprehend . I believe they are used to convert the magnitude from a raw, unfeeling number into a scaled value (0.0 to 1.0) that represents the perceived loudness of that particular frequency band to the human ear.

In the case of Pi VU Meter it accepts 1024 samples, so it should in theory output 513 bins. I have selected 85 of the lower end of those frequencies (low frequencies are much more perceptually relevant in a VU visualisation) and picked the highest magnitude from each group of 5 to produce 17 bins that are- basically- the culmination of me eyeballing (and earing) the fft output until it looked "good enough" to me.

Since this takes us from 0 to 1/6th the Nyquist frequency, and (I believe) the sample rate is 44100hz that means, I guess, that the bins represent (approximately) 0hz to 3675hz with about 216hz per bin. (the Nyquist frequency is just a fancy way of saying "half the sample rate")

There are a great many caveats to this- I don't fully understand any part of this intricate audio puzzle. I've simply tweaked things until "it looks about right."

Gadgetoid commented 6 years ago

At this point I recall that the magic number 17 is not so magic after all- it's simply the number of LEDs wide that Scroll pHAT HD is.

17 is a good number, though, since it's enough bins for all of our flashy light display products, even if you might have to discard or merge one or two sometimes. You don't really gain much from more bins, since human perception of sound, and expectations about how a meter should respond visually limit the frequencies that make sense.

So:

display_fft()

Receives a single argument- a Python List of 17 bins scaled from 0 to 65535. (The protocol really ought to be using, leaner unsigned, explicitly 16bit ints, rather than "gimme your best int" ints)

This list, as detailed above, loosely represents the frequencies from 0hz to 3675hz in bins of around 216hz. This makes a VU meter that represents quite closely,

display_vu()

Is simpler! It receives the left/right channels which represent the highest value in the 1024 sample audio buffer at that instant.

Both the left/right values are scales from 0 to 32767. The amplitude of these values relates directly to the current ALSA volume, or volume of the audio player producing the stream so it can be tricky to treat the upper limit as the "one true divisor". I recommend you experiment with either trying to query the current volume from your source audio player/ALSA and scaling accordingly, or calculating a "sticky moving average" against which the values are scaled.

If you don't get scaling right (I made a total hash of it in all the C plugins for Pi VU Meter) then quiet songs, or low volume, will fail to register on the VU Meter at all (technically correct, but visually disappointing) and loud stuff will simply saturate it.

Anyway, I hope this satiates your curiosity and gives you some information to go on. I'd love to get some more feedback about this project, because it hasn't quite got the traction I'd hoped! - since I know next to nothing about any of the concepts I'm using here, I hoped to invoke the "someone's wrong on the internet" effect and tempt a drive-by wizard to make Pi VU Meter better.

thetechknight commented 6 years ago

Not quite sure I understand how the "socket" thing works? I dont program in C or Python for that matter. I use B4J which is a BASIC -> Java compiler, and I use Xojo which is a BASIC -> Native console executable compiler, both run perfectly on the Pi. I am trying to create an MP3/Web music player (yet another one) with either an OLED or VFD display hooked up with the GPIO to the display. I know how to write the code to send graphics and data to the display, but I am on the hunt of trying to find a library that not only plays the audio, but does the VU return as well. I can draw the bars myself on the display, I just need the values. So far, no luck on finding that. Xojo has a built in sound player library but it only plays with no feedback, or duration/position, volume, etc. B4J or Java has the AudioSystem library that gives me full control, as well as possibly using volumio for Pi. Problem is, again, volumeio has no VU meter support over its "websocket" protocol.

So that makes me wonder, since this library is a VU/FFT library, how would I tie this into my program? or is it a background daemon that runs and I just query/stream the data based on the audio being monitored?

Gadgetoid commented 6 years ago

This is actually based on "ameter", a demo plugin for ALSA that displays a desktop VU meter. The trouble with that is it seems like a really terrible idea to have an audio plugin tightly coupled to a desktop GUI.

My original design- having Pi VU Meter update various display products directly- was something of a recreation of this bad design pattern. The "socket" mode aims to fix this.

"socket" is simple enough- it's just a node on the filesystem via which two applications agree to communicate. Your "server" application (the one which handles displaying the FFT/VU data) must create the node "/tmp/pivumeter.socket" as a UNIX socket. When audio is played, and Pi VU Meter is invoked it opens a connection to that socket, and begins streaming VU and FFT data to it.

You grab the data header (12 bytes, representing 3 ints) which contains the Left VU Meter, right VU Meter and FFT bin count. Then you use the FFT bin count (bin count * 4 for int) to determine how many bytes of data you need to grab until you've got the full data frame.

I actually need to spend some time changing this protocol a little bit to make it more robust and easier to sync up. Also 4 byte integers are generously over capacity for the data involved, I need to switch to "uint16_t" (2 bytes) which will serve to halve the bloat.

Pi VU Meter may or may not be the answer for you.

roblan commented 6 years ago

I've created 'support file' for 3D Xmas Tree from The Pi Hut (or just started to try to create such thing :) ) https://gist.github.com/roblan/1a373b5a3414a464b53a140f55dab058

Maybe there should be a place in readme to feature 3-rd party support files :)

onatm commented 4 years ago

I know this is a pretty old issue but I'll try my luck.

Is there any plans to support LED shim? I can try to implement it by myself but I don't have enough experience with electronics to achieve that.