Jump to content

Sound pressure level spectrum callback


Recommended Posts

Ciao a tutti,

I'm preparing some test code for the Sound Pressure Level Bricklet, after I purchased it today.

Would it be possible to get an example (C/C++) on how to use the tf_sound_pressure_level_register_spectrum_low_level_callback?
Your API documentation doesn't show how to use it and I can't find anything related to that (neither in your forum nor in your GitHub repos).

Does the "spectrum_chunk_offset" indicate which part of the spectrum, that is which 30 (of the 512 at max) bins the callback has shipped?
Say spectrum_chunk_offset is 0 (what I assume for the first chunk of data): do the 30 bins correspond to bins 1 to 29 (the first it reserved for the DC offset)?
And, say spectrum_chunk_offset would be 1 (or 30?): does it mean the data corresponds to bins 30 to 59
?

It would also be great to see an example of the tf_sound_pressure_level_get_spectrum function in your documentation, although its usage is clear.

One more question: your Brick Viewer screenshot of the spectrum (on the Bricklet website) shows the decibel value above the spectrum. The decibel value (69 dB in that case) is close to the highest peak of the spectrum. If I want to know both parameters, that is the current sound pressure level and the spectrum, is the current decibel value included in the spectrum as its maximal value?

Can I use both callbacks at the same time (tf_sound_pressure_level_register_decibel_callback and tf_sound_pressure_level_register_spectrum_low_level_callback)?

Cheers,
Claudio

  • Like 1
Link to comment
Share on other sites

The _*_low_level_* functions are used internally to implement the public streaming-functions like tf_sound_pressure_level_get_spectrum and the callback for SOUND_PRESSURE_LEVEL_CALLBACK_SPECTRUM.

So please use either tf_sound_pressure_level_get_spectrum or sound_pressure_level_set_spectrum_callback_configuration together with the SOUND_PRESSURE_LEVEL_CALLBACK_SPECTRUM.

