Skip to content
This repository has been archived by the owner on Jan 13, 2023. It is now read-only.

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

Closed
Gadgetoid opened this issue Nov 20, 2017 · 7 comments
Closed
Assignees
Labels

Comments

@Gadgetoid
Copy link
Member

Gadgetoid commented Nov 20, 2017

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!

@Gadgetoid Gadgetoid self-assigned this Nov 20, 2017
@roblan
Copy link

roblan commented Nov 21, 2017

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
Copy link
Member Author

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.

static void update(int meter_level_l, int meter_level_r, snd_pcm_scope_ameter_t *level){
int packet_size = 20;
int fft_bin_count = packet_size - 3;
int data[packet_size];
memset(&data, 0, sizeof(int) * packet_size);
data[0] = meter_level_l;
data[1] = meter_level_r;
data[2] = fft_bin_count; // Number of FFT bins to expect (data length - 3)
generate_fft(level, &data[3], fft_bin_count);
write(socket_fd, (const void *) & data, sizeof(int) * packet_size);
}

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:

if bin_size > 0:
data = self.request.recv(bin_size * 4)
if len(data) < bin_size * 4:
raise ValueError("Insufficient FFT bin data")
# Unpack <bin_size> longs into our FFT bins
fft_bins = list(struct.unpack("<" + "L" * bin_size, data))

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
Copy link
Member Author

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
Copy link

thetechknight commented Nov 25, 2017

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
Copy link
Member Author

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
Copy link

roblan commented Nov 25, 2017

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
Copy link

onatm commented Feb 24, 2020

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

4 participants