Path: blob/master/servers/audio/effects/audio_effect_spectrum_analyzer.cpp
21024 views
/**************************************************************************/1/* audio_effect_spectrum_analyzer.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "audio_effect_spectrum_analyzer.h"31#include "audio_effect_spectrum_analyzer.compat.inc"32#include "servers/audio/audio_server.h"3334static void smbFft(float *fftBuffer, long fftFrameSize, long sign)35/*36FFT routine, (C)1996 S.M.Bernsee. Sign = -1 is FFT, 1 is iFFT (inverse)37Fills fftBuffer[0...2*fftFrameSize-1] with the Fourier transform of the38time domain data in fftBuffer[0...2*fftFrameSize-1]. The FFT array takes39and returns the cosine and sine parts in an interleaved manner, ie.40fftBuffer[0] = cosPart[0], fftBuffer[1] = sinPart[0], asf. fftFrameSize41must be a power of 2. It expects a complex input signal (see footnote 2),42ie. when working with 'common' audio signals our input signal has to be43passed as {in[0],0.,in[1],0.,in[2],0.,...} asf. In that case, the transform44of the frequencies of interest is in fftBuffer[0...fftFrameSize].45*/46{47float wr, wi, arg, *p1, *p2, temp;48float tr, ti, ur, ui, *p1r, *p1i, *p2r, *p2i;49long i, bitm, j, le, le2, k;5051for (i = 2; i < 2 * fftFrameSize - 2; i += 2) {52for (bitm = 2, j = 0; bitm < 2 * fftFrameSize; bitm <<= 1) {53if (i & bitm) {54j++;55}56j <<= 1;57}58if (i < j) {59p1 = fftBuffer + i;60p2 = fftBuffer + j;61temp = *p1;62*(p1++) = *p2;63*(p2++) = temp;64temp = *p1;65*p1 = *p2;66*p2 = temp;67}68}69for (k = 0, le = 2; k < (long)(std::log((double)fftFrameSize) / std::log(2.) + .5); k++) {70le <<= 1;71le2 = le >> 1;72ur = 1.0;73ui = 0.0;74arg = Math::PI / (le2 >> 1);75wr = std::cos(arg);76wi = sign * std::sin(arg);77for (j = 0; j < le2; j += 2) {78p1r = fftBuffer + j;79p1i = p1r + 1;80p2r = p1r + le2;81p2i = p2r + 1;82for (i = j; i < 2 * fftFrameSize; i += le) {83tr = *p2r * ur - *p2i * ui;84ti = *p2r * ui + *p2i * ur;85*p2r = *p1r - tr;86*p2i = *p1i - ti;87*p1r += tr;88*p1i += ti;89p1r += le;90p1i += le;91p2r += le;92p2i += le;93}94tr = ur * wr - ui * wi;95ui = ur * wi + ui * wr;96ur = tr;97}98}99}100101void AudioEffectSpectrumAnalyzerInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {102//copy everything over first, since this only really does capture103for (int i = 0; i < p_frame_count; i++) {104p_dst_frames[i] = p_src_frames[i];105}106107//capture spectrum108while (p_frame_count) {109int to_fill = fft_size * 2 - temporal_fft_pos;110to_fill = MIN(to_fill, p_frame_count);111const double to_fill_step = Math::TAU / (double)fft_size;112113float *fftw = temporal_fft.ptrw();114for (int i = 0; i < to_fill; i++) { //left and right buffers115float window = -0.5 * Math::cos(to_fill_step * (double)temporal_fft_pos) + 0.5;116fftw[temporal_fft_pos * 2] = window * p_src_frames->left;117fftw[temporal_fft_pos * 2 + 1] = 0;118fftw[(temporal_fft_pos + fft_size * 2) * 2] = window * p_src_frames->right;119fftw[(temporal_fft_pos + fft_size * 2) * 2 + 1] = 0;120++p_src_frames;121++temporal_fft_pos;122}123124p_frame_count -= to_fill;125126if (temporal_fft_pos == fft_size * 2) {127//time to do a FFT128smbFft(fftw, fft_size * 2, -1);129smbFft(fftw + fft_size * 4, fft_size * 2, -1);130int next = (fft_pos + 1) % fft_count;131132AudioFrame *hw = (AudioFrame *)fft_history[next].ptr(); //do not use write, avoid cow133134for (int i = 0; i < fft_size; i++) {135//abs(vec)/fft_size normalizes each frequency136hw[i].left = Vector2(fftw[i * 2], fftw[i * 2 + 1]).length() / float(fft_size);137hw[i].right = Vector2(fftw[fft_size * 4 + i * 2], fftw[fft_size * 4 + i * 2 + 1]).length() / float(fft_size);138}139140fft_pos = next; //swap141temporal_fft_pos = 0;142}143}144}145146void AudioEffectSpectrumAnalyzerInstance::_bind_methods() {147ClassDB::bind_method(D_METHOD("get_magnitude_for_frequency_range", "from_hz", "to_hz", "mode"), &AudioEffectSpectrumAnalyzerInstance::get_magnitude_for_frequency_range, DEFVAL(MAGNITUDE_MAX));148BIND_ENUM_CONSTANT(MAGNITUDE_AVERAGE);149BIND_ENUM_CONSTANT(MAGNITUDE_MAX);150}151152Vector2 AudioEffectSpectrumAnalyzerInstance::get_magnitude_for_frequency_range(float p_begin, float p_end, MagnitudeMode p_mode) const {153int fft_index = fft_pos;154155int begin_pos = p_begin * fft_size / (mix_rate * 0.5);156int end_pos = p_end * fft_size / (mix_rate * 0.5);157158begin_pos = CLAMP(begin_pos, 0, fft_size - 1);159end_pos = CLAMP(end_pos, 0, fft_size - 1);160161if (begin_pos > end_pos) {162SWAP(begin_pos, end_pos);163}164const AudioFrame *r = fft_history[fft_index].ptr();165166if (p_mode == MAGNITUDE_AVERAGE) {167Vector2 avg;168169for (int i = begin_pos; i <= end_pos; i++) {170avg += Vector2(r[i]);171}172173avg /= float(end_pos - begin_pos + 1);174175return avg;176} else {177Vector2 max;178179for (int i = begin_pos; i <= end_pos; i++) {180max.x = MAX(max.x, r[i].left);181max.y = MAX(max.y, r[i].right);182}183184return max;185}186}187188Ref<AudioEffectInstance> AudioEffectSpectrumAnalyzer::instantiate() {189Ref<AudioEffectSpectrumAnalyzerInstance> ins;190ins.instantiate();191ins->base = Ref<AudioEffectSpectrumAnalyzer>(this);192static const int fft_sizes[FFT_SIZE_MAX] = { 256, 512, 1024, 2048, 4096 };193ins->fft_size = fft_sizes[fft_size];194ins->mix_rate = AudioServer::get_singleton()->get_mix_rate();195ins->fft_count = (buffer_length / (float(ins->fft_size) / ins->mix_rate)) + 1;196ins->fft_pos = 0;197ins->fft_history.resize(ins->fft_count);198ins->temporal_fft.resize(ins->fft_size * 8); //x2 stereo, x2 amount of samples for freqs, x2 for input199ins->temporal_fft_pos = 0;200for (int i = 0; i < ins->fft_count; i++) {201ins->fft_history.write[i].resize(ins->fft_size); //only magnitude matters202for (int j = 0; j < ins->fft_size; j++) {203ins->fft_history.write[i].write[j] = AudioFrame(0, 0);204}205}206return ins;207}208209void AudioEffectSpectrumAnalyzer::set_buffer_length(float p_seconds) {210buffer_length = p_seconds;211}212213float AudioEffectSpectrumAnalyzer::get_buffer_length() const {214return buffer_length;215}216217void AudioEffectSpectrumAnalyzer::set_fft_size(FFTSize p_fft_size) {218ERR_FAIL_INDEX(p_fft_size, FFT_SIZE_MAX);219fft_size = p_fft_size;220}221222AudioEffectSpectrumAnalyzer::FFTSize AudioEffectSpectrumAnalyzer::get_fft_size() const {223return fft_size;224}225226void AudioEffectSpectrumAnalyzer::_bind_methods() {227ClassDB::bind_method(D_METHOD("set_buffer_length", "seconds"), &AudioEffectSpectrumAnalyzer::set_buffer_length);228ClassDB::bind_method(D_METHOD("get_buffer_length"), &AudioEffectSpectrumAnalyzer::get_buffer_length);229230ClassDB::bind_method(D_METHOD("set_fft_size", "size"), &AudioEffectSpectrumAnalyzer::set_fft_size);231ClassDB::bind_method(D_METHOD("get_fft_size"), &AudioEffectSpectrumAnalyzer::get_fft_size);232233ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "buffer_length", PROPERTY_HINT_RANGE, "0.1,4,0.1,suffix:s"), "set_buffer_length", "get_buffer_length");234ADD_PROPERTY(PropertyInfo(Variant::INT, "fft_size", PROPERTY_HINT_ENUM, "256,512,1024,2048,4096"), "set_fft_size", "get_fft_size");235236BIND_ENUM_CONSTANT(FFT_SIZE_256);237BIND_ENUM_CONSTANT(FFT_SIZE_512);238BIND_ENUM_CONSTANT(FFT_SIZE_1024);239BIND_ENUM_CONSTANT(FFT_SIZE_2048);240BIND_ENUM_CONSTANT(FFT_SIZE_4096);241BIND_ENUM_CONSTANT(FFT_SIZE_MAX);242}243244AudioEffectSpectrumAnalyzer::AudioEffectSpectrumAnalyzer() {245buffer_length = 2;246fft_size = FFT_SIZE_1024;247}248249250