How to write a wrapper?

Started by Midimaster, April 01, 2021, 14:59:29

Previous topic - Next topic

Midimaster

#30
Hi henry,

thank you for your reply. I try to learn all these specialities of C related to pointers. And sometime I think I did understand it, but...

String-related C-calls

I tried this and it worked:
CallString(char str[]) {
printf("space of array= %d elemente\n",sizeof(str));
printf("real len of array= %d elemente\n",strlen(str));
printf("STRING= |%s|\n", str);
return bbStringFromUTF8String(str);
}



Also this works. Send a string, maniplate it and return it:
CallString(char str[]) {
char abc[100]={0,0,0,0,0,0,0,0,0,0,0,0,0};
strcpy(abc,str);
printf("STRING= |%s|\n", abc);
return bbStringFromUTF8String(abc);
}





But main target is still to store pointers a third party function returns....


MiniAudio.h function I want to access:
//miniaudio.h line 03320
typedef struct ma_device_config     ma_device_config;

//miniaudio.h line 03359
struct ma_device_config
{
    ma_device_type deviceType;
    .....
};

// miniaudio.h line 32823
extern ma_device_config ma_device_config_init(ma_device_type deviceType)
{
    ma_device_config config;
    MA_ZERO_OBJECT(&config);
    config.deviceType = deviceType;
    ....
    return config;
}


My target is still to store in BlitzMax a pointer from a given function.
Only to be able to send it again to another given C-function to continue processing.
As I want to understand how to write a "wrapper", there is no need for processing the content in BlitzMax

I want to store the RAM area returned by config


Return a pointer of a given ma_device_config to BlitzMax

Now I try to understand COLs function in post#26
Quote from: col on April 03, 2021, 17:37:27
// my glue function:
struct ma_device_config *TestStart_2(int x) {
        // we'll create a copy for BMax
        struct ma_device_config* instance;

struct ma_device_config loc;
loc= StartDevice(1);

        instance = (stuct ma_device_config*)malloc(sizeof(ma_device_config));
        *instance = loc;
return instance;
}


I found some typos, and now it works:

