363 lines
11 KiB
C++
363 lines
11 KiB
C++
/*==============================================================================
|
|
Custom DSP Example
|
|
Copyright (c), Firelight Technologies Pty, Ltd 2004-2026.
|
|
|
|
This example shows how to add a user created DSP callback to process audio
|
|
data. The read callback is executed at runtime, and can be added anywhere in
|
|
the DSP network.
|
|
|
|
For information on using FMOD example code in your own programs, visit
|
|
https://www.fmod.com/legal
|
|
==============================================================================*/
|
|
#include "fmod.hpp"
|
|
#include "common.h"
|
|
|
|
typedef struct
|
|
{
|
|
float *buffer;
|
|
float volume_linear;
|
|
int length_samples;
|
|
int channels;
|
|
} mydsp_data_t;
|
|
|
|
FMOD_RESULT F_CALL myDSPCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels)
|
|
{
|
|
mydsp_data_t *data = (mydsp_data_t *)dsp_state->plugindata;
|
|
|
|
/*
|
|
This loop assumes inchannels = outchannels, which it will be if the DSP is created with '0'
|
|
as the number of channels in FMOD_DSP_DESCRIPTION.
|
|
Specifying an actual channel count will mean you have to take care of any number of channels coming in,
|
|
but outputting the number of channels specified. Generally it is best to keep the channel
|
|
count at 0 for maximum compatibility.
|
|
*/
|
|
for (unsigned int samp = 0; samp < length; samp++)
|
|
{
|
|
/*
|
|
Feel free to unroll this.
|
|
*/
|
|
for (int chan = 0; chan < *outchannels; chan++)
|
|
{
|
|
/*
|
|
This DSP filter just halves the volume!
|
|
Input is modified, and sent to output.
|
|
*/
|
|
data->buffer[(samp * *outchannels) + chan] = outbuffer[(samp * inchannels) + chan] = inbuffer[(samp * inchannels) + chan] * data->volume_linear;
|
|
}
|
|
}
|
|
|
|
data->channels = inchannels;
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
/*
|
|
Callback called when DSP is created. This implementation creates a structure which is attached to the dsp state's 'plugindata' member.
|
|
*/
|
|
FMOD_RESULT F_CALL myDSPCreateCallback(FMOD_DSP_STATE *dsp_state)
|
|
{
|
|
unsigned int blocksize;
|
|
FMOD_RESULT result;
|
|
|
|
result = dsp_state->functions->getblocksize(dsp_state, &blocksize);
|
|
ERRCHECK(result);
|
|
|
|
mydsp_data_t *data = (mydsp_data_t *)calloc(sizeof(mydsp_data_t), 1);
|
|
if (!data)
|
|
{
|
|
return FMOD_ERR_MEMORY;
|
|
}
|
|
dsp_state->plugindata = data;
|
|
data->volume_linear = 1.0f;
|
|
data->length_samples = blocksize;
|
|
|
|
data->buffer = (float *)malloc(blocksize * 8 * sizeof(float)); // *8 = maximum size allowing room for 7.1. Could ask dsp_state->functions->getspeakermode for the right speakermode to get real speaker count.
|
|
if (!data->buffer)
|
|
{
|
|
return FMOD_ERR_MEMORY;
|
|
}
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
/*
|
|
Callback called when DSP is destroyed. The memory allocated in the create callback can be freed here.
|
|
*/
|
|
FMOD_RESULT F_CALL myDSPReleaseCallback(FMOD_DSP_STATE *dsp_state)
|
|
{
|
|
if (dsp_state->plugindata)
|
|
{
|
|
mydsp_data_t *data = (mydsp_data_t *)dsp_state->plugindata;
|
|
|
|
if (data->buffer)
|
|
{
|
|
free(data->buffer);
|
|
}
|
|
|
|
free(data);
|
|
}
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
/*
|
|
Callback called when DSP::getParameterData is called. This returns a pointer to the raw floating point PCM data.
|
|
We have set up 'parameter 0' to be the data parameter, so it checks to make sure the passed in index is 0, and nothing else.
|
|
*/
|
|
FMOD_RESULT F_CALL myDSPGetParameterDataCallback(FMOD_DSP_STATE *dsp_state, int index, void **data, unsigned int *length, char *)
|
|
{
|
|
if (index == 0)
|
|
{
|
|
unsigned int blocksize;
|
|
FMOD_RESULT result;
|
|
mydsp_data_t *mydata = (mydsp_data_t *)dsp_state->plugindata;
|
|
|
|
result = dsp_state->functions->getblocksize(dsp_state, &blocksize);
|
|
ERRCHECK(result);
|
|
|
|
*data = (void *)mydata;
|
|
*length = blocksize * 2 * sizeof(float);
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
return FMOD_ERR_INVALID_PARAM;
|
|
}
|
|
|
|
/*
|
|
Callback called when DSP::setParameterFloat is called. This accepts a floating point 0 to 1 volume value, and stores it.
|
|
We have set up 'parameter 1' to be the volume parameter, so it checks to make sure the passed in index is 1, and nothing else.
|
|
*/
|
|
FMOD_RESULT F_CALL myDSPSetParameterFloatCallback(FMOD_DSP_STATE *dsp_state, int index, float value)
|
|
{
|
|
if (index == 1)
|
|
{
|
|
mydsp_data_t *mydata = (mydsp_data_t *)dsp_state->plugindata;
|
|
|
|
mydata->volume_linear = value;
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
return FMOD_ERR_INVALID_PARAM;
|
|
}
|
|
|
|
/*
|
|
Callback called when DSP::getParameterFloat is called. This returns a floating point 0 to 1 volume value.
|
|
We have set up 'parameter 1' to be the volume parameter, so it checks to make sure the passed in index is 1, and nothing else.
|
|
An alternate way of displaying the data is provided, as a string, so the main app can use it.
|
|
*/
|
|
FMOD_RESULT F_CALL myDSPGetParameterFloatCallback(FMOD_DSP_STATE *dsp_state, int index, float *value, char *valstr)
|
|
{
|
|
if (index == 1)
|
|
{
|
|
mydsp_data_t *mydata = (mydsp_data_t *)dsp_state->plugindata;
|
|
|
|
*value = mydata->volume_linear;
|
|
if (valstr)
|
|
{
|
|
snprintf(valstr, FMOD_DSP_GETPARAM_VALUESTR_LENGTH, "%d", (int)((*value * 100.0f)+0.5f));
|
|
}
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
return FMOD_ERR_INVALID_PARAM;
|
|
}
|
|
|
|
int FMOD_Main()
|
|
{
|
|
FMOD::System *system;
|
|
FMOD::Sound *sound;
|
|
FMOD::Channel *channel;
|
|
FMOD::DSP *mydsp;
|
|
FMOD::ChannelGroup *mastergroup;
|
|
FMOD_RESULT result;
|
|
void *extradriverdata = 0;
|
|
|
|
Common_Init(&extradriverdata);
|
|
|
|
/*
|
|
Create a System object and initialize.
|
|
*/
|
|
result = FMOD::System_Create(&system);
|
|
ERRCHECK(result);
|
|
|
|
result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
|
|
ERRCHECK(result);
|
|
|
|
result = system->createSound(Common_MediaPath("stereo.ogg"), FMOD_LOOP_NORMAL, 0, &sound);
|
|
ERRCHECK(result);
|
|
|
|
result = system->playSound(sound, 0, false, &channel);
|
|
ERRCHECK(result);
|
|
|
|
/*
|
|
Create the DSP effect.
|
|
*/
|
|
{
|
|
FMOD_DSP_DESCRIPTION dspdesc;
|
|
memset(&dspdesc, 0, sizeof(dspdesc));
|
|
FMOD_DSP_PARAMETER_DESC wavedata_desc;
|
|
FMOD_DSP_PARAMETER_DESC volume_desc;
|
|
FMOD_DSP_PARAMETER_DESC *paramdesc[2] =
|
|
{
|
|
&wavedata_desc,
|
|
&volume_desc
|
|
};
|
|
|
|
FMOD_DSP_INIT_PARAMDESC_DATA(wavedata_desc, "wave data", "", "wave data", FMOD_DSP_PARAMETER_DATA_TYPE_USER);
|
|
FMOD_DSP_INIT_PARAMDESC_FLOAT(volume_desc, "volume", "%", "linear volume in percent", 0, 1, 1);
|
|
|
|
strncpy(dspdesc.name, "My first DSP unit", sizeof(dspdesc.name));
|
|
dspdesc.version = 0x00010000;
|
|
dspdesc.numinputbuffers = 1;
|
|
dspdesc.numoutputbuffers = 1;
|
|
dspdesc.read = myDSPCallback;
|
|
dspdesc.create = myDSPCreateCallback;
|
|
dspdesc.release = myDSPReleaseCallback;
|
|
dspdesc.getparameterdata = myDSPGetParameterDataCallback;
|
|
dspdesc.setparameterfloat = myDSPSetParameterFloatCallback;
|
|
dspdesc.getparameterfloat = myDSPGetParameterFloatCallback;
|
|
dspdesc.numparameters = 2;
|
|
dspdesc.paramdesc = paramdesc;
|
|
|
|
result = system->createDSP(&dspdesc, &mydsp);
|
|
ERRCHECK(result);
|
|
}
|
|
|
|
result = system->getMasterChannelGroup(&mastergroup);
|
|
ERRCHECK(result);
|
|
|
|
result = mastergroup->addDSP(0, mydsp);
|
|
ERRCHECK(result);
|
|
|
|
/*
|
|
Main loop.
|
|
*/
|
|
do
|
|
{
|
|
bool bypass;
|
|
|
|
Common_Update();
|
|
|
|
result = mydsp->getBypass(&bypass);
|
|
ERRCHECK(result);
|
|
|
|
if (Common_BtnPress(BTN_ACTION1))
|
|
{
|
|
bypass = !bypass;
|
|
|
|
result = mydsp->setBypass(bypass);
|
|
ERRCHECK(result);
|
|
}
|
|
if (Common_BtnPress(BTN_ACTION2))
|
|
{
|
|
float vol;
|
|
|
|
result = mydsp->getParameterFloat(1, &vol, 0, 0);
|
|
ERRCHECK(result);
|
|
|
|
if (vol > 0.0f)
|
|
{
|
|
vol -= 0.1f;
|
|
}
|
|
|
|
result = mydsp->setParameterFloat(1, vol);
|
|
ERRCHECK(result);
|
|
}
|
|
if (Common_BtnPress(BTN_ACTION3))
|
|
{
|
|
float vol;
|
|
|
|
result = mydsp->getParameterFloat(1, &vol, 0, 0);
|
|
ERRCHECK(result);
|
|
|
|
if (vol < 1.0f)
|
|
{
|
|
vol += 0.1f;
|
|
}
|
|
|
|
result = mydsp->setParameterFloat(1, vol);
|
|
ERRCHECK(result);
|
|
}
|
|
|
|
result = system->update();
|
|
ERRCHECK(result);
|
|
|
|
{
|
|
char volstr[32] = { 0 };
|
|
FMOD_DSP_PARAMETER_DESC *desc;
|
|
mydsp_data_t *data;
|
|
|
|
result = mydsp->getParameterInfo(1, &desc);
|
|
ERRCHECK(result);
|
|
result = mydsp->getParameterFloat(1, 0, volstr, 32);
|
|
ERRCHECK(result);
|
|
result = mydsp->getParameterData(0, (void **)&data, 0, 0, 0);
|
|
ERRCHECK(result);
|
|
|
|
Common_Draw("==================================================");
|
|
Common_Draw("Custom DSP Example.");
|
|
Common_Draw("Copyright (c) Firelight Technologies 2004-2026.");
|
|
Common_Draw("==================================================");
|
|
Common_Draw("");
|
|
Common_Draw("Press %s to toggle filter bypass", Common_BtnStr(BTN_ACTION1));
|
|
Common_Draw("Press %s to decrease volume 10%%", Common_BtnStr(BTN_ACTION2));
|
|
Common_Draw("Press %s to increase volume 10%%", Common_BtnStr(BTN_ACTION3));
|
|
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
|
|
Common_Draw("");
|
|
Common_Draw("Filter is %s", bypass ? "inactive" : "active");
|
|
Common_Draw("Volume is %s%s", volstr, desc->label);
|
|
|
|
if (data->channels)
|
|
{
|
|
char display[80] = { 0 };
|
|
int channel;
|
|
|
|
for (channel = 0; channel < data->channels; channel++)
|
|
{
|
|
int count,level;
|
|
float max = 0;
|
|
|
|
for (count = 0; count < data->length_samples; count++)
|
|
{
|
|
if (fabsf(data->buffer[(count * data->channels) + channel]) > max)
|
|
{
|
|
max = fabsf(data->buffer[(count * data->channels) + channel]);
|
|
}
|
|
}
|
|
level = (int)(max * 40.0f);
|
|
|
|
snprintf(display, sizeof(display), "%2d ", channel);
|
|
for (count = 0; count < level; count++) display[count + 3] = '=';
|
|
|
|
Common_Draw(display);
|
|
}
|
|
}
|
|
}
|
|
|
|
Common_Sleep(50);
|
|
} while (!Common_BtnPress(BTN_QUIT));
|
|
|
|
/*
|
|
Shut down
|
|
*/
|
|
result = sound->release();
|
|
ERRCHECK(result);
|
|
|
|
result = mastergroup->removeDSP(mydsp);
|
|
ERRCHECK(result);
|
|
result = mydsp->release();
|
|
ERRCHECK(result);
|
|
|
|
result = system->close();
|
|
ERRCHECK(result);
|
|
result = system->release();
|
|
ERRCHECK(result);
|
|
|
|
Common_Close();
|
|
|
|
return 0;
|
|
}
|