-
Notifications
You must be signed in to change notification settings - Fork 9
Audio Filters
NOTE: This section is subject to change at anytime as rkevin is developing audio filters. The APIs listed may not be accurate.
You can add audio filters to the currently playing BGM, BGS or SE, such as echo, distortions, high-pass and low-pass filters, etc.
This is a work in progress. More filters are being written right now. If you want a particular type of filter that's not here, it can be added on request (or better yet, write it yourself and make a PR!).
This acts the same as the Distortion -> Rectifier Distortion effect in Audacity. It takes in an intensity between 0 and 1, which indicates how much the audio should be distorted (the same as Audacity's distortion amount (0% to 100%).
To use it, call Audio.bgm_add_filter("Rectifier", intensity)
.
To add a filter on a BGM, call Audio.bgm_add_filter(filtername, ...)
. Depending on what filtername
is, the arguments to initialize it will be different, so please check the list of available filters for how to initialize each type. Audio.bgs_add_filter
and Audio.me_add_filter
work the same way.
You can stack any numer of filters together (including the same type of filter) if you keep calling Audio.bgm_add_filter
. To clear all filters, use Audio.bgm_clear_filters
(or Audio.bgs_clear_filters
/ Audio.me_clear_filters
).
Filters only last as long as the current track is playing. If you use Audio.bgm_play
or Audio.bgm_crossfade
to switch out the track, all filters are automatically removed.
To write your own audio filter in C++, you need to:
- Declare your class in
src/audiofilter.h
that subclasses fromAudioFilter
. - Write your own implmentation in
src/audiofilter.c
. - Add a way for Ruby to create your audiofilter in the
addFilterToAudioSource
function inbinding-mri/audio-binding.c
.
You can look at the RectifierAudioFilter
implementation as an example.
Your AudioFilter
class should implement 4 functions that processes 8-bit or 16-bit audio data, in mono or stereo. These functions are called process_mono
and process_stereo
, and each have two overrides that take in a uint8_t
array or int16_t
array.
The function signatures should be as follows:
void process_mono(uint8_t *data, int size, int freq);
void process_mono(int16_t *data, int size, int freq);
void process_stereo(uint8_t *data, int size, int freq);
void process_stereo(int16_t *data, int size, int freq);
size
indicates how many elements you have in your array, and freq
is the audio frequency (e.g. 44100Hz is a common one).
Alternatively, you can directly override the void process(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
function of the AudioFilter. The default implementation parses the format
and calls one of the 4 functions listed above, but you can override this one function instead if you prefer.
The process_mono
and process_stereo
functions should take in the audio samples, modify them in place, then return.
Depending on the audio file, it could use 8-bit or 16-bit samples.
8-bit samples are unsigned integers ranging from 0~255. The midpoint would be 128, anything between 0 and 127 would be on the "negative" half of the waveform, while anything between 129 and 255 would be on the "positive" half. If you want to convert it into a 8-bit signed integer (between -128 and +127), you can cast the uint8_t
to a int8_t
and then flip the MSB (do a XOR with 0x80).
16-bit samples are signed integers ranging from -32768 to +32767. The midpoint is 0, all negative numbers are on the "negative" half of the waveform, while all positive numbers are on the "positive" half.
If you are absolutely sure you won't deal with a certain sample size, you can opt to not implement those functions. The default implementation in AudioFilter
throws an exception about it not being implemented, and should crash the audio thread.
For mono samples, the data is fairly self explanatory - an array of integers, each one telling you where the waveform is.
For stereo samples, the samples should be interleaved - data[0]
should be the first audio sample for channel 1, data[1]
should be the first audio sample for channel 2, data[2]
should be the second audio sample for channel 1, etc. Be sure to process them separately if you need to.
Similarly, if you know you will only be dealing with one type of audio, you can opt to not implement processing the other type.
This function would be called from a dedicated audio thread that handles nothing but feeding data into the OpenAL source. Do not interact with any data outside of this thread (e.g. in the ruby thread) to avoid race conditions. Please try to avoid using locks to synchronize between the ruby thread as well, since that could lead to deadlocks (unless you are extremely sure what you're doing).
You need to modify the addFilterToAudioSource
function in binding-mri/audio-binding.cpp
to make sure ruby can call your class's constructor. Add a if
statement for your filter, like this:
if (!strcmp(filtertype, "MyAwesomeFilter")) {
// initialize your arguments
int arg1;
double arg2;
char* arg3; //if you are using strings, be sure to make a copy of the string using strcpy (or equivalent) in your constructor, the pointer may be invalid once you exit this function
bool arg4;
int optional_arg = 1;
// for more details on how rb_get_args work, please look in `binding-util.cpp`, or bug rkevin to write a proper "how to expose C++ code to ruby" documentation.
// in short, in the format string "z" means a string, "i" is an integer, "f" is a double, "b" is a bool, and "|" means everything after it is optional
// note there is no comma before RB_ARG_END
rb_get_args(argc, argv, "zifzb|i", &filtertype, &arg1, &arg2, &arg3, &arg4, &optional_arg RB_ARG_END);
// construct your filter
MyAwesomeAudioFilter* filter = new MyAwesomeAudioFilter(arg1, arg2, arg3, arg4, optional_arg);
// then add your filter. don't worry about garbage collection, the filter will be destroyed on a `clearFilter` or if the currently playing file is closed
shState->audio().addFilter(audiotype, filter);
return;
}
Afterwards, compile ModShot and verify your filter works by calling Audio.bgm_add_filter("MyAwesomeFilter", 1, 2.5, "dQw4w9WgXcQ", false)
and see if it works!