Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

next step in documentation: samples #34

Open
bbbart opened this issue Apr 10, 2021 · 15 comments
Open

next step in documentation: samples #34

bbbart opened this issue Apr 10, 2021 · 15 comments

Comments

@bbbart
Copy link
Contributor

bbbart commented Apr 10, 2021

After completing the API documentation in #33 , I'd like to add some more user friendly documentation like how to get started, installation, etc...

Now, I'm a fairly new an inexperienced user of FMOD myself, so I was wondering if others might perhaps want to share some (simple or advanced) example code using the library here that I can incorporate in the documentation?

That would be great. :-)

@marcelomanzo
Copy link
Contributor

marcelomanzo commented Apr 11, 2021 via email

@bbbart
Copy link
Contributor Author

bbbart commented Apr 15, 2021

Ah yes, that's a good idea. I'll look into it. Thanks @marcelomanzo !

@bbbart
Copy link
Contributor Author

bbbart commented Apr 26, 2021

I'm stuck trying to implement the dsp_custom C++ example in pyfmodex. In particular with the initialization of dspdesc and the paramdesc argument.

Here's what I have right now:

"""Example code to demonstrate how to add a user created DSP callback to
process audio data.
"""

import sys
from ctypes import POINTER, byref, cast

import pyfmodex
from pyfmodex.callback_prototypes import (DSP_CREATE_CALLBACK,
                                        DSP_GETPARAM_DATA_CALLBACK,
                                        DSP_GETPARAM_FLOAT_CALLBACK,
                                        DSP_READ_CALLBACK,
                                        DSP_RELEASE_CALLBACK,
                                        DSP_SETPARAM_FLOAT_CALLBACK)
from pyfmodex.enums import DSP_PARAMETER_TYPE
from pyfmodex.flags import MODE
from pyfmodex.structure_declarations import DSP_STATE
from pyfmodex.structures import DSP_DESCRIPTION, DSP_PARAMETER_DESC

MIN_FMOD_VERSION = 0x00020108


mydsp_callback = DSP_READ_CALLBACK()
mydsp_create_callback = DSP_CREATE_CALLBACK()
mydsp_release_callback = DSP_RELEASE_CALLBACK()
mydsp_getparameterdata_callback = DSP_GETPARAM_DATA_CALLBACK()
mydsp_setparameterfloat_callback = DSP_SETPARAM_FLOAT_CALLBACK()
mydsp_getparameterfloat_callback = DSP_GETPARAM_FLOAT_CALLBACK()


# Create a System object and initialize.
system = pyfmodex.System()
VERSION = system.version
if VERSION < MIN_FMOD_VERSION:
    print(
        f"FMOD lib version {VERSION:#08x} doesn't meet "
        f"minimum requirement of version {MIN_FMOD_VERSION:#08x}"
    )
    sys.exit(1)

system.init(maxchannels=1)

sound = system.create_sound("media/stereo.ogg", mode=MODE.LOOP_NORMAL)
channel = system.play_sound(sound)

# Create the DSP effect.
NUMPARAMETERS = 2
wavedata_desc = DSP_PARAMETER_DESC()
volume_desc = DSP_PARAMETER_DESC()
paramdesc = cast(
    (POINTER(DSP_PARAMETER_DESC) * NUMPARAMETERS)(),
    POINTER(POINTER(DSP_PARAMETER_DESC)),
)

paramdesc[0].type = DSP_PARAMETER_TYPE.DATA
paramdesc[0].name = "wave data"
paramdesc[0].label = ""
paramdesc[0].description = "wave data"

paramdesc[1].type = DSP_PARAMETER_TYPE.FLOAT
paramdesc[1].name = "volume"
paramdesc[1].label = "%"
paramdesc[1].description = "linear volume in percent"

dspdesc = DSP_DESCRIPTION(
    name=b"My frist DSP unit",
    version=0x00010000,
    numinputbuffers=1,
    numoutputbuffers=1,
    numparameters=NUMPARAMETERS,
    read=mydsp_callback,
    release=mydsp_release_callback,
    getparameterdata=mydsp_getparameterdata_callback,
    setparameterfloat=mydsp_setparameterfloat_callback,
    getparameterfloat=mydsp_getparameterfloat_callback,
    paramdesc=paramdesc,
)
mydsp = system.create_dsp(dspdesc)

which results in a segfault. Setting the numparameters argument to 0 removes the segfault, which kind of makes sense to me. I haven't investigated much further (with strace for example), but I have tried loads of different ways of initializing paramdesc, eventually using https://stackoverflow.com/a/17102186/11455988 as a guide.

@tyrylu, or anyone else, do you have more experience with this?

@tyrylu
Copy link
Owner

tyrylu commented Apr 27, 2021

I doubt that there is anyone who tried this, but there are some obvious issues. The first are the null callbacks, they will cause a crash at some point (at least the required ones), then there's the param desc initialization, it looks really weird. I would likely do something like:
paramdesc = (DSP_PARAMETER_DESC * 2)()
Then, you to pass byte strings everywhere and raw enum values as well. I am not sure whether the parameter type specific values need to be filled, if yes, you can access them through the desc_union member. Of course, passing the description array created this way is ugly in its own right, you have to do something like:
paramdesc=pointer(cast(paramdesc, POINTER(DSP_PARAMETER_DESC)))

