8

I’ve got an array of PCM data; it can be 16-bit, 24-bit packed, 32-bit, etc. It can be signed, or unsigned, and it can be 32 or 64-bit floating point. It is currently stored as a void** matrix, indexed first by channel, then by frame. The goal is to allow my library to take in any PCM format and buffer it, without requiring manipulation of the data to fit a designated structure. If the A/D converter spits out 24-bit packed arrays of interleaved PCM, I need to accept it gracefully. I also need to support 16-bit non-interleaved, as well as any permutation of the above formats.

I know the bit depth and other information at runtime, and I’m trying to code efficiently while not duplicating code. What I need is an effective way to cast the matrix, put PCM data into the matrix, and then pull it out later.

I can cast the matrix to int32_t, or int16_t for the 32 and 16-bit signed PCM respectively; I’ll probably have to store the 24-bit PCM in an int32_t for 32-bit, 8-bit byte systems as well.

Can anyone recommend a good way to put data into this array, and pull it out later? I’d like to avoid large sections of code which look like:

switch (mFormat) {
case 1:  // unsigned 8 bit
  for (int i = 0; i < mChannels; i++)
    framesArray = (uint8_t*)pcm[i];
  break;
case 2:  // signed 8 bit
  for (int i = 0; i < mChannels; i++)
    framesArray = (int8_t*)pcm[i];
  break;
case 3:  // unsigned 16 bit
...

Limitations: I’m working in C/C++, no templates, no RTTI, no STL. Think embedded. Things get trickier when I have to port this to a DSP with 16-bit bytes.

Does anybody have any useful macros they might be willing to share?

ib.
  • 27,830
  • 11
  • 80
  • 100
Griffin
  • 501
  • 6
  • 14
  • 4
    I would have thought that this is exactly the problem templates were supposed to solve. Is the "no templates" thing a limitation of your toolchain, some other restriction on the project, or just a personal preference? – Anon. Jan 10 '11 at 00:49
  • 2
    What do you do with the data? It's not clear from the question that you actually need to do the casting at all if all you do is buffer the raw data and hand it off later. – Kylotan Jan 10 '11 at 00:52
  • 2
    @Anon, given the OP doesn't even accept replicated syntax guarded by conditional branch, I would probably guess the executable size matter is raising red flag on templates. (since templates function result in bigger executables than switch...case) – YeenFei Jan 10 '11 at 01:11
  • The template restriction would have more to do with the platform and toolchain than anything else. The plan is to perform some calculations and do some signal processing with the data. I do need to be able to interpret it, I can't just pass it on. – Griffin Jan 10 '11 at 01:16
  • @YeenFei: On the other hand, ch0kee's answer could be a bit more concisely implemented with templates, without really changing the resulting executable. (Essentally, what ch0kee is doing with the FORMAT_DEFINITION macro is a C-preprocessor equivalent of simple templates, which is why the result would be the same. The function-pointer array is also something that could be generated with clever C++ template work, but it's a static array so, again, same compiled result.) – Brooks Moses Jan 10 '11 at 08:56

1 Answers1

7

This one will match typecodes to casting functions. The basic idea is that it creates a set of tiny conversion functions for each type, and an array of function pointers, and then indexes into that array based on the data format in order to find the correct conversion function to call.

Usage example:

int main ()
{   
    void** pcm;
    int currentChannel;
    int currentFrame;
    int mFormat;

    // gets data casted to our type
    STORETYPE translatedFrameData = GET_FRAMEDATA(pcm, currentChannel, currentFrame, mFormat);  

    return 0;
}

The header file:

// this is a big type, we cast to this one
#define STORETYPE int32_t

// these functions get a single frame
typedef STORETYPE (*getterFunction)(void**, int, int);

// this macros make an array that maps format codes to cast functions
#define BEGIN_RESERVE_FORMAT_CODES getterFunction __getter_array[] = {
#define RESERVE_FORMAT_CODE(code) __get__##code##__,
#define END_RESERVE_FORMAT_CODES };

//
#define FORMAT_DEFINITION(code, format) STORETYPE __get__##code##__(void**pcm, int channel, int frame) \
{ return (STORETYPE) ((format**)pcm)[channel][frame]; }

// get corresponding function 
#define GET_FRAMEDATA( pcm, channel, frame, format ) __getter_array[format](pcm,channel,frame)

//serious part, define needed types
FORMAT_DEFINITION(0, uint8_t)
FORMAT_DEFINITION(1, int8_t)
FORMAT_DEFINITION(2, uint16_t)
FORMAT_DEFINITION(3, int16_t)

//actually this makes the array which binds types
BEGIN_RESERVE_FORMAT_CODES
    RESERVE_FORMAT_CODE(0)
    RESERVE_FORMAT_CODE(1)
    RESERVE_FORMAT_CODE(2)
    RESERVE_FORMAT_CODE(3)
END_RESERVE_FORMAT_CODES

//WATCH OUT FOR SEQUENCE

hope helps

ib.
  • 27,830
  • 11
  • 80
  • 100
ch0kee
  • 736
  • 4
  • 12
  • Was it far from your question ? Is function call efficent enough on your platform ? – ch0kee Jan 10 '11 at 02:34
  • Thats... Genius. Took me a while to fully understand what you're doing here, but it _LOOKS_ upon first view to be exactly what I need. With a little fiddling, I can even use these functions to put data into the pcm matrix. This is absolutely amazing. The cool part is that this should work with the floating point types, as well as the int types. Thanks a TON! – Griffin Jan 10 '11 at 04:51
  • I edited this to add a concise description of the solution at the top, since (as Griffin notes) it's not immediately easy to understand just from the code. Hope you don't mind -- I think it's an excellent answer. – Brooks Moses Jan 10 '11 at 08:51
  • some kind of range checking during debug builds would be adviseable though – smerlin Jan 10 '11 at 09:03