pyfmodex
pyfmodex copied to clipboard
next step in documentation: samples
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. :-)
Most of things I learned by converting the c++ examples from the fmod directory: C:\Program Files (x86)\FMOD SoundSystem\FMOD Studio API Windows\api\core\examples
On Sat, Apr 10, 2021 at 3:34 PM Bart Van Loon @.***> wrote:
After completing the API documentation in #33 https://github.com/tyrylu/pyfmodex/pull/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. :-)
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/tyrylu/pyfmodex/issues/34, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADCIHP3APCJQ34MH6VEAM4DTICR4XANCNFSM42W2J7QQ .
Ah yes, that's a good idea. I'll look into it. Thanks @marcelomanzo !
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?
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)))
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?
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.
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.
Hm, I don't think that's right:
-
paramdesc
is of typeDSP_PARAMETER_DESC_Array_2
- so,
paramdesc[0]
should be of typeDSP_PARAMETER_DESC
- which would make
paramdesc[0].desc_union.datadesc
of typeDSP_PARAMETER_DESC_DATA
butwavedata_desc
is of typeDSP_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.
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 #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 #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.
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.
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.
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.
The samples PR is merged and pyflags is no longer required, so i suppose we can close this one?
waw, nice. yes, I suppose we can, just need to check if the samples still work with TIMEUNIT moved from flags to enums (#37).
ok, some minor changes needed to be made, all grouped in #39 - ready to be pulled