/** * projectM -- Milkdrop-esque visualisation SDK * Copyright (C)2003-2004 projectM Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * See 'LICENSE.txt' included within this release * */ /** * $Id: PCM.c,v 1.3 2006/03/13 20:35:26 psperl Exp $ * * Takes sound data from wherever and hands it back out. * Returns PCM Data or spectrum data, or the derivative of the PCM data */ #include #include #include #include "Common.hpp" #include "wipemalloc.h" #include "fftsg.h" #include "PCM.hpp" #include // see https://github.com/projectM-visualizer/projectm/issues/161 class AutoLevel { private: double level; // accumulate sample data size_t level_samples; double level_sum; double level_max; double l0,l1,l2; public: AutoLevel() : level(0.01),level_samples(0),level_sum(0),level_max(0),l0(-1),l1(-1),l2(-1) { } /* * Here is where we try to do auto volume setting. Doing this here * means that none of the code downstream (waveforms, beatdetect, etc) needs * to worry about it. * * 1) Don't over react to level changes within a song * 2) Ignore silence/gaps * * I don't know if it's necessary to have both sum and max, but that makes * it easier to experiment... */ // This is an arbitrary number that helps control // a) how quickly the level can change and // b) keeps code from being affected by how the caller provides data (lot of short buffers, or fewer long buffers) #define AUTOLEVEL_SEGMENT 4096 double updateLevel(size_t samples, double sum, double max) { if (sum/samples < 0.00001) return level; level_sum += sum; level_max = fmax(level_max,max*1.02); level_samples += samples; if (level_samples >= AUTOLEVEL_SEGMENT || l0 <= 0) { double max_recent = fmax(fmax(l0, l1), fmax(l2, level_max)); l0 = l1; l1 = l2; l2 = level_max; level_max *= 0.95; level_sum = level_samples = 0; level = (l0 <= 0) ? max_recent : level * 0.96 + max_recent * 0.04; level = fmax(level,0.0001); } return level; } }; PCM::PCM() : start(0), newsamples(0) { leveler = new AutoLevel(); //Allocate FFT workspace // per rdft() documentation // length of ip >= 2+sqrt(n) and length of w == n/2 #if FFT_LENGTH > 1024 #error update this code #endif w = (double *)wipemalloc(FFT_LENGTH*sizeof(double)); // see fftsg.cpp length of ip >= 2+sqrt(n/2) // in this case n=2*FFT_LENGTH, so 34 is big enough to handle FFT_LENGTH=1024 ip = (int *)wipemalloc(34 * sizeof(int)); ip[0]=0; memset(pcmL, 0, sizeof(pcmL)); memset(pcmR, 0, sizeof(pcmR)); memset(freqL, 0, sizeof(freqL)); memset(freqR, 0, sizeof(freqR)); memset(spectrumL, 0, sizeof(spectrumL)); memset(spectrumR, 0, sizeof(spectrumR)); } PCM::~PCM() { delete leveler; free(w); free(ip); } #include void PCM::addPCMfloat(const float *PCMdata, size_t samples) { float a,sum=0,max=0; for (size_t i=0; iupdateLevel(samples, sum, max); } /* NOTE: this method expects total samples, not samples per channel */ void PCM::addPCMfloat_2ch(const float *PCMdata, size_t count) { size_t samples = count/2; float a,b,sum=0,max=0; for (size_t i=0; iupdateLevel(samples, sum/2, max); } void PCM::addPCM16Data(const short* pcm_data, size_t samples) { float a, b, sum = 0, max = 0; for (size_t i = 0; i < samples; ++i) { size_t j = (i + start) % maxsamples; a = pcmL[j] = (pcm_data[i * 2 + 0] / 16384.0); b = pcmR[j] = (pcm_data[i * 2 + 1] / 16384.0); sum += fabs(a) + fabs(b); max = fmax(fmax(max, a), b); } start = (start + samples) % maxsamples; newsamples += samples; level = leveler->updateLevel(samples, sum/2, max); } void PCM::addPCM16(const short PCMdata[2][512]) { const int samples=512; float a,b,sum=0,max=0; for (size_t i=0;iupdateLevel(samples, sum/2, max); } void PCM::addPCM8(const unsigned char PCMdata[2][1024]) { const int samples=1024; float a,b,sum=0,max=0; for (size_t i=0; iupdateLevel(samples, sum/2, max); } void PCM::addPCM8_512(const unsigned char PCMdata[2][512]) { const size_t samples=512; float a,b,sum=0,max=0; for (size_t i=0; iupdateLevel(samples, sum/2, max); } // puts sound data requested at provided pointer // // samples is number of PCM samples to return // smoothing is the smoothing coefficient // returned values are normalized from -1 to 1 void PCM::getPCM(float *data, CHANNEL channel, size_t samples, float smoothing) { assert(channel == 0 || channel == 1); if (0==smoothing) { _copyPCM(data, channel, samples); return; } // since we've already got the freq data laying around, let's use that for smoothing _updateFFT(); // copy double freq[FFT_LENGTH*2]; double *from = channel==0 ? freqL : freqR; for (int i=0 ; i= samples ? 0 : spectrum[i + 2]; data[i] = (l2 + 4 * l1 + 6 * c + 4 * r1 + r2) / 16.0; } } } void PCM::_updateFFT() { if (newsamples > 0) { _updateFFT(0); _updateFFT(1); newsamples = 0; } } void PCM::_updateFFT(size_t channel) { assert(channel == 0 || channel == 1); double *freq = channel==0 ? freqL : freqR; _copyPCM(freq, channel, FFT_LENGTH*2); rdft(FFT_LENGTH*2, 1, freq, ip, w); // compute magnitude data (m^2 actually) float *spectrum = channel==0 ? spectrumL : spectrumR; for (size_t i=1 ; imx ? mx : a 500); for (size_t i = 1; i < FFT_LENGTH; i++) TEST(freq0[i] < 0.1); free(data); free(freq0); free(freq1); } // high frequency { const size_t samples = 2; float data[4] = {1.0,0.0,0.0,1.0}; for (size_t i = 0; i < 1024; i++) pcm.addPCMfloat_2ch(data, samples * 2); float *freq0 = new float[FFT_LENGTH]; float *freq1 = new float[FFT_LENGTH]; pcm.level = 1.0; pcm.getSpectrum(freq0, CHANNEL_0, FFT_LENGTH, 0.0); pcm.getSpectrum(freq1, CHANNEL_1, FFT_LENGTH, 0.0); // freq0 and freq1 should be equal for (size_t i = 0; i < FFT_LENGTH; i++) TEST(eq(freq0[i], freq1[i])); for (size_t i=0 ; i 100000); free(freq0); free(freq1); } return true; } bool test() override { TEST(test_addpcm()); TEST(test_fft()); return true; } }; Test* PCM::test() { return new PCMTest(); } #else Test* PCM::test() { return nullptr; } #endif