@bbbart
Copy link
Contributor Author

bbbart commented Apr 27, 2021

Hey, thanks for your reply.

I doubt that there is anyone who tried this, but there are some obvious issues. The first are the null callbacks, they will cause a crash at some point (at least the required ones),

Ah yes, I can see that. However, because the problem goes away when numparameters=0, I'm looking at paramdesc for the moment.

then there's the param desc initialization, it looks really weird. I would likely do something like:
paramdesc = (DSP_PARAMETER_DESC * 2)()

OK, I see. Let me try that.

I reckoned it has to be a pointer to an array of pointers (https://fmod.com/resources/documentation-api?version=2.1&page=plugin-api-dsp.html#fmod_dsp_description shows **paramdesc), but I admit I am somewhat out of my depth here.

Then, you to pass byte strings everywhere

Not everywhere. I started doing that after I realised that that's what I got back when retrieving a DSP_DESCRIPTION. Let me fiddle with that some more.

and raw enum values as well.

Ah, yes, that's a good catch. Probably the .value-call is required here.

I am not sure whether the parameter type specific values need to be filled, if yes, you can access them through the desc_union member.

Yup, got that done in the dsp_inspector sample I committed today.

Of course, passing the description array created this way is ugly in its own right, you have to do something like:
paramdesc=pointer(cast(paramdesc, POINTER(DSP_PARAMETER_DESC)))

Aha! OK, trying again tomorrow with a fresh mind. :-)

Thanks a lot for the insights. I am already thinking about have an entire (thin) layer on top of pyfmodex to make using the library feel more Pythonic. I don't think a consumer of the package should ever have to import anything from ctypes, right?

@bbbart
Copy link
Contributor Author

bbbart commented Apr 28, 2021

ok, I haven't really made progress in terms of getting any further, but I do believe my understanding of the matter has improved somewhat.

Here's a small non-working example, cleaned up keeping the latest comments in mind:


"""Example code to demonstrate how to add a user created DSP to process
audio data.
"""

from ctypes import POINTER, cast, pointer

import pyfmodex
from pyfmodex.enums import DSP_PARAMETER_TYPE
from pyfmodex.structures import (DSP_DESCRIPTION, DSP_PARAMETER_DESC,
                                 DSP_PARAMETER_DESC_FLOAT,
                                 DSP_PARAMETER_DESC_UNION)

system = pyfmodex.System()
system.init(maxchannels=1)

NUMPARAMETERS = 2
wavedata_desc = DSP_PARAMETER_DESC(
    type=DSP_PARAMETER_TYPE.DATA.value,
    name=b"wave data",
    label=b"",
    description=b"wave data",
)

volume_float_desc = DSP_PARAMETER_DESC_FLOAT(min=0)
volume_desc = DSP_PARAMETER_DESC(
    type=DSP_PARAMETER_TYPE.FLOAT.value,
    name=b"volume",
    label=b"%",
    description=b"linear volume in percent",
    desc_union=DSP_PARAMETER_DESC_UNION(floatdesc=volume_float_desc),
)

paramdesc = (DSP_PARAMETER_DESC * NUMPARAMETERS)()
paramdesc[0] = wavedata_desc
paramdesc[1] = volume_desc

dspdesc = DSP_DESCRIPTION(
    name=b"My first DSP unit",
    version=0x00010000,
    numinputbuffers=1,
    numoutputbuffers=1,
    numparameters=NUMPARAMETERS,
    paramdesc=pointer(cast(paramdesc, POINTER(DSP_PARAMETER_DESC))),
)
mydsp = system.create_dsp(dspdesc)

A strace tells me that the segfault happens on a futex call. This call is not present when numparameters=0. Don't know if that bit of information helps. :-)

I'm still out of ideas here. For the moment, I'm focusing on reimplementing the other examples from upstream.

@tyrylu
Copy link
Owner

tyrylu commented Apr 28, 2021

I still think that paramdesc[0] = wavedata_desc should be something like paramdesc[0].desc_union.datadesc = wavedata_desc + filled all the common fields and all required dsp desc fields as well.

@bbbart
Copy link
Contributor Author

bbbart commented Apr 29, 2021

Hm, I don't think that's right:

  • paramdesc is of type DSP_PARAMETER_DESC_Array_2
  • so, paramdesc[0] should be of type DSP_PARAMETER_DESC
  • which would make paramdesc[0].desc_union.datadesc of type DSP_PARAMETER_DESC_DATA
    but wavedata_desc is of type DSP_PARAMETER_DESC (and should be, I think).

The problem lies somewhere with assigning two parameter. The following works just fine:

