Skip to content
This repository has been archived by the owner on Mar 7, 2023. It is now read-only.

Audio Filters

Speak2Erase edited this page Mar 21, 2021 · 1 revision

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.

List of available filters

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!).

Rectifier

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).

Using a filter

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.

Writing a filter

To write your own audio filter in C++, you need to:

  • Declare your class in src/audiofilter.h that subclasses from AudioFilter.
  • Write your own implmentation in src/audiofilter.c.
  • Add a way for Ruby to create your audiofilter in the addFilterToAudioSource function in binding-mri/audio-binding.c.

You can look at the RectifierAudioFilter implementation as an example.

Declaring your class

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.

Implementing the filter

The process_mono and process_stereo functions should take in the audio samples, modify them in place, then return.

8-bit vs 16-bit samples

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.

Mono vs stereo samples

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.

Threading

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).

Exposing your class to Ruby

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!