diff --git a/src/projectM-engine/CMakeLists.txt b/src/projectM-engine/CMakeLists.txt index ac847adfd..c802e47bb 100644 --- a/src/projectM-engine/CMakeLists.txt +++ b/src/projectM-engine/CMakeLists.txt @@ -1,7 +1,7 @@ PROJECT(projectM) ADD_LIBRARY(projectM SHARED projectM.cpp projectM.hpp PBuffer.cpp PBuffer.hpp InitCond.cpp InitCond.hpp Expr.cpp PCM.cpp Parser.cpp Preset.cpp Common.hpp BeatDetect.cpp PCM.hpp PerPixelEqn.cpp Eval.hpp -Param.cpp CustomWave.cpp CustomShape.hpp CustomShape.cpp Param.hpp CustomWave.hpp BeatDetect.hpp console_interface.h +RingBuffer.hpp Param.cpp CustomWave.cpp CustomShape.hpp CustomShape.cpp Param.hpp CustomWave.hpp BeatDetect.hpp console_interface.h Func.hpp Func.cpp Eval.cpp wipemalloc.h PerFrameEqn.cpp PerPointEqn.cpp fftsg.cpp console_interface.cpp CValue.hpp Expr.hpp timer.cpp wipemalloc.cpp PerFrameEqn.hpp PerPixelEqn.hpp PerPointEqn.hpp BuiltinFuncs.hpp BuiltinFuncs.cpp compare.h event.h fatal.h fftsg.h timer.h BuiltinParams.hpp BuiltinParams.cpp Preset.hpp Renderer.cpp Renderer.hpp ParamUtils.hpp diff --git a/src/projectM-engine/MoodBar.cpp b/src/projectM-engine/MoodBar.cpp index 71f20556a..96b46f2ca 100644 --- a/src/projectM-engine/MoodBar.cpp +++ b/src/projectM-engine/MoodBar.cpp @@ -2,8 +2,12 @@ // C++ Implementation: MoodBar // // Description: -// -// +// Calculates moodbar values from spectrum frequency data. This code is shamelessly stolen +// from the GPL'ed moodbar project / research by Gavin Wood. The biggest difference between +// his implementation and mine is the adaptations for "real time" normalization. A ring buffer +// is used to remember a fixed window of most recently observed rgb values. All such unnormalized +// values are used to calculate a "stretched" normalized set of color intensities. + // Author: Carmelo Piccione , (C) 2007 // // Copyright: See COPYING file that comes with this distribution @@ -19,11 +23,43 @@ extern "C" { #include #include "PCM.hpp" + +#define SPECTRUM_BAND_FREQ(band, size, rate) \ + (((float)(band))*((float)(rate))/((float)(size))) + + +/// Bark band hard coded values stolen directly from moodbar implementation const unsigned int MoodBar::s_bark_bands[] = { 100, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720, 2000, 2320, 2700, 3150, 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500 }; + +void MoodBar::standardNormalize(float * rgb) { + + float sum = 0; + + for (int i = 0; i < 3; i++) { + sum += rgb[i]; + } + + if (sum == 0) + return; + + for (int i = 0; i < 3; i++) { + rgb[i] /= sum; + } + +} + +void MoodBar::resetBuffer() { + + for (int c = 0; c < 3; c++) + for (unsigned int i = 0; i < RingBuffer::RING_BUFFER_SIZE; i++) + m_ringBuffers[c].append(.5f); + +} + void MoodBar::calculateMood (float * rgb_left, float * rgb_right, float * rgb_avg) { @@ -35,37 +71,61 @@ void MoodBar::calculateMood for (i = 0; i < 24; ++i) { m_amplitudes_left[i] = 0.f; m_amplitudes_right[i] = 0.f; - } + for (i = 0; i < m_numFreqs; ++i) { - real_left = m_pcm->vdataL[2*i]; imag_left = m_pcm->vdataL[2*i + 1]; - real_right = m_pcm->vdataR[2*i]; imag_right = m_pcm->vdataR[2*i + 1]; + //std::cerr << "vdataL[2*" << i << "] = " << m_pcm->vdataL[2*i] << std::endl; + //std::cerr << "vdataL[2*" << i << "+1] = " << m_pcm->vdataL[2*i+1] << std::endl; + + real_left = m_pcm->pcmdataL[2*i]; imag_left = m_pcm->pcmdataL[2*i + 1]; + real_right = m_pcm->pcmdataR[2*i]; imag_right = m_pcm->pcmdataR[2*i + 1]; m_amplitudes_left[m_barkband_table[i]] += sqrtf (real_left*real_left + imag_left*imag_left); m_amplitudes_right[m_barkband_table[i]] += sqrtf (real_right*real_right + imag_right*imag_right); } + for (i= 0; i < 3; i++) { + rgb_left[i] = 0.0; + rgb_right[i] = 0.0; + } + for (i = 0; i < 24; ++i) { rgb_left[i/8] += m_amplitudes_left[i] * m_amplitudes_left[i]; rgb_right[i/8] += m_amplitudes_right[i] * m_amplitudes_right[i]; } for (i = 0; i < 3; i++) { + rgb_avg[i] = (rgb_left[i] + rgb_right[i]) / 2.0; + rgb_avg[i] = sqrtf (rgb_avg[i]); + rgb_left[i] = sqrtf (rgb_left[i]); rgb_right[i] = sqrtf (rgb_right[i]); - rgb_avg[i] = (rgb_left[i] + rgb_right[i]) / 2; - rgb_avg[i] = sqrtf (rgb_avg[i]); } - /// @bug verify normalized values - std::cerr << "rgb_avg: " << rgb_avg[0] << "," << "," << rgb_avg[1] << "," << rgb_avg[2] << std::endl; + + stretchNormalize(rgb_left); + stretchNormalize(rgb_right); + stretchNormalize(rgb_avg); + + /// @bug verify normalized m_ringBuffer + //standardNormalize(rgb_avg); + //standardNormalize(rgb_left); + //standardNormalize(rgb_right); + + std::cerr << "rgb_avg: " << rgb_avg[0] << "," << rgb_avg[1] << "," << rgb_avg[2] << std::endl; + +#ifdef ASSERT_MOODBAR for (i = 0; i < 3; i++) { assert(rgb_avg[i] <= 1.0); assert(rgb_avg[i] >= 0.0); } +#endif + + + } @@ -88,12 +148,134 @@ MoodBar::calcBarkbandTable () for (i = 0; i < m_numFreqs; ++i) { - if (barkband < 23 && 1) - //(unsigned int) GST_SPECTRUM_BAND_FREQ (i, m_size, m_rate) - // >= s_bark_bands[barkband]) + if (barkband < 23 && + (unsigned int) SPECTRUM_BAND_FREQ (i, m_size, m_rate) + >= s_bark_bands[barkband]) barkband++; m_barkband_table[i] = barkband; } } + + + +/* Copied and mod'ed from moodbar source code which also says the following: + The normalization code was copied from Gav Wood's Exscalibar + * library, normalise.cpp + */ +void MoodBar::stretchNormalize (float * rgb) +{ + float mini, maxi, tu = 0.f, tb = 0.f; + float avgu = 0.f, avgb = 0.f, delta, avg = 0.f; + float avguu = 0.f, avgbb = 0.f; + unsigned int i; + int t = 0; + + // iterate over r,g,b + for (int c = 0; c < 3; c++) { + + // Append latest un-normalized value on ring buffer + m_ringBuffers[c].append(rgb[c]); + + unsigned int numvals = RingBuffer::RING_BUFFER_SIZE; + + if (numvals == 0) + return; + + mini = maxi = m_ringBuffers[c].get(); + + // Compute max and min m_ringBuffer of the array + for (i = 1; i < numvals; i++) + { + float _tmpval = m_ringBuffers[c].get(); + if (_tmpval > maxi) + maxi = _tmpval; + else if (_tmpval < mini) + mini = _tmpval; + } + + // Compute array average excluding the maximum and minimum ranges + for (i = 0; i < numvals; i++) + { + float _tmpval = m_ringBuffers[c].get(); + + if(_tmpval != mini && _tmpval != maxi) + { + avg += _tmpval / ((float) numvals); + t++; + } + } + + // Now compute average values if we partition the elements into + // two sets segmented by the previously computed average + // Again we exclude the max and min elements. + for (i = 0; i < numvals; i++) + { + float _tmpval = m_ringBuffers[c].get(); + + if (_tmpval != mini && _tmpval != maxi) + { + if (_tmpval > avg) + { + avgu += _tmpval; + tu++; + } + else + { + avgb += _tmpval; + tb++; + } + } + } + + // This normalizes the computations in the previous for loop + // so they represent proper averages of their respective sets + avgu /= (float) tu; + avgb /= (float) tb; + + tu = 0.f; + tb = 0.f; + + // Computes two averages. One of m_ringBuffer that are less than previously computer lower bound and + // one of m_ringBuffer greater than the previously computed upper bound. + // As usual, min and max elements are excluded. + for (i = 0; i < numvals; i++) { + float _tmpval = m_ringBuffers[c].get(); + + + if (_tmpval != mini && _tmpval != maxi) + { + if (_tmpval > avgu) + { + avguu += _tmpval; + tu++; + } + + else if (_tmpval < avgb) + { + avgbb += _tmpval; + tb++; + } + } + } + + avguu /= (float) tu; + avgbb /= (float) tb; + + // lost from here- what is theory behind this? + mini = fmax (avg + (avgb - avg) * 2.f, avgbb); + maxi = fmin (avg + (avgu - avg) * 2.f, avguu); + delta = maxi - mini; + + if (delta == 0.f) + delta = 1.f; + + // Assign colos to normalized m_ringBufferue of last item in buffer +// i = numvals-1; +// m_ringBuffers[c]. + rgb[c] = finite (rgb[c]) ? fmin(1.f, fmax(0.f, (rgb[c] - mini) / delta)) + : 0.f; + + } +} diff --git a/src/projectM-engine/MoodBar.hpp b/src/projectM-engine/MoodBar.hpp index 0667ca2d8..3ce746111 100644 --- a/src/projectM-engine/MoodBar.hpp +++ b/src/projectM-engine/MoodBar.hpp @@ -20,23 +20,38 @@ #ifndef _MOODBAR_HPP #define _MOODBAR_HPP - -class PCM; +#include "PCM.hpp" +#include "RingBuffer.hpp" class MoodBar { public: - MoodBar(unsigned int numFreqs, int size, int rate, PCM * pcm) : m_numFreqs(numFreqs), m_size(size), m_rate(rate), m_pcm(pcm) { + + MoodBar(PCM * pcm) : m_numFreqs(pcm->numsamples/2 + 1), m_size(pcm->numsamples), m_rate(FIXED_SAMPLE_RATE), m_pcm(pcm) { calcBarkbandTable(); + resetBuffer(); + } + MoodBar(int rate, PCM * pcm) : m_numFreqs(pcm->numsamples/2 + 1), m_size(pcm->numsamples), m_rate(rate), m_pcm(pcm) { + calcBarkbandTable(); + resetBuffer(); + + } + ~MoodBar() { delete(m_barkband_table); } /// Calculate rgb mood values for both left and right channels. - /// Out should be an array containing numFreqs pairs of real/complex values. + /// Uses the pcm instance's latest assignment into its + /// pcmL/R data buffers as inputs void calculateMood(float * rgb_left, float * rgb_right, float * rgb_avg); private: + void resetBuffer() ; + + /// @bug need to find this elsewhere + static const int FIXED_SAMPLE_RATE = 44100; + unsigned int m_numFreqs; int m_size; int m_rate; @@ -44,12 +59,15 @@ private: * incoming band is supposed to go in. */ void calcBarkbandTable (); PCM * m_pcm; - + void standardNormalize(float * rgb); float m_amplitudes_left[24]; float m_amplitudes_right[24]; - + + void stretchNormalize (float * colors); static const unsigned int s_bark_bands[24]; + RingBuffer m_ringBuffers[3]; + unsigned int * m_barkband_table; }; diff --git a/src/projectM-engine/RingBuffer.hpp b/src/projectM-engine/RingBuffer.hpp new file mode 100644 index 000000000..76e6def70 --- /dev/null +++ b/src/projectM-engine/RingBuffer.hpp @@ -0,0 +1,52 @@ +#ifndef RING_BUFFER_HPP +#define RING_BUFFER_HPP +/// Code courtesy of: http://www.osix.net/modules/article/?id=464 + +template +class RingBuffer { +public: + static const unsigned long RING_BUFFER_SIZE = 300; +private: + + kind buffer[RING_BUFFER_SIZE]; + unsigned int current_element; +public: + RingBuffer() : current_element(0) { + } + + RingBuffer(const RingBuffer& old_ring_buf) { + memcpy(buffer, old_ring_buf.buffer, RING_BUFFER_SIZE*sizeof(kind)); + current_element = old_ring_buf.current_element; + } + + RingBuffer operator = (const RingBuffer& old_ring_buf) { + memcpy(buffer, old_ring_buf.buffer, RING_BUFFER_SIZE*sizeof(kind)); + current_element = old_ring_buf.current_element; + } + + ~RingBuffer() { } + + void append(kind value) { + if(current_element >= RING_BUFFER_SIZE) { + current_element = 0; + } + + buffer[current_element] = value; + + ++current_element; + } + + kind get() { + if(current_element >= RING_BUFFER_SIZE) { + current_element = 0; + } + + ++current_element; + return( buffer[(current_element-1)] ); + } + + int current() { + return (current_element); + } +}; +#endif diff --git a/src/projectM-engine/projectM.cpp b/src/projectM-engine/projectM.cpp index a3b466ba9..84415d19e 100755 --- a/src/projectM-engine/projectM.cpp +++ b/src/projectM-engine/projectM.cpp @@ -64,7 +64,7 @@ double smoothDuration = 5; //int smoothFrame = 0; int oldFrame = 1; -DLLEXPORT projectM::projectM(int gx, int gy, int fps, int texsize, int width, int height) :renderer(0), renderTarget(0), smoothFrame(0), beatDetect ( 0 ) +DLLEXPORT projectM::projectM(int gx, int gy, int fps, int texsize, int width, int height) :renderer(0), renderTarget(0), smoothFrame(0), beatDetect ( 0 ), moodBar(0) { projectM_reset(); @@ -87,6 +87,9 @@ std::cerr << "[projectM] 2" << std::endl; std::cerr << "[projectM] 3" << std::endl; if (beatDetect) delete(beatDetect); + if (moodBar) + delete(moodBar); + std::cerr << "[projectM] 4" << std::endl; if (renderTarget) delete(renderTarget); @@ -95,7 +98,7 @@ std::cerr << "[projectM] 5" << std::endl; } DLLEXPORT projectM::projectM(std::string config_file) : - renderer(0), renderTarget(0), smoothFrame(0), beatDetect ( 0 ) + renderer(0), renderTarget(0), smoothFrame(0), beatDetect ( 0 ), moodBar(0) { projectM_reset(); readConfig(config_file); @@ -156,19 +159,24 @@ DLLEXPORT void projectM::renderFrame() // printf("start:%d at:%d min:%d stop:%d on:%d %d\n",startframe, frame frame-startframe,avgtime, noSwitch,progress); presetInputs.ResetMesh(); - - // printf("%f %d\n",Time,frame); beatDetect->detectFromSamples(); + +#ifndef USE_MOODBAR +#define USE_MOODBAR +#endif + #ifdef USE_MOODBAR - float rgb[3]; - moodBar->calculateMood(rgb); - presetInputs.mood_r = rgb[0]; - presetInputs.mood_g = rgb[1]; - presetInputs.mood_b = rgb[2]; + float rgb_left[3], rgb_right[3], rgb_avg[3]; + moodBar->calculateMood(rgb_left, rgb_right, rgb_avg); + + presetInputs.mood_r = rgb_avg[0]; + presetInputs.mood_g = rgb_avg[1]; + presetInputs.mood_b = rgb_avg[2]; + #endif DWRITE ( "=== vol: %f\tbass: %f\tmid: %f\ttreb: %f ===\n", @@ -378,6 +386,8 @@ DLLEXPORT void projectM::projectM_reset() assert(!beatDetect); beatDetect = new BeatDetect(); + moodBar = new MoodBar(beatDetect->pcm); + /* Preset loading function */ initPresetTools(); #if 0