typo #1: 
instance = (stuct ma_device_config*
stuct instead of STRUCT

typ0 #2:
instance = (stuct ma_device_config*)malloc(sizeof(ma_device_config));
sizeof(ma_device_config...) STRUCT is missing?


Now I have  two functions:

typedef enum
{
    ma_device_type_playback = 1,
    ma_device_type_capture  = 2,
    ma_device_type_duplex   = 3,
    ma_device_type_loopback = 4
} ma_device_type;


struct ma_device_config
{
    ma_device_type deviceType;
    int sampleRate;
    //.....
};



// given MiniAudio.h function:
struct ma_device_config StartDevice(ma_device_type deviceType) {
struct ma_device_config config;
config.deviceType=1;
config.sampleRate=12345;
return config;
}


// a working glue function only access the allocated RAM:
struct ma_device_config *TestStart1(int x) {
printf("test1\n");
struct ma_device_config *loc;
int length = sizeof(struct ma_device_config);
loc= (struct ma_device_config*) malloc(length);
printf("Value= %i \n", loc->sampleRate);
return loc;
}


// now also working glue function calling the StartDevice():
struct ma_device_config *TestStart2(int x) {
// we'll create a copy for BMax
printf("test2\n");
        struct ma_device_config *instance;
struct ma_device_config loc;

loc= StartDevice(2);
    printf("Value= %i \n", loc.sampleRate);

instance = (struct ma_device_config*) malloc (sizeof(struct ma_device_config));
*instance = loc;
printf("Value= %i \n", instance->sampleRate);
return instance;
}




...on the way to Egypt

Henri

QuoteI want to store the RAM area returned by config
This principle was shown in my book example at the first page. You can create a config object inside a function with a return type of a config *. Function inside Blitzmax has a return type of byte ptr that you can store in a field of your own custom type (probably named TMiniaudio or something) and later use that byte ptr in a different function. You can cast that byte ptr back to config * in another C function.

Note that everything you write in C should be in your own (glue) file. Better not alter original miniaudio header/c-files.

-Henri
- Got 01100011 problems, but the bit ain't 00000001

Midimaster

#32
thanks Henry. Your book example was at that time to "heavy stuff" for me. I did not understand it. My problem is, that all this C-pointer-stuff is new to me. And the combination with the need of interaction to BlitzMax make it again morre complicate. but I read every day the thread again from beginning to see, what I can understand and use now. The more I learn the more I understand.
Quote from: Henri on April 06, 2021, 14:42:17
...Note that everything you write in C should be in your own (glue) file. Better not alter original miniaudio header/c-files.

-Henri

That is what I want to do: Remain the MiniAudio.h as it is and add my own GLUE functions as the wrapper.

Right in this moment I was able to access the StartDevice() without changing anything in it. So thank you to all and your patience. A first step is done.

The example main() function has several steps. Now one code line is done:
    int main()
    {
        ma_device_config config = ma_device_config_init(ma_device_type_playback);
...



The next code lines look like being easy:
    int main()
    {
...
        config.playback.format   = ma_format_f32;   // Set to ma_format_unknown to use the device's native format.
        config.playback.channels = 2;               // Set to 0 to use the device's native channel count.
        config.sampleRate        = 48000;           // Set to 0 to use the device's native sample rate.


...on the way to Egypt

Midimaster

#33
Now I'm able to get returned the MiniAudio-Device to BlitzMax as a pointer. And I'm able to set parameters in this new Device. But only with my own C-function FillValueB(), which is able to use the pointer:

Code (BlitzMax) Select
SuperStrict
Import "lern.c"
Global MiniAudio:Byte Ptr=GetDevice(1)

CheckRam()
SetDevice MiniAudio, 16, 2, 44100
CheckRam()

Extern
Function GetDevice:Byte Ptr( DeviceTup:Int ) ="StartDeviceGlue"
Function SetDevice( DeviceRAM:Byte Ptr, format:Int, Channels:Int, Hertz:Int ) ="FillValueB"
End Extern

Function CheckRam()
Local t$
For Local i%=0 To 15
t$=t$+ Right("   " + Miniaudio[i],4)
If i Mod 4=3
t$=t$+" |"
EndIf
Next
Print "-----------------------------------------------------------------------"
Print t
Print "-----------------------------------------------------------------------"
Print
End Function


and the "lern.c"
typedef enum
{
    ma_device_type_playback = 1,
    ma_device_type_capture  = 2,
    ma_device_type_duplex   = 3,
    ma_device_type_loopback = 4
} ma_device_type;

struct ma_device_config
{
    ma_device_type deviceType;
    int sampleRate;
    int channels;
    int format;
    //.....
};

// given MiniAudio.h function:
struct ma_device_config StartDevice(ma_device_type deviceType) {
printf("START_DEVICE \n");
struct ma_device_config config;
config.deviceType=deviceType;
config.sampleRate=222;
return config;
}

// Start Device Glue:
struct ma_device_config *StartDeviceGlue(int DeviceTyp) {
// we'll create a copy for BMax
printf("START_DEVICE_GLUE \n");
struct ma_device_config *instance;
int length = sizeof(struct ma_device_config);
instance = (struct ma_device_config*) malloc(length);
*instance= StartDevice(1);
printf("set sample rate= %i \n", instance->sampleRate);
return instance;
}

// Device Filler:
extern void FillValueB(struct ma_device_config *config, int format, int channels, int hertz) {
printf("FILL_VALUE_B \n");
config->format=format;
config->channels=channels;
config->sampleRate=hertz;
printf("set sample rate= %i \n", config->sampleRate);
}


This shows the results:
Executing:lern.debug.exe
START_DEVICE_GLUE
START_DEVICE
set sample rate= 222
-----------------------------------------------------------------------
   1   0   0   0 | 222   0   0   0 |   0   0   0   0 |   0   0   0   0 |
-----------------------------------------------------------------------

FILL_VALUE_B
set sample rate= 44100
-----------------------------------------------------------------------
   1   0   0   0 |  68 172   0   0 |   2   0   0   0 |  16   0   0   0 |
-----------------------------------------------------------------------


Process complete



How to glue to original function?

But to provide the Device in a form the MiniAudio.h needs it, it has to be the STRUCT itself and not a pointer:

my function:
// Device Filler:
extern void FillValueB(struct ma_device_config *config, int format, int channels, int hertz) {
printf("FILL_VALUE_B \n");
config->format=format;
config->channels=channels;
config->sampleRate=hertz;
printf("set sample rate= %i \n", config->sampleRate);
}


// given MiniAudio.h function:
void FillDevice(struct ma_device_config config, int format, int channels, int hertz) {
printf("FILL_DEVICE \n");
config.format=format;
config.channels=hertz;
config.sampleRate=hertz;
printf("set sample rate= %i \n", config.sampleRate);
}



How can i "back-cast" a blitzMax-Pointer to the real STRUCT?
...on the way to Egypt

col

#34
QuoteHow can i "back-cast" a blitzMax-Pointer to the real STRUCT?

To 'back-cast' a pointer you are best to do that in the c wrapper code - it is called 'derefencing' where you use the '*' dereference operator.
You should think of it as to 'remove one level of pointer indirection' - ie it removes one '*' from the type - this is because you can have pointers to pointers to pointers etc.

Anyway... you have your Byte Ptr in BMax. Because you wrote the code to create that pointer from c code you also know that the pointer is a 'struct ma_device_config*' - ie a pointer to 'struct ma_device_config'.

In c (and cpp) to get from a 'struct ma_device_config*' back to a 'struct ma_device_config' you can use the '*' dereference operator. Example:


// The 'config' parameter comes from BMax where it is a Byte Ptr - Best to pass that Byte Ptr to the c code and dereference it properly in the c code.
void DeferencePointer(struct ma_device_config *config)
{
   // Dereference the pointer to get to the underlying value type of 'struct ma_device_config'. Removing one level of pointer indirection in this case will yield a 'struct ma_device_config'.
   struct ma_device_config ValueType = *config; // Notice the '*' before the 'config' variable name.

   // When you want to access type members (the Fields in BMax speak) of a c (or cpp) value type you use the '.' operator. Notice this is different from when
   // you are using a pointer type where you access the type members using '->'
   printf("SampleRate: %d", ValueType.sampleRate);
}




Funnily enough you already have something similar to this in your example, although admittedly, if you're not familiar with c or cpp then its not so clear as to what is really happening:

struct ma_device_config *instance;
[...]
*instance= StartDevice(1);

Remember that the StartDevice function returns a 'struct ma_device_config'.



Just a side note:
I personally write the * along with the type name such as
MyType* pMyType;
As opposed to putting the * with the variable name as
MyType *pMyType;

I do this so that the code is cleaning to read for me, plus it can help stop some potential confusion.
I would much rather prefer to see

MyType* pMyType;
[...]
pMyType->whatever = dumdum;
[...]
MyType myType = *pMyType;

than

MyType *pMyType;
[...]
pMyType->whatever = dumdum;
[...]
MyType myType = *pMyType;

Just my personal perference.

In C I think having the pointer '*' with the variable is just a standard practice. In cpp people now seem to prefer the '*' with the type name.
I've seen plenty of code that has a space either side of the '*' for the variable declaration - In Visual Studio it does this for auto code generation.
My personal take on this is 'The compiler really doesn't care so why should we? Just pick a style that you like.' :)
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Midimaster

Thanks Col. I already tried this, but it looks like it has not the effect I need.

When I replace the function..
// Device Filler:
extern void FillValueB(struct ma_device_config *config, int format, int channels, int hertz) {
printf("FILL_VALUE_B \n");
config->format=format;
config->channels=channels;
config->sampleRate=hertz;
printf("set sample rate= %i \n", config->sampleRate);
}


...with this code...
extern void FillValueB(struct ma_device_config *configPointer, int format, int channels, int hertz) {
printf("FILL_VALUE_GLUE \n");
struct ma_device_config config = *configPointer;
config.format=format;
config.channels=channels;
config.sampleRate=hertz;
printf("set sample rate= %i \n", config.sampleRate);
}


...it works without an error, but the final testing in BlitzMax says, that the function did not use the Config-RAM, but a copy.

I would expect this RAM-scan:
Executing:lern.debug.exe
START_DEVICE_GLUE
START_DEVICE
set sample rate= 222
-----------------------------------------------------------------------
   1   0   0   0 | 222   0   0   0 |   0   0   0   0 |   0   0   0   0 |
-----------------------------------------------------------------------

FILL_VALUE_B
set sample rate= 44100
-----------------------------------------------------------------------
   1   0   0   0 |  68 172   0   0 |   2   0   0   0 |  16   0   0   0 |
-----------------------------------------------------------------------


but I get this:
Executing:lern.debug.exe
START_DEVICE_GLUE
START_DEVICE
set sample rate= 222
-----------------------------------------------------------------------
   1   0   0   0 | 222   0   0   0 |   0   0   0   0 |   0   0   0   0 |
-----------------------------------------------------------------------

FILL_VALUE_B
set sample rate= 44100
-----------------------------------------------------------------------
   1   0   0   0 | 222   0   0   0 |   0   0   0   0 |   0   0   0   0 |
-----------------------------------------------------------------------
...on the way to Egypt

col

#36
That's actually correct.

This code...

struct ma_device_config config = *configPointer;

makes a copy of the derefenced configPointer because you are dealing with value types. It's a bit like saying:


struct ma_device_config config1;
struct ma_device_config config2;
config1 = config2;

So config1 will copy the data from config2 even though they are separate instances.
You can make changes to config2 and config1 will keep its values and remain unchanged.

If you want the changes to reflect via the BMax pointer then you can change the members using the 'pointer version' then do the dereference (copy) afterwards...


extern void FillValueB(struct ma_device_config *configPointer, int format, int channels, int hertz) {
printf("FILL_VALUE_GLUE \n");
configPointer->format=format;
configPointer->channels=channels;
configPointer->sampleRate=hertz;

struct ma_device_config config = *configPointer;
printf("set sample rate= %i \n", config.sampleRate);
}
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Midimaster

#37
at the moment I belive, that this is not the solution. The function FillDeviceGlue() is only a test for a glue function to the given function FillDevice() in MiniAudio.h.

And also this FillDevice() is only a example for 100 functions inside MiniAudio.h, which constantly will change the values inside this STRUCT. So I need to hold a RAM-area inside BlitzMax, to contain the complete STRUCT. And on the other side the miniAudio.h needs to access the same RAM all the time. And I need a  function to tell the 100 functions all the time, where this RAM is. So copies will not be target-oriented.

In the manual of MiniAudio.h there is a proposal for a main loop:
    void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
    {
        // In playback mode copy data to pOutput. In capture mode read data from pInput. In full-duplex mode, both
        // pOutput and pInput will be valid and you can move data from pInput into pOutput. Never process more than
        // frameCount frames.
    }

    int main()
    {
        ma_device_config config = ma_device_config_init(ma_device_type_playback);
        config.playback.format   = ma_format_f32;   // Set to ma_format_unknown to use the device's native format.
        config.playback.channels = 2;               // Set to 0 to use the device's native channel count.
        config.sampleRate        = 48000;           // Set to 0 to use the device's native sample rate.
        config.dataCallback      = data_callback;   // This function will be called when miniaudio needs more data.
        config.pUserData         = pMyCustomData;   // Can be accessed from the device object (device.pUserData).

        ma_device device;
        if (ma_device_init(NULL, &config, &device) != MA_SUCCESS) {
            return -1;  // Failed to initialize the device.
        }

        ma_device_start(&device);     // The device is sleeping by default so you'll need to start it manually.

        // Do something here. Probably your program's main loop.

        ma_device_uninit(&device);    // This will stop the device so no need to do that manually.
        return 0;
    }


and a pat of the manual:
QuoteThe configuration of the device is defined by the `ma_device_config` structure. The config object is always initialized with `ma_device_config_init()`. It's
important to always initialize the config with this function as it initializes it with logical defaults and ensures your program doesn't break when new members
are added to the `ma_device_config` structure. The example above uses a fairly simple and standard device configuration. The call to `ma_device_config_init()`
takes a single parameter, which is whether or not the device is a playback, capture, duplex or loopback device (loopback devices are not supported on all
backends).

The `config.playback.channels` member sets the number of channels to use with the device.

Note that leaving the format, channel count and/or sample rate at their default values will result in the internal device's native configuration being used
which is useful if you want to avoid the overhead of miniaudio's automatic data conversion.

In addition to the sample format, channel count and sample rate, the data callback and user data pointer are also set via the config. The user data pointer is
not passed into the callback as a parameter, but is instead set to the `pUserData` member of `ma_device` which you can access directly since all miniaudio
structures are transparent.

Initializing the device is done with `ma_device_init()`. This will return a result code telling you what went wrong, if anything. On success it will return
`MA_SUCCESS`. After initialization is complete the device will be in a stopped state. To start it, use `ma_device_start()`. Uninitializing the device will stop
it, which is what the example above does, but you can also stop the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again.

The example above demonstrates the initialization of a playback device,...



Would an experiment with  realloc() help in this case?

Or can I keep the STRUCT or a RAM-area "alive" also on the C-side?

...on the way to Egypt

col

I don't think you'll have much choice as value types are stack based - that means when the function exits then all local value types are out of scope and invalid, so you need a way to keep it alive for when the wrapper function exits - that is to create the instance on the heap.

I guess you could keep a static version of an instance in the c file - a singleton, but this means only one instance at a time. If you want more than once instance then for sure you'll be really complicating things. There's nothing wrong with doing things how I suggested? Are you worried about copies in the functions? speed? or something else?

Usually a library would use pointers for its object instances. Passing large value types around like the ma_device_type isn't exactly the most 'efficient' method.

BMaxNG has Struct... maybe someone who uses NG could see if it works when passing a Struct back and forth between C code as a return type or parameter? I would guess that as a parameter it may work but I haven't used it in a long time.
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

col

#39
I assume you don't want the copy thing going on?
You could wrap the c value type 'ma_device_config' in a container, make an instance of the container on the heap, and use the container as a Byte Ptr within BMax.

Either way I think to keep things simple and reliable you're going to have to 'wrap' (pardon the pun  :)) ) you're head around pointers etc.

So something like this would stop prevent the copying of all the members of a value type:

BMaxTest.bmx
Code (BlitzMax) Select


SuperStrict

Import "BMaxTest.c"

Extern
Function StartDevice:Byte Ptr(DeviceType:Int)
Function FillValueB(MaDeviceConfig:Byte Ptr, Format:Int, Channels:Int, Hertz:Int)
EndExtern

Local MaDeviceConfig:Byte Ptr = StartDevice(1)
CheckRam(MaDeviceConfig)

FillValueB(MaDeviceConfig, 16, 2, 44100)
CheckRam(MaDeviceConfig)

Function CheckRam(MaInstance:Byte Ptr)
    Local t$
    For Local i%=0 To 15
        t$=t$+ Right("   " + MaInstance[i], 4)
        If i Mod 4=3
            t$=t$+" |"
        EndIf
    Next
    Print "-----------------------------------------------------------------------"
    Print t
    Print "-----------------------------------------------------------------------"
    Print
End Function




typedef enum
{
    ma_device_type_playback = 1,
    ma_device_type_capture  = 2,
    ma_device_type_duplex   = 3,
    ma_device_type_loopback = 4
} ma_device_type;

struct ma_device_config
{
    ma_device_type deviceType;
    int sampleRate;
    int channels;
    int format;
    //.....
};




// BlitzMax 'C Value Type' wrapper
typedef struct BMaxMaDeviceConfig BMaxMaDeviceConfig;
struct BMaxMaDeviceConfig
{
    struct ma_device_config config;
};

BMaxMaDeviceConfig* StartDevice(ma_device_type deviceType) {
printf("START_DEVICE \n");

BMaxMaDeviceConfig* DeviceConfig = malloc(sizeof(BMaxMaDeviceConfig));
DeviceConfig->config.deviceType = deviceType;
DeviceConfig->config.sampleRate = 222;
DeviceConfig->config.channels = 0;
DeviceConfig->config.format = 0;

return DeviceConfig;
}

void FillValueB(BMaxMaDeviceConfig* DeviceConfig, int format, int channels, int hertz) {
    printf("FILL_VALUE_B \n");
    DeviceConfig->config.format = format;
    DeviceConfig->config.channels = channels;
    DeviceConfig->config.sampleRate = hertz;
    printf("set sample rate= %i \n", DeviceConfig->config.sampleRate);
}
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Midimaster

now I decided to simpy do it this way:
// Device Filler:
extern void MySetDevice(struct ma_device_config *config, int format, int channels, int hertz) {
printf("FILL_VALUE_B \n");
config->format=format;
config->channels=channels;
config->sampleRate=hertz;
printf("set sample rate= %i \n", config->sampleRate);
}


For this case it is sufficient to work with a pointer, because this original function also only changes the values and then return without calling any other function.


but now I'm faced to another question:

Access to all these predefined values from outside

In MiniAudio.h there are hundreds of constants and enums.

examples:
typedef enum
{
    ma_format_unknown = 0, 
    ma_format_u8      = 1,
    ma_format_s16     = 2, 
    ma_format_s24     = 3, 
    ma_format_s32     = 4,
    ma_format_f32     = 5,
    ma_format_count
} ma_format;


I learned, that I can often use simple INT to use them from Outside.
Code (BlitzMax) Select
SetDevice MiniAudio, 2 , 44100
But what would be neccessary to use this symbolic values also in BltzMax?

Do I create a long table with the same values?
Code (BlitzMax) Select
Const   ma_format_unknown = 0, 
Const    ma_format_u8      = 1,
Const    ma_format_s16     = 2, 
Const    ma_format_s24     = 3, 
Const    ma_format_s32     = 4,
Const    ma_format_f32     = 5,

Code (BlitzMax) Select
SetDevice MiniAudio, ma_format_s16 , 44100

Or is there a chance to do somthing like ...
Code (BlitzMax) Select
SetDevice MiniAudio, MiniAudio.Enum.ma_format_s16 , 44100

What is the best common practice?
...on the way to Egypt

col

I modified my last post. not sure if you noticed.
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Midimaster

Quote from: col on April 08, 2021, 09:15:07



...

// BlitzMax 'C Value Type' wrapper
typedef struct BMaxMaDeviceConfig BMaxMaDeviceConfig;
struct BMaxMaDeviceConfig
{
    struct ma_device_config config;
};

BMaxMaDeviceConfig* StartDevice(ma_device_type deviceType) {
printf("START_DEVICE \n");

BMaxMaDeviceConfig* DeviceConfig = malloc(sizeof(BMaxMaDeviceConfig));
DeviceConfig->config.deviceType = deviceType;
...

return DeviceConfig;
}

void FillValueB(BMaxMaDeviceConfig* DeviceConfig, int format, int channels, int hertz) {
    printf("FILL_VALUE_B \n");
    DeviceConfig->config.format = format;
    DeviceConfig->config.channels = channels;
    DeviceConfig->config.sampleRate = hertz;
    printf("set sample rate= %i \n", DeviceConfig->config.sampleRate);
}


This could be a solution. Thank you for showing me this. This is what I meant with "keep the STRUCT on the C-side". On the BlitzMax-side there is no difference and looks clear and simple . And on the C-side the glue functions can use the same RAM all the time and I can translate a lot of MiniAudio.h code much closer to the original code.

But can I now send the config as a variable to another function?

something like:
void DeviceInitGlue(BMaxMaDeviceConfig* DeviceConfig) {
    ma_device device;
       // original code: ma_device_init(NULL, &config, &device)
    ma_device_init(NULL, &DeviceConfig->config, &device);
}


...on the way to Egypt

col

#43
QuoteWhat is the best common practice?

This is entirely up to you - as the writer of the wrapper you get to decide. In other languages such as C# you would have the MiniAudio.Format16 or similar.

When I wrapped code I would tried to keep the BMax code as close as possible to the original source so I that could refer to the original documentations - so I would use the Const approach and have huge list of them.

QuoteBut can I now send the config as a variable to another function?
You sure can.

But of course the next thing to consider is the ma_device instance - which is also a value type. If you need to keep this around too then you could wrap that in a container using the same technique as the ma_device_config wrapper and return it to BMax as a Byte Ptr again. If you create the container first then you wont have any 'copying' involved:


typedef struct BMaxMaDevice BMaxMaDevice;
struct BMaxMaDevice
{
    struct ma_device device;
};

BMaxMaDevice* DeviceInitGlue(BMaxMaDeviceConfig* DeviceConfig) {
    BMaxMaDevice* Device = (BMaxMaDevice*)malloc(sizeof(BMaxMaDevice));

    ma_device_init(NULL, &DeviceConfig->config, &Device->device);
    return Device;
}

https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

col

QuoteOr is there a chance to do somthing like ...

You can do that in BMax with using a Type and Const fields:

Code (BlitzMax) Select

Print(ma_format.ma_format_f32)

Type ma_format
    Const ma_format_unknown:Int = 0
    Const ma_format_u8:Int      = 1
    Const ma_format_s16:Int     = 2
    Const ma_format_s24:Int     = 3
    Const ma_format_s32:Int     = 4
    Const ma_format_f32:Int     = 5
EndType
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."