[...]
# volume                                                                        
volume_float_desc = DSP_PARAMETER_DESC_FLOAT(min=0, max=1, defaultval=1)        
volume_desc = DSP_PARAMETER_DESC(                                               
    type=DSP_PARAMETER_TYPE.FLOAT.value,                                        
    name=b"volume",                                                             
    label=b"%",         
    description=b"linear volume in percent",                                                   
    desc_union=DSP_PARAMETER_DESC_UNION(floatdesc=volume_float_desc),           
)                                                                               
                                                                                
NUMPARAMETERS = 1                                                               
paramdesc = (DSP_PARAMETER_DESC * NUMPARAMETERS)()                              
paramdesc[0] = volume_desc                                                      
                                                                                
dspdesc = DSP_DESCRIPTION(                                                      
    name=b"My first DSP unit",                                                  
    version=0x00010000,                                                         
    numinputbuffers=1,                                                          
    numoutputbuffers=1,                                                         
    numparameters=NUMPARAMETERS,                                                
    paramdesc=pointer(cast(paramdesc, POINTER(DSP_PARAMETER_DESC))),            
)                                                                               
mydsp = system.create_dsp(dspdesc) 

as does this:

[...]
# wavedata                                                                      
wavedata_data_desc = DSP_PARAMETER_DESC_DATA(                                   
    datatype=DSP_PARAMETER_DATA_TYPE.USER.value                                 
)                                                                               
wavedata_desc = DSP_PARAMETER_DESC(                                             
    type=DSP_PARAMETER_TYPE.DATA.value,                                         
    name=b"wave data",                                                          
    label=b"",                                                                  
    description=b"wave data",                                                   
    desc_union=DSP_PARAMETER_DESC_UNION(datadesc=wavedata_data_desc),           
)  

NUMPARAMETERS = 1                                                               
paramdesc = (DSP_PARAMETER_DESC * NUMPARAMETERS)()                              
paramdesc[0] = volume_desc                                                      
                                                                                
dspdesc = DSP_DESCRIPTION(                                                      
    name=b"My first DSP unit",                                                  
    version=0x00010000,                                                         
    numinputbuffers=1,                                                          
    numoutputbuffers=1,                                                         
    numparameters=NUMPARAMETERS,                                                
    paramdesc=pointer(cast(paramdesc, POINTER(DSP_PARAMETER_DESC))),            
)                                                                               
mydsp = system.create_dsp(dspdesc) 

So, defining and declaring one of these parameters (either one) seems to work just fine. It allows me to inspect the mydsp variable and retrieve all the set parameters.

However, as soon as I set NUMPARAMETERS to 2 and assign both of them, in whatever order, we segfault.

So, progress of a kind, but still hitting this wall. Thanks a lot for your help.

@bbbart
Copy link
Contributor Author

bbbart commented May 4, 2021

Alright, here's the current state:

  • all code samples from upstream have been ported to Python using pyfmodex
  • the only kind-of-dependency is curses, which only requires an additional install on Windows
  • there is a problem with dsp_inspector.py; the code is in PR Code examples #36 , but no reference is made to it in the documentation; please run it on your system, I get mumbo-jumbo on the DSP info, but correct information on the parameter info
  • I'm still stuck with dsp_custom.py, discussed at length in this thread above; that code is not part of PR Code examples #36

As far as I'm concerned, this is ready to be tested on non-Linux platforms and then merged.

I have worked with Python 3.9. I'll test the samples with 3.8 and 3.7 as well, but I don't expect any problems as I've tried to stay away from the most recent language features.

@bbbart
Copy link
Contributor Author

bbbart commented May 4, 2021

I have worked with Python 3.9. I'll test the samples with 3.8 and 3.7 as well, but I don't expect any problems as I've tried to stay away from the most recent language features.

Done. Nothing to report, all works fine.

Just a note: py-flags states that Python 3.6+ users should consider using the Flag and IntFlag classes of the standard enum module. Those are very similar to the flags.Flags class. This could remove the currently only outside dependency of pyfmodex.

@tyrylu
Copy link
Owner

tyrylu commented May 5, 2021

Okay, i'll test the samples on Windows (likely Python 3.9), maybe discover why the dsp info example reports weird data. We, as it seems, likely require python 3.6 as the minimum supported version and drop pyflags altogether.

@bbbart
Copy link
Contributor Author

bbbart commented May 5, 2021

Okay, i'll test the samples on Windows (likely Python 3.9), maybe discover why the dsp info example reports weird data.

That would be great.

We, as it seems, likely require python 3.6 as the minimum supported version and drop pyflags altogether.

yes. 3.[789] is fine for me.

@tyrylu
Copy link
Owner

tyrylu commented May 10, 2021

The samples PR is merged and pyflags is no longer required, so i suppose we can close this one?

@bbbart
Copy link
Contributor Author

bbbart commented May 11, 2021

waw, nice. yes, I suppose we can, just need to check if the samples still work with TIMEUNIT moved from flags to enums (#37).

@bbbart
Copy link
Contributor Author

bbbart commented May 11, 2021

ok, some minor changes needed to be made, all grouped in #39 - ready to be pulled

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

No branches or pull requests

3 participants