You can use the decibel callback and the spectrum callback at the same time. The decibel value is not directly included in the spectrum (it can in theory be calculated from the spectrum though: https://github.com/Tinkerforge/sound-pressure-level-bricklet/blob/master/software/src/pcm.c#L122).

Link to comment
Share on other sites

Posted (edited)

Ok thanks!

Sorry, I thought the C/C++ and C/C++ bindings for microcontrollers would have the same methodology (just different function names) for the callbacks.
I'm actually trying to set the sound pressure level spectrum callback on your ESP32 Ethernet Brick using your bindings.

In the corresponding section on your website, it is documented that " []... is the period with which the Spectrum Low Level callback is triggered periodically". And the "Spectrum Low Level" links to the tf_sound_pressure_level_register_spectrum_low_level_callback. This is why I was wondering how the low level callback works.

So I understand that the uc bindings are working differently. I use the tf_sound_pressure_level_get_spectrum() to get the frequency spectrum. Perfect!

Cheers

Edited by cl-
Link to comment
Share on other sites

Ah this is a bug in the microcontroller bindings documentation. It looks like high-level callbacks are not documented yet, but you can actually register the high-level callback with

int tf_sound_pressure_level_register_spectrum_callback(TF_SoundPressureLevel *sound_pressure_level, TF_SoundPressureLevel_SpectrumHandler handler, uint16_t *spectrum_buffer, void *user_data)

In contrast to the "normal" C/C++ bindings, you have to pass a buffer as the spectrum parameter that is big enough to hold one complete spectrum (so the required size depends on what you've passed to set_configuration) This buffer is used to accumulate the spectrum values (basically the same way as get_spectrum() works internally).

Link to comment
Share on other sites

Posted (edited)

Thank you very much!

Just one more question to understand the spectrum callback in the uc binding in more detail:

To fill up the spectrum buffer using the tf_sound_pressure_level_register_spectrum_callback, one has to define a handler even if it can be an empty function?
What if I don't have any user data that needs to be input into the callback handler? Should I define the handler in any case? Or is there another approach?

Background:
I tried to use NULL as handler, because I thought the buffer and handler are given to the callback separately and the spectrum data is being filled up (independent of my callback function handler ever touching the buffer). However, given no handler (NULL) doesn't work.

// register spectrum callback to function spectrum_handler
tf_sound_pressure_level_register_spectrum_callback(&bricklet_spl,
                                                   NULL, // <- that does not work
                                                   spectrum_buffer,
                                                   &spl_data);

By scanning your code (bricklet_sound_pressure_level.c, uc bindings) I see that there is a difference between line 1421

sound_pressure_level->spectrum_handler = handler;

and line 1390

sound_pressure_level->spectrum_low_level_handler = handler;

I understood that if the handler is not present (NULL), the low level stuff is not being done (that is streaming out the data).

So it seems that I have to use an empty handler just to make sure the buffer is filled with the current spectrum data, right?

// callback function for the sound pressure level spectrum callback
void spectrum_handler(TF_SoundPressureLevel *device,
                      uint16_t spectrum_length,
                      uint16_t spectrum_chunk_offset,
                      uint16_t spectrum_chunk_data[30],
                      void *user_data) {
  // avoid unused parameter warning
  (void)device;

  // receive user data as SplData
  struct SplData *spl_data = (struct SplData *) user_data;

  // nothing to do here
}

// register spectrum callback to function spectrum_handler
tf_sound_pressure_level_register_spectrum_callback(&bricklet_spl,
                                                   spectrum_handler // <- that works, even if the function body is empty,
                                                   spectrum_buffer,
                                                   &spl_data);

 

I am currently postprocessing the spectrum data (getting the ten highest peaks, converting to db(A), etc.) outside the handler function and it works as expected.

I just want to make sure I understand your bindings properly so I don't do functions calls that are actually not required to get the same "result".

 

Thanks a lot and cheers,
Claudio

Edited by cl-
Link to comment
Share on other sites

NULL is the marker that no handler is registered, so yes, you have to register an empty handler in order for the bindings to run the callback payload reassembly (i.e. the low level stuff ;) )

51 minutes ago, cl- said:

What if I don't have any user data that needs to be input into the callback handler?

You can pass NULL as user data.

51 minutes ago, cl- said:

I am currently postprocessing the spectrum data (getting the ten highest peaks, converting to db(A), etc.) outside the handler function and it works as expected.

This is dangerous: If your post processing takes too long, the next spectrum can already be transmitted. So this would either block the bricklet (if you do this in the thread/task that you run the tick/other bindings functions in) or (if this runs concurrently) the spectrum data is overwritten while you post process it.

The safe way to do this is to either run the post processing in the callback handler or use memcpy etc. to pull the data out.

Basically the callback handler signals that it is now safe to access the passed in buffer and if you leave the callback handler, the bindings assume that they are allowed to continue using the buffer as necessary.

Another thought: If you are only interested in some of the spectrum data, for example the highest peaks, you can use the low_level callback directly and then don't have to allocate a potentially big buffer. (This is especially helpful if one wants to use the bindings on the more limited platforms such as ATMega microcontrollers). Of course you then would have to use the low level chunk variables to determine in what bin you currently are, etc.

Link to comment
Share on other sites

On 8/2/2022 at 4:05 PM, rtrbt said:

This is dangerous: If your post processing takes too long, the next spectrum can already be transmitted. So this would either block the bricklet (if you do this in the thread/task that you run the tick/other bindings functions in) or (if this runs concurrently) the spectrum data is overwritten while you post process it.

The safe way to do this is to either run the post processing in the callback handler or use memcpy etc. to pull the data out.

That's a very good point. I adapted my code so it can be safely executed on multi-core platforms as well.

On 8/2/2022 at 4:05 PM, rtrbt said:

Another thought: If you are only interested in some of the spectrum data, for example the highest peaks, you can use the low_level callback directly and then don't have to allocate a potentially big buffer. (This is especially helpful if one wants to use the bindings on the more limited platforms such as ATMega microcontrollers). Of course you then would have to use the low level chunk variables to determine in what bin you currently are, etc.

I will look into that for sure. At the moment though, to get the (say 10) highest peaks of the full spectrum, I have to collect all peaks anyway.

 

Thanks a lot for the valuable input!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...