Cleanup PCM interface (#467)

* rewrite PCM interface
* AutoLeveler
* perf - quick check for no equations
* cleanup use of BeatDetect.beatSensitivity
Co-authored-by: Mischa Spiegelmock <me@mish.dev>
This commit is contained in:
mbellew 2021-02-20 13:52:17 -08:00 committed by GitHub
parent b3c3282eb0
commit 10faca9abf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 887 additions and 564 deletions

4
presets/tests/000-empty.milk Executable file
View File

@ -0,0 +1,4 @@
[preset00]
// test preset defaults, see BuiltinParams.cpp
// most minimal preset to make something visible
wave_r=1

13
presets/tests/001-line.milk Executable file
View File

@ -0,0 +1,13 @@
[preset00]
// test preset defaults, see BuiltinParams.cpp
// minimal preset to make something visible
fdecay=0
warp=0;
nWaveMode=6
fWaveScale=1
fwavesmoothing=0.01
wave_r=1.0
wave_g=1.0
wave_b=1.0
wave_x=0.500000
wave_y=0.500000

View File

@ -2,19 +2,13 @@
per_frame_1000=// simple wave
per_frame_1001=// MODE=0 Circle
fDecay=0.980000
fDecay=0
nWaveMode=0
bMaximizeWaveColor=1
fWaveAlpha=4.400000
fWaveScale=1.605447
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
wave_r=0.900000
wave_g=0.90000
wave_b=0.900000
wave_r=1
wave_x=0.500000
wave_y=0.500000

View File

@ -1,20 +1,10 @@
[preset00]
per_frame_1000=// simple wave
per_frame_1001=// MODE=1
per_frame_1001=// MODE=1 RadialBlob
fDecay=0.980000
fDecay=0
nWaveMode=1
bMaximizeWaveColor=1
fWaveAlpha=4.400000
fWaveScale=1.605447
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
wave_r=0.900000
wave_g=0.90000
wave_b=0.900000
wave_r=1
wave_x=0.500000
wave_y=0.500000

View File

@ -2,19 +2,9 @@
per_frame_1000=// simple wave
per_frame_1001=// MODE=2 Blob2
fDecay=0.980000
fDecay=0
nWaveMode=2
bMaximizeWaveColor=1
fWaveAlpha=4.400000
fWaveScale=1.605447
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
wave_r=0.900000
wave_g=0.90000
wave_b=0.900000
wave_r=1
wave_x=0.500000
wave_y=0.500000
wave_y=0.500000

View File

@ -2,19 +2,9 @@
per_frame_1000=// simple wave
per_frame_1001=// MODE=3 Blob3
fDecay=0.980000
fDecay=0
nWaveMode=3
bMaximizeWaveColor=1
fWaveAlpha=4.400000
fWaveScale=1.605447
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
wave_r=0.900000
wave_g=0.90000
wave_b=0.900000
wave_r=1
wave_x=0.500000
wave_y=0.500000

View File

@ -2,19 +2,9 @@
per_frame_1000=// simple wave
per_frame_1001=// MODE=4 DerivativeLine
fDecay=0.980000
fDecay=0
nWaveMode=4
bMaximizeWaveColor=1
fWaveAlpha=4.400000
fWaveScale=1.605447
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
wave_r=0.900000
wave_g=0.90000
wave_b=0.900000
wave_r=1
wave_x=0.500000
wave_y=0.500000
wave_y=0.500000

View File

@ -2,19 +2,9 @@
per_frame_1000=// simple wave
per_frame_1001=// MODE=5 Blob5
fDecay=0.980000
fDecay=0
nWaveMode=5
bMaximizeWaveColor=1
fWaveAlpha=4.400000
fWaveScale=1.605447
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
wave_r=0.900000
wave_g=0.90000
wave_b=0.900000
wave_r=1
wave_x=0.500000
wave_y=0.500000

View File

@ -2,19 +2,9 @@
per_frame_1000=// simple wave
per_frame_1001=// MODE=6 Line
fDecay=0.980000
fDecay=0
nWaveMode=6
bMaximizeWaveColor=1
fWaveAlpha=4.400000
fWaveScale=1.605447
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
wave_r=0.900000
wave_g=0.90000
wave_b=0.900000
wave_r=1
wave_x=0.500000
wave_y=0.500000

View File

@ -2,19 +2,9 @@
per_frame_1000=// simple wave
per_frame_1001=// MODE=7 DoubleLine
fDecay=0.980000
fDecay=0
nWaveMode=7
bMaximizeWaveColor=1
fWaveAlpha=4.400000
fWaveScale=1.605447
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
wave_r=0.900000
wave_g=0.90000
wave_b=0.900000
wave_r=1
wave_x=0.500000
wave_y=0.500000

View File

@ -0,0 +1,26 @@
[preset00]
per_frame_1000=// simple wave
per_frame_1001=// MODE=6 Line
fDecay=0
warp=0.000000
wave_r=1
// MilkdropWaveform
nWaveMode=6
fWaveSmoothing=0.00
wave_x=0.600000
wave_y=0.5
// CustomWaveform
wavecode_0_enabled=1
wavecode_0_scaling=0.05
wavecode_0_smoothing=0
wavecode_0_r=0.000000
wavecode_0_g=1.000000
wavecode_0_b=1.000000
wavecode_0_a=1.000000
wavecode_0_x=0.5
wavecode_0_y=0.4
wave_0_per_point1=x=sample;
wave_0_per_point2=y=y+value1;

View File

@ -0,0 +1,26 @@
[preset00]
per_frame_1000=// simple wave
per_frame_1001=// MODE=6 Line
fDecay=0
warp=0.000000
wave_r=1
// MilkdropWaveform
nWaveMode=6
fWaveSmoothing=0.01
wave_x=0.600000
wave_y=0.5
// CustomWaveform
wavecode_0_enabled=1
wavecode_0_scaling=0.05
wavecode_0_smoothing=0.01
wavecode_0_r=0.000000
wavecode_0_g=1.000000
wavecode_0_b=1.000000
wavecode_0_a=1.000000
wavecode_0_x=0.5
wavecode_0_y=0.4
wave_0_per_point1=x=sample;
wave_0_per_point2=y=y+value1;

View File

@ -0,0 +1,26 @@
[preset00]
per_frame_1000=// simple wave
per_frame_1001=// MODE=6 Line
fDecay=0
warp=0.000000
wave_r=1
// MilkdropWaveform
nWaveMode=6
fWaveSmoothing=1.00
wave_x=0.600000
wave_y=0.5
// CustomWaveform
wavecode_0_enabled=1
wavecode_0_scaling=0.05
wavecode_0_smoothing=1.00
wavecode_0_r=0.000000
wavecode_0_g=1.000000
wavecode_0_b=1.000000
wavecode_0_a=1.000000
wavecode_0_x=0.5
wavecode_0_y=0.4
wave_0_per_point1=x=sample;
wave_0_per_point2=y=y+value1;

View File

@ -0,0 +1,26 @@
[preset00]
per_frame_1000=// simple wave
per_frame_1001=// MODE=6 Line
fDecay=0
warp=0.000000
wave_r=1
// MilkdropWaveform
nWaveMode=6
fWaveSmoothing=0.80
wave_x=0.600000
wave_y=0.5
// CustomWaveform
wavecode_0_enabled=1
wavecode_0_scaling=0.05
wavecode_0_smoothing=0.80
wavecode_0_r=0.000000
wavecode_0_g=1.000000
wavecode_0_b=1.000000
wavecode_0_a=1.000000
wavecode_0_x=0.5
wavecode_0_y=0.4
wave_0_per_point1=x=sample;
wave_0_per_point2=y=y+value1;

View File

@ -0,0 +1,25 @@
[preset00]
per_frame_1000=// simple wave
per_frame_1001=// MODE=6 Line
fDecay=0
warp=0.000000
wave_r=1
// MilkdropWaveform
nWaveMode=6
fWaveSmoothing=0.90
wave_x=0.600000
wave_y=0.5
// CustomWaveform
wavecode_0_enabled=1
wavecode_0_samples=512
wavecode_0_scaling=0.05
wavecode_0_smoothing=0.90
wavecode_0_r=0.000000
wavecode_0_g=1.000000
wavecode_0_b=1.000000
wavecode_0_a=1.000000
wave_0_per_point1=x=sample;
wave_0_per_point2=y=0.4+value1;

View File

@ -0,0 +1,26 @@
[preset00]
per_frame_1000=// simple wave
per_frame_1001=// MODE=6 Line
fDecay=0
warp=0.000000
wave_r=1
// MilkdropWaveform
nWaveMode=6
fWaveSmoothing=0.99
wave_x=0.600000
wave_y=0.5
// CustomWaveform
wavecode_0_enabled=1
wavecode_0_scaling=0.05
wavecode_0_smoothing=0.99
wavecode_0_r=0.000000
wavecode_0_g=1.000000
wavecode_0_b=1.000000
wavecode_0_a=1.000000
wavecode_0_x=0.5
wavecode_0_y=0.4
wave_0_per_point1=x=sample;
wave_0_per_point2=y=y+value1;

5
presets/tests/250-wavecode.milk Executable file → Normal file
View File

@ -9,7 +9,6 @@ fWaveAlpha=4.400000
fWaveScale=1.5
fZoomExponent=1.000000
zoom=1.000000
rot=0.006000
warp=0.000000
sx=1.000000
sy=1.000000
@ -30,7 +29,7 @@ wavecode_0_r=0.000000
wavecode_0_g=1.000000
wavecode_0_b=1.000000
wavecode_0_a=1.000000
wave_0_per_point41=x=x+value1;
wave_0_per_point42=y=y+value2;
wave_0_per_point41=x=x+value1/2
wave_0_per_point42=y=y+value2/2;
per_frame_1=zoom=1

View File

@ -0,0 +1,34 @@
[preset00]
per_frame_1000=// spectrum vs pcm
fDecay=0
warp=0.000000
wave_a=0
// spectrum=0
wavecode_0_enabled=1
wavecode_0_bspectrum=0
wavecode_0_scaling=0.1
wavecode_0_r=0.000000
wavecode_0_g=1.000000
wavecode_0_b=1.000000
wavecode_0_a=1.000000
wavecode_0_x=0.5
wavecode_0_y=0.75
wave_0_per_point1=x=sample;
wave_0_per_point2=y=y+value1;
// spectrum=1
wavecode_1_enabled=1
wavecode_1_bSpectrum=1
wavecode_1_scaling=1
wavecode_1_r=1.000000
wavecode_1_g=1.000000
wavecode_1_b=1.000000
wavecode_1_a=1.000000
wavecode_1_x=0.5
wavecode_1_y=0.25
wave_1_per_point1=x=sample;
wave_1_per_point2=y=0.25+value1;

View File

@ -3,6 +3,7 @@ fDecay=0.75
fWarpScale=2.853000
fZoomExponent=1.000000
warp=0.000000
wave_a=0
cy=1.0
cx=0.5
sx=1

View File

@ -429,6 +429,10 @@ void MilkdropPreset::initialize_PerPixelMeshes()
// Evaluates all per-pixel equations
void MilkdropPreset::evalPerPixelEqns()
{
// Quick bail out if there is nothing to do.
if (per_pixel_eqn_tree.empty())
return;
if (nullptr == per_pixel_program)
{
// This is a little forward looking, but if we want to JIT assignments expressions, we might

View File

@ -19,12 +19,14 @@ PresetInputs::PresetInputs() : PipelineContext()
void PresetInputs::update(const BeatDetect & music, const PipelineContext & context) {
// Reflect new values form the beat detection unit
this->bass = music.bass;
this->mid = music.mid;
this->treb = music.treb;
this->bass_att = music.bass_att;
this->mid_att = music.mid_att;
this->treb_att = music.treb_att;
// see https://github.com/projectM-visualizer/projectm/pull/348
// beatSensitivity code moved from BeatDetect to here
this->bass = music.bass * music.beatSensitivity;
this->mid = music.mid * music.beatSensitivity;
this->treb = music.treb * music.beatSensitivity;
this->bass_att = music.bass_att * music.beatSensitivity;
this->mid_att = music.mid_att * music.beatSensitivity;
this->treb_att = music.treb_att * music.beatSensitivity;
// Reflect new values from the pipeline context
this->fps = context.fps;

View File

@ -24,9 +24,9 @@
* Takes sound data from wherever and hands it back out.
* Returns PCM Data or spectrum data, or the derivative of the PCM data
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "Common.hpp"
#include "wipemalloc.h"
@ -34,371 +34,526 @@
#include "PCM.hpp"
#include <cassert>
int PCM::maxsamples = 2048;
//initPCM(int samples)
//
//Initializes the PCM buffer to
// number of samples specified.
#include <iostream>
PCM::PCM() {
_initPCM( 2048 );
// 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;
#ifdef DEBUG
std::cerr << "[PCM] MAX SAMPLES:" << maxsamples << std::endl;
#endif
}
void PCM::_initPCM(int samples) {
int i;
waveSmoothing = 0;
//Allocate memory for PCM data buffer
assert(samples == 2048);
PCMd = (float **)wipemalloc(2 * sizeof(float *));
PCMd[0] = (float *)wipemalloc(samples * sizeof(float));
PCMd[1] = (float *)wipemalloc(samples * sizeof(float));
//maxsamples=samples;
newsamples=0;
numsamples = maxsamples;
//Initialize buffers to 0
for (i=0;i<samples;i++)
public:
AutoLevel() : level(0.01),level_samples(0),level_sum(0),level_max(0),l0(-1),l1(-1),l2(-1)
{
PCMd[0][i]=0;
PCMd[1][i]=0;
}
start=0;
/*
* 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...
*/
//Allocate FFT workspace
// per rdft() documentation
// length of ip >= 2+sqrt(n) and length of w == n/2
// 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/2*sizeof(double));
ip = (int *)wipemalloc(34 * sizeof(int));
ip[0]=0;
/** PCM data */
// this->maxsamples = 2048;
// this->numsamples = 0;
// this->pcmdataL = NULL;
// this->pcmdataR = NULL;
/** Allocate PCM data structures */
pcmdataL=(float *)wipemalloc(this->maxsamples*sizeof(float));
pcmdataR=(float *)wipemalloc(this->maxsamples*sizeof(float));
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() {
free(pcmdataL);
free(pcmdataR);
free(w);
free(ip);
free(PCMd[0]);
free(PCMd[1]);
free(PCMd);
PCM::~PCM()
{
delete leveler;
free(w);
free(ip);
}
#include <iostream>
void PCM::addPCMfloat(const float *PCMdata, int samples)
void PCM::addPCMfloat(const float *PCMdata, size_t samples)
{
int i,j;
for(i=0;i<samples;i++)
float a,sum=0,max=0;
for (size_t i=0; i<samples; i++)
{
j=i+start;
size_t j=(i+start)%maxsamples;
a=pcmL[j] = PCMdata[i];
pcmR[j] = PCMdata[i];
sum += fabs(a);
max = fmax(max,a);
}
start = (start+samples)%maxsamples;
newsamples += samples;
level = leveler->updateLevel(samples, sum, max);
}
if (PCMdata[i] != 0 ) {
PCMd[0][j%maxsamples] = PCMdata[i];
PCMd[1][j%maxsamples] = PCMdata[i];
/* 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; i<samples; i++)
{
size_t j=(start+i)%maxsamples;
a = pcmL[j] = PCMdata[i*2];
b = pcmR[j] = PCMdata[i*2+1];
sum += fabs(a) + fabs(b);
max = fmax(fmax(max,fabs(a)),fabs(b));
}
start = (start + samples) % maxsamples;
newsamples += samples;
level = leveler->updateLevel(samples, sum/2, max);
}
}
else
{
PCMd[0][j % maxsamples] = 0;
PCMd[1][j % maxsamples] = 0;
}
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;i<samples;i++)
{
size_t j=(i+start) % maxsamples;
a=pcmL[j]=(PCMdata[0][i]/16384.0);
b=pcmR[j]=(PCMdata[1][i]/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::addPCM8(const unsigned char PCMdata[2][1024])
{
const int samples=1024;
float a,b,sum=0,max=0;
for (size_t i=0; i<samples; i++)
{
size_t j= (i+start) % maxsamples;
a=pcmL[j]=(((float)PCMdata[0][i] - 128.0) / 64 );
b=pcmR[j]=(((float)PCMdata[1][i] - 128.0) / 64 );
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::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; i<samples; i++)
{
size_t j = (i+start) % maxsamples;
a=pcmL[j]=(((float)PCMdata[0][i] - 128.0 ) / 64 );
b=pcmR[j]=(((float)PCMdata[1][i] - 128.0 ) / 64 );
sum += fabs(a) + fabs(b);
max = fmax(fmax(max,a),b);
}
start = (start + samples) % maxsamples;
newsamples += samples;
level = leveler->updateLevel(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;
}
start+=samples;
start=start%maxsamples;
// since we've already got the freq data laying around, let's use that for smoothing
_updateFFT();
newsamples+=samples;
if (newsamples>maxsamples) newsamples=maxsamples;
numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
getPCM(vdataL,FFT_LENGTH,0,1,0,0);
getPCM(vdataR,FFT_LENGTH,1,1,0,0);
}
// copy
double freq[FFT_LENGTH*2];
double *from = channel==0 ? freqL : freqR;
for (int i=0 ; i<FFT_LENGTH*2 ; i++)
freq[i] = from[i];
void PCM::addPCMfloat_2ch(const float *PCMdata, int samples)
{
int i,j;
for(i=0;i<samples;i+=2)
// The visible effects ramp up as you smoothing value gets close to 1.0 (consistent with milkdrop2)
if (1==0) // gaussian
{
j=(i/2)+start;
PCMd[0][j%maxsamples] = PCMdata[i];
PCMd[1][j%maxsamples] = PCMdata[i+1];
// precompute constant:
double k = -1.0 / ((1 - smoothing) * (1 - smoothing) * FFT_LENGTH * FFT_LENGTH);
for (int i = 1; i < FFT_LENGTH; i++)
{
float g = pow(2.718281828459045, i * i * k);
freq[i * 2] *= g;
freq[i * 2 + 1] *= g;
}
freq[1] *= pow(2.718281828459045, FFT_LENGTH*FFT_LENGTH*k);
}
else
{
// butterworth
// this might be slightly faster to compute. pow() is expensive
double k = 1.0 / ((1 - smoothing) * (1 - smoothing) * FFT_LENGTH * FFT_LENGTH);
for (int i = 1; i < FFT_LENGTH; i++)
{
float b = 1.0 / (1.0 + (i * i * k));
freq[i * 2] *= b;
freq[i * 2 + 1] *= b;
}
freq[1] *= 1.0 / (1.0 + (FFT_LENGTH*FFT_LENGTH*k));
}
start+=samples/2;
start=start%maxsamples;
// inverse fft
rdft(FFT_LENGTH*2, -1, freq, ip, w);
for (size_t j = 0; j < FFT_LENGTH*2; j++)
freq[j] *= 1.0 / FFT_LENGTH;
newsamples+=samples/2;
if (newsamples>maxsamples)
newsamples=maxsamples;
numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
getPCM(vdataL,FFT_LENGTH,0,1,0,0);
getPCM(vdataR,FFT_LENGTH,1,1,0,0);
// copy out with zero-padding if necessary
size_t count = samples<FFT_LENGTH ? samples : FFT_LENGTH;
for (size_t i=0 ; i<count ; i++)
data[i] = freq[i%(FFT_LENGTH*2)];
for (size_t i=count ; i<samples ; i++)
data[i] = 0;
}
void PCM::addPCM16Data(const short* pcm_data, short samples) {
int i, j;
for (i = 0; i < samples; ++i) {
j=i+start;
PCMd[0][j % maxsamples]=(pcm_data[i * 2 + 0]/16384.0);
PCMd[1][j % maxsamples]=(pcm_data[i * 2 + 1]/16384.0);
}
start = (start + samples) % maxsamples;
newsamples+=samples;
if (newsamples>maxsamples) newsamples=maxsamples;
numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
getPCM(vdataL,FFT_LENGTH,0,1,0,0);
getPCM(vdataR,FFT_LENGTH,1,1,0,0);
}
void PCM::addPCM16(short PCMdata[2][512])
/* NOTE: Still don't have real support for smoothing parameter, but this gets close to the milkdrop2 default look */
void PCM::getSpectrum(float *data, CHANNEL channel, size_t samples, float smoothing)
{
int i,j;
int samples=512;
assert(channel == 0 || channel == 1);
_updateFFT();
for(i=0;i<samples;i++)
{
j=i+start;
if ( PCMdata[0][i] != 0 && PCMdata[1][i] != 0 ) {
PCMd[0][j%maxsamples]=(PCMdata[0][i]/16384.0);
PCMd[1][j%maxsamples]=(PCMdata[1][i]/16384.0);
} else {
PCMd[0][j % maxsamples] = (float)0;
PCMd[1][j % maxsamples] = (float)0;
}
}
// printf("Added %d samples %d %d %f\n",samples,start,(start+samples)%maxsamples,PCM[0][start+10]);
start+=samples;
start=start%maxsamples;
newsamples+=samples;
if (newsamples>maxsamples) newsamples=maxsamples;
numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
getPCM(vdataL,FFT_LENGTH,0,1,0,0);
getPCM(vdataR,FFT_LENGTH,1,1,0,0);
float *spectrum = channel == 0 ? spectrumL : spectrumR;
if (smoothing == 0)
{
size_t count = samples <= FFT_LENGTH ? samples : FFT_LENGTH;
for (size_t i = 0; i < count; i++)
data[i] = spectrum[i];
for (size_t i = count; i < samples; i++)
data[0] = 0;
}
else
{
float l2 = 0, l1 =0 , c = 0, r1, r2;
r1 = spectrum[0]; r2 = spectrum[0+1];
for (size_t i = 0; i < samples; i++)
{
l2 = l1;
l1 = c;
c = r1;
r1 = r2;
r2 = (i + 2) >= samples ? 0 : spectrum[i + 2];
data[i] = (l2 + 4 * l1 + 6 * c + 4 * r1 + r2) / 16.0;
}
}
}
void PCM::addPCM8( unsigned char PCMdata[2][1024])
void PCM::_updateFFT()
{
int i,j;
int samples=1024;
for(i=0;i<samples;i++)
{
j=i+start;
if ( PCMdata[0][i] != 0 && PCMdata[1][i] != 0 ) {
PCMd[0][j%maxsamples]=( (float)( PCMdata[0][i] - 128.0 ) / 64 );
PCMd[1][j%maxsamples]=( (float)( PCMdata[1][i] - 128.0 ) / 64 );
} else {
PCMd[0][j % maxsamples] = 0;
PCMd[1][j % maxsamples] = 0;
}
}
// printf("Added %d samples %d %d %f\n",samples,start,(start+samples)%maxsamples,PCM[0][start+10]);
start+=samples;
start=start%maxsamples;
newsamples+=samples;
if (newsamples>maxsamples) newsamples=maxsamples;
numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
getPCM(vdataL,FFT_LENGTH,0,1,0,0);
getPCM(vdataR,FFT_LENGTH,1,1,0,0);
if (newsamples > 0)
{
_updateFFT(0);
_updateFFT(1);
newsamples = 0;
}
}
void PCM::addPCM8_512( const unsigned char PCMdata[2][512])
void PCM::_updateFFT(size_t channel)
{
int i,j;
int samples=512;
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);
for(i=0;i<samples;i++)
{
j=i+start;
if ( PCMdata[0][i] != 0 && PCMdata[1][i] != 0 ) {
PCMd[0][j%maxsamples]=( (float)( PCMdata[0][i] - 128.0 ) / 64 );
PCMd[1][j%maxsamples]=( (float)( PCMdata[1][i] - 128.0 ) / 64 );
} else {
PCMd[0][j % maxsamples] = 0;
PCMd[1][j % maxsamples] = 0;
}
}
// printf("Added %d samples %d %d %f\n",samples,start,(start+samples)%maxsamples,PCM[0][start+10]);
start+=samples;
start=start%maxsamples;
newsamples+=samples;
if (newsamples>maxsamples) newsamples=maxsamples;
numsamples = getPCMnew(pcmdataR,1,0,waveSmoothing,0,0);
getPCMnew(pcmdataL,0,0,waveSmoothing,0,1);
getPCM(vdataL,FFT_LENGTH,0,1,0,0);
getPCM(vdataR,FFT_LENGTH,1,1,0,0);
// compute magnitude data (m^2 actually)
float *spectrum = channel==0 ? spectrumL : spectrumR;
for (size_t i=1 ; i<FFT_LENGTH ; i++)
{
double m2 = (freq[i * 2] * freq[i * 2] + freq[i * 2 + 1] * freq[i * 2 + 1]);
spectrum[i-1] = m2 * ((double)i)/FFT_LENGTH;
}
spectrum[FFT_LENGTH-1] = freq[1] * freq[1];
}
//puts sound data requested at provided pointer
//
//samples is number of PCM samples to return
//freq = 0 gives PCM data
//freq = 1 gives FFT data
//smoothing is the smoothing coefficient
//returned values are normalized from -1 to 1
void PCM::getPCM(float *PCMdata, int samples, int channel, int freq, float smoothing, int derive)
inline double constrain(double a, double mn, double mx)
{
if (smoothing == 0)
{
for (int i = 0; i < samples; i++)
{
int index = start - 1 - i;
if (index < 0)
index = maxsamples + index;
PCMdata[i] = PCMd[channel][index];
}
}
else
{
int index=start-1;
if (index<0)
index=maxsamples+index;
PCMdata[0] = PCMd[channel][index];
for (int i = 1; i < samples; i++)
{
index = start - 1 - i;
if (index < 0)
index = maxsamples + index;
PCMdata[i] = (1 - smoothing) * PCMd[channel][index] + smoothing * PCMdata[i - 1];
}
}
//return derivative of PCM data
if (derive)
{
for(int i=0;i<samples-1;i++)
{
PCMdata[i]=PCMdata[i]-PCMdata[i+1];
}
PCMdata[samples-1]=0;
}
//return frequency data instead of PCM (perform FFT)
if (freq)
{
// NOTE some presets set bSpectrum=1 and samples!=2^n, not sure what rdft() does with that
assert(samples <= 1024);
samples = std::min(1024,samples);
double temppcm[1024];
for (int i=0;i<samples;i++)
{temppcm[i]=(double)PCMdata[i];}
rdft(samples, 1, temppcm, ip, w);
for (int j=0;j<samples;j++)
{PCMdata[j]=(float)temppcm[j];}
}
return a>mx ? mx : a<mn ? mn : a;
}
//getPCMnew
//
//Like getPCM except it returns all new samples in the buffer
//the actual return value is the number of samples, up to maxsamples.
//the passed pointer, PCMData, must bee able to hold up to maxsamples
int PCM::getPCMnew(float *PCMdata, int channel, int freq, float smoothing, int derive, int reset)
// pull data from circular buffer
void PCM::_copyPCM(float *to, int channel, size_t count)
{
int i,index;
assert(channel == 0 || channel == 1);
assert(count < maxsamples);
const float *from = channel==0 ? pcmL : pcmR;
const double volume = 1.0 / level;
for (size_t i=0, pos=start ; i<count ; i++)
{
if (pos==0)
pos = maxsamples;
to[i] = from[--pos] * volume;
}
}
index=start-1;
if (index<0) index=maxsamples+index;
PCMdata[0]=PCMd[channel][index];
for(i=1;i<newsamples;i++)
{
index=start-1-i;
if (index<0) index=maxsamples+index;
PCMdata[i]=(1-smoothing)*PCMd[channel][index]+smoothing*PCMdata[i-1];
}
//return derivative of PCM data
if(derive)
{
for(i=0;i<newsamples-1;i++)
{
PCMdata[i]=PCMdata[i]-PCMdata[i+1];
}
PCMdata[newsamples-1]=0;
}
//return frequency data instead of PCM (perform FFT)
// if (freq) rdft(samples, 1, PCMdata, ip, w);
i=newsamples;
if (reset) newsamples=0;
return i;
void PCM::_copyPCM(double *to, int channel, size_t count)
{
assert(channel == 0 || channel == 1);
const float *from = channel==0 ? pcmL : pcmR;
const double volume = 1.0 / level;
for (size_t i=0, pos=start ; i<count ; i++)
{
if (pos==0)
pos = maxsamples;
to[i] = from[--pos] * volume;
}
}
//Free stuff
void PCM::freePCM() {
free(PCMd[0]);
free(PCMd[1]);
free(PCMd);
void PCM::freePCM()
{
free(ip);
free(w);
PCMd = NULL;
ip = NULL;
w = NULL;
}
// TESTS
#include "TestRunner.hpp"
#ifndef NDEBUG
#include <PresetLoader.hpp>
#define TEST(cond) if (!verify(__FILE__ ": " #cond,cond)) return false
#define TEST2(str,cond) if (!verify(str,cond)) return false
struct PCMTest : public Test
{
PCMTest() : Test("PCMTest")
{}
bool eq(float a, float b)
{
return fabs(a-b) < (fabs(a)+fabs(b) + 1)/1000.0f;
}
public:
/* smoke test for each addPCM method */
bool test_addpcm()
{
PCM pcm;
// mono float
{
const size_t samples = 301;
float *data = new float[samples];
for (size_t i = 0; i < samples; i++)
data[i] = ((float) i) / (samples - 1);
for (size_t i = 0; i < 10; i++)
pcm.addPCMfloat(data, samples);
float *copy = new float[samples];
pcm.level = 1.0;
pcm._copyPCM(copy, 0, samples);
for (size_t i = 0; i < samples; i++)
TEST(eq(copy[i],((float)samples - 1 - i) / (samples - 1)));
pcm._copyPCM(copy, 1, samples);
for (size_t i = 0; i < samples; i++)
TEST(eq(copy[i], ((float)samples - 1 - i) / (samples - 1)));
free(data);
free(copy);
}
// float_2ch
{
const size_t samples = 301;
float *data = new float[samples*2];
for (size_t i = 0; i < samples; i++)
{
data[i*2] = ((float) i) / (samples - 1);
data[i*2+1] = 1.0-data[i*2] ;
}
for (size_t i = 0; i < 10; i++)
pcm.addPCMfloat_2ch(data, samples*2);
float *copy0 = new float[samples];
float *copy1 = new float[samples];
pcm.level = 1;
pcm._copyPCM(copy0, 0, samples);
pcm._copyPCM(copy1, 1, samples);
for (size_t i = 0; i < samples; i++)
TEST(eq(1.0,copy0[i]+copy1[i]));
free(data);
free(copy0);
free(copy1);
}
// void PCM::addPCM16Data(const short* pcm_data, size_t samples)
// void PCM::addPCM16(const short PCMdata[2][512])
// void PCM::addPCM8(const unsigned char PCMdata[2][1024])
// void PCM::addPCM8_512(const unsigned char PCMdata[2][512])
return true;
}
bool test_fft()
{
PCM pcm;
// low frequency
{
const size_t samples = 1024;
float *data = new float[samples * 2];
for (size_t i = 0; i < samples; i++)
{
float f = 2 * 3.141592653589793 * ((double) i) / (samples - 1);
data[i * 2] = sin(f);
data[i * 2 + 1] = sin(f + 1.0); // out of phase
}
pcm.addPCMfloat_2ch(data, samples * 2);
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]));
TEST(freq0[0] > 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<FFT_LENGTH-1 ; i++)
TEST(0==freq0[i]);
TEST(freq0[FFT_LENGTH-1] > 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

View File

@ -29,53 +29,100 @@
#ifndef _PCM_H
#define _PCM_H
#include <stdlib.h>
#include "dlldefs.h"
// 1024 is more computationally intensive, but maybe better at detecting lower bass
#define FFT_LENGTH 1024
// FFT_LENGTH is number of magnitude values available from getSpectrum().
// Internally this is generated using 2xFFT_LENGTH samples per channel.
#define FFT_LENGTH 512
class Test;
class AutoLevel;
enum CHANNEL
{
CHANNEL_L = 0,
CHANNEL_0 = 0,
CHANNEL_R = 1,
CHANNEL_1 = 1
};
class
#ifdef WIN32
DLLEXPORT
#endif
PCM {
PCM
{
public:
float **PCMd;
int start;
/* maximum number of sound samples that are actually stored. */
static const size_t maxsamples=2048;
/** Use wave smoothing */
float waveSmoothing;
int *ip;
double *w;
int newsamples;
int numsamples; //size of new PCM info
float *pcmdataL; //holder for most recent pcm data
float *pcmdataR; //holder for most recent pcm data
/** PCM data */
float vdataL[FFT_LENGTH]; //holders for FFT data (spectrum)
float vdataR[FFT_LENGTH];
static int maxsamples;
PCM();
~PCM();
void addPCMfloat(const float *PCMdata, int samples);
void addPCMfloat_2ch(const float *PCMdata, int samples);
void addPCM16(short [2][512]);
void addPCM16Data(const short* pcm_data, short samples);
void addPCM8( unsigned char [2][1024]);
void addPCM8_512( const unsigned char [2][512]);
void getPCM(float *data, int samples, int channel, int freq, float smoothing, int derive);
void freePCM();
int getPCMnew(float *PCMdata, int channel, int freq, float smoothing, int derive,int reset);
void addPCMfloat( const float *PCMdata, size_t samples );
void addPCMfloat_2ch( const float *PCMdata, size_t count );
void addPCM16( const short [2][512] );
void addPCM16Data( const short* pcm_data, size_t samples );
void addPCM8( const unsigned char [2][1024] );
void addPCM8_512( const unsigned char [2][512] );
/**
* PCM data
* When smoothing=0 is copied directly from PCM buffers. smoothing=1.0 is almost a straight line.
* The returned data will 'wrap' if more than maxsamples are requested.
*/
void getPCM(float *data, CHANNEL channel, size_t samples, float smoothing);
/** Spectrum data
* Smoothing is not fully implemented, only none (smoothing==0) or a little (smoothing!=0).
* The returned data will be zero padded if more than FFT_LENGTH values are requested
*/
void getSpectrum(float *data, CHANNEL channel, size_t samples, float smoothing);
static Test* test();
private:
void _initPCM(int maxsamples);
// mem-usage:
// pcmd 2x2048*4b = 16K
// vdata 2x512x2*8b = 16K
// spectrum 2x512*4b = 4k
// w = 512*8b = 4k
};
// circular PCM buffer
// adjust "volume" of PCM data as we go, this simplifies everything downstream...
// normalize to range [-1.0,1.0]
float pcmL[maxsamples];
float pcmR[maxsamples];
int start;
size_t newsamples;
// raw FFT data
double freqL[FFT_LENGTH*2];
double freqR[FFT_LENGTH*2];
// magnitude data
float spectrumL[FFT_LENGTH];
float spectrumR[FFT_LENGTH];
// for FFT library
int *ip;
double *w;
void freePCM();
// copy data out of the circular PCM buffer
void _copyPCM(float *PCMdata, int channel, size_t count);
void _copyPCM(double *PCMdata, int channel, size_t count);
// update FFT data if new samples are available.
void _updateFFT();
void _updateFFT(size_t channel);
friend class PCMTest;
// state for tracking audio level
double level;
class AutoLevel *leveler;
};
#endif /** !_PCM_H */

View File

@ -104,60 +104,41 @@ void BeatDetect::detectFromSamples()
treb=0;
vol=0;
// TODO: get sample rate from PCM? Assume 44100
getBeatVals(44100.0f, FFT_LENGTH, pcm->pcmdataL, pcm->pcmdataR);
float vdataL[FFT_LENGTH];
float vdataR[FFT_LENGTH];
pcm->getSpectrum(vdataL, CHANNEL_0, FFT_LENGTH, 0.0);
pcm->getSpectrum(vdataR, CHANNEL_1, FFT_LENGTH, 0.0);
// OK, we're not really using this number 44.1 anywhere
// This is more of a nod to the fact that if the actually data rate is REALLY different
// then in theory the bass/mid/treb ranges should be adjusted.
// In practice, I doubt it would adversely affect the actually display very much
getBeatVals(44100.0f, FFT_LENGTH, vdataL, vdataR);
}
float BeatDetect::getPCMScale()
{
// the constant here just depends on the particulars of getBeatVals(), the
// range of vol_history, and what "looks right".
// larger value means larger, more jagged waveform.
// this is also impacted by beatSensitivity.
return (1.5 / fmax(0.0001f,sqrtf(vol_history)))*beatSensitivity;
}
void BeatDetect::getBeatVals( float samplerate, unsigned fft_length, float *vdataL, float *vdataR )
{
assert( 512==fft_length || 1024==fft_length ); // should be power of 2, expect >= 512
assert(fft_length >= 256);
unsigned ranges[4] = {0, 3, 23, 255};
// TODO: compute ranges based on samplerate
// roughly aiming or basee-mid cutoff of 220ish and mid-treb cutoff of 2000ish, if I did my math right
unsigned ranges512[4] = {0, 3 /* 3*441000/512 = =258 */, 23 /* 23*441000/512 = =1981 */ , 200};
unsigned ranges1024[4] = {0, 5 /* 5*44100/1024 == 215 */, 46 /* 46*44100/1024 == 1981 */, 400};
unsigned *ranges = fft_length==1024 ? ranges1024 : ranges512;
bass_instant = 0;
for (unsigned i=ranges[0]+1 ; i<=ranges[1] ; i++)
{
bass_instant += (vdataL[i*2]*vdataL[i*2]) + (vdataR[i*2]*vdataR[i*2]);
}
bass_instant *= 100.0/(ranges[1]-ranges[0]);
bass_instant=0;
for (unsigned i=ranges[0] ; i<ranges[1] ; i++)
bass_instant += vdataL[i] + vdataR[i];
bass_history -= bass_buffer[beat_buffer_pos] * (1.0/BEAT_HISTORY_LENGTH);
bass_buffer[beat_buffer_pos] = bass_instant;
bass_history += bass_instant * (1.0/BEAT_HISTORY_LENGTH);
mid_instant = 0;
for (unsigned i=ranges[1]+1 ; i<=ranges[2] ; i++)
{
mid_instant += (vdataL[i*2]*vdataL[i*2]) + (vdataR[i*2]*vdataR[i*2]);
}
mid_instant *= 100.0/(ranges[2]-ranges[1]);
mid_instant=0;
for (unsigned i=ranges[1] ; i<ranges[2] ; i++)
mid_instant += vdataL[i] + vdataR[i];
mid_history -= mid_buffer[beat_buffer_pos] * (1.0/BEAT_HISTORY_LENGTH);
mid_buffer[beat_buffer_pos] = mid_instant;
mid_history += mid_instant * (1.0/BEAT_HISTORY_LENGTH);
treb_instant = 0;
for (unsigned i=ranges[2]+1 ; i<=ranges[3] ; i++)
{
treb_instant += (vdataL[i*2]*vdataL[i*2]) + (vdataR[i*2]*vdataR[i*2]);
}
treb_instant *= 90.0/(ranges[3]-ranges[2]);
for (unsigned i=ranges[2] ; i<ranges[3] ; i++)
treb_instant += vdataL[i] + vdataR[i];
treb_history -= treb_buffer[beat_buffer_pos] * (1.0/BEAT_HISTORY_LENGTH);
treb_buffer[beat_buffer_pos] = treb_instant;
treb_history += treb_instant * (1.0/BEAT_HISTORY_LENGTH);
@ -168,10 +149,10 @@ void BeatDetect::getBeatVals( float samplerate, unsigned fft_length, float *vdat
vol_history += vol_instant * (1.0/BEAT_HISTORY_LENGTH);
// fprintf(stderr, "%6.3f %6.2f %6.3f\n", bass_history/vol_history, mid_history/vol_history, treb_history/vol_history);
bass = bass_instant / fmax(0.0001, 1.3 * bass_history + 0.2*vol_history);
mid = mid_instant / fmax(0.0001, 1.3 * mid_history + 0.2*vol_history);
treb = treb_instant / fmax(0.0001, 1.3 * treb_history + 0.2*vol_history);
vol = vol_instant / fmax(0.0001, 1.5f * vol_history);
bass = bass_instant / fmax(0.0001, bass_history);
mid = mid_instant / fmax(0.0001, mid_history);
treb = treb_instant / fmax(0.0001, treb_history);
vol = vol_instant / fmax(0.0001, vol_history);
if ( projectM_isnan( treb ) ) {
treb = 0.0;
@ -188,18 +169,6 @@ void BeatDetect::getBeatVals( float samplerate, unsigned fft_length, float *vdat
bass_att = .6f * bass_att + .4f * bass;
vol_att = .6f * vol_att + .4f * vol;
// Use beat sensitivity as a multiplier
// 0 is "dead"
// 5 is pretty wild so above that doesn't make sense
bass_att = bass_att * beatSensitivity;
bass = bass * beatSensitivity;
mid_att = mid_att * beatSensitivity;
mid = mid * beatSensitivity;
treb_att = treb_att * beatSensitivity;
treb = treb * beatSensitivity;
vol_att = vol_att * beatSensitivity;
vol = vol * beatSensitivity;
if (bass_att>100) bass_att=100;
if (bass >100) bass=100;
if (mid_att>100) mid_att=100;

View File

@ -44,11 +44,14 @@
class DLLEXPORT BeatDetect
{
public:
float treb ;
// Does this really belong here? maybe belongs on projectM.Settings?
float beatSensitivity;
float treb ;
float mid ;
float bass ;
float vol_old ;
float beatSensitivity;
float treb_att ;
float mid_att ;
float bass_att ;
@ -67,7 +70,10 @@ class DLLEXPORT BeatDetect
// getPCMScale() was added to address https://github.com/projectM-visualizer/projectm/issues/161
// Returning 1.0 results in using the raw PCM data, which can make the presets look pretty unresponsive
// if the application volume is low.
float getPCMScale();
float getPCMScale()
{
return beatSensitivity;
}
private:
int beat_buffer_pos;

View File

@ -35,14 +35,11 @@ void MilkdropWaveform::InitVertexAttrib() {
void MilkdropWaveform::Draw(RenderContext &context)
{
// wavearray is 2048 so don't exceed that
// TODO use named constant
if (samples > 2048)
samples = 2048;
if (samples > (int)PCM::maxsamples)
samples = (int)PCM::maxsamples;
// NOTE MilkdropWaveform does not have a "samples" parameter
// so this member variable is just being set in WaveformMath and used here
WaveformMath(context);
assert(samples<=512);
for (int waveno=1 ; waveno<=(two_waves?2:1) ; waveno++)
{
@ -192,10 +189,22 @@ void MilkdropWaveform::MaximizeColors(RenderContext &context)
void MilkdropWaveform::WaveformMath(RenderContext &context)
{
const float *pcmdataR = context.beatDetect->pcm->pcmdataR;
const float *pcmdataL = context.beatDetect->pcm->pcmdataL;
// scale PCM data based on vol_history to make it more or less independent of the application output volume
// NOTE: these buffer sizes are based on the MilkdropWaveformMode implementation later in this method.
float pcmdataL[512];
float pcmdataR[512];
context.beatDetect->pcm->getPCM(pcmdataL, CHANNEL_0, 512, smoothing);
context.beatDetect->pcm->getPCM(pcmdataR, CHANNEL_1, 512, smoothing);
// tie size of waveform to beatSensitivity
const float vol_scale = context.beatDetect->getPCMScale();
if (vol_scale != 1.0)
{
for (int i=0 ; i<512 ; i++)
{
pcmdataL[i] *= vol_scale;
pcmdataR[i] *= vol_scale;
}
}
float r2, theta;
@ -219,18 +228,18 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
rot = 0;
aspectScale=1.0;
samples = context.beatDetect->pcm->numsamples;
samples = 512;
float inv_nverts_minus_one = 1.0f/(float)(samples);
float last_value = vol_scale * (pcmdataR[samples-1]+pcmdataL[samples-1]);
float first_value = vol_scale * (pcmdataR[0]+pcmdataL[0]);
float last_value = pcmdataR[samples-1]+pcmdataL[samples-1];
float first_value = pcmdataR[0]+pcmdataL[0];
float offset = first_value-last_value;
for ( int i=0;i<samples;i++)
{
float value = vol_scale * (pcmdataR[i]+pcmdataL[i]);
float value = pcmdataR[i]+pcmdataL[i];
value += offset * (i/(float)samples);
r2=(0.5f + 0.4f*.12f*value*scale + mystery)*.5f;
@ -250,8 +259,8 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
samples = 512-32;
for ( int i=0;i<512-32;i++)
{
theta=vol_scale*pcmdataL[i+32]*0.06f*scale * 1.57f + context.time*2.3f;
r2=(0.53f + 0.43f*vol_scale*pcmdataR[i]*0.12f*scale+ mystery)*.5f;
theta=pcmdataL[i+32]*0.06f*scale * 1.57f + context.time*2.3f;
r2=(0.53f + 0.43f*pcmdataR[i]*0.12f*scale+ mystery)*.5f;
wavearray[i][0]=(r2*cos(theta)*(context.aspectCorrect ? context.aspectRatio : 1.0f)+x);
wavearray[i][1]=(r2*sin(theta)+temp_y);
@ -267,8 +276,8 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
for ( int i=0;i<512-32;i++)
{
wavearray[i][0]=(vol_scale*pcmdataR[i]*scale*0.5f*(context.aspectCorrect ? context.aspectRatio : 1.0f) + x);
wavearray[i][1]=(vol_scale*pcmdataL[i+32]*scale*0.5f + temp_y);
wavearray[i][0]=(pcmdataR[i]*scale*0.5f*(context.aspectCorrect ? context.aspectRatio : 1.0f) + x);
wavearray[i][1]=(pcmdataL[i+32]*scale*0.5f + temp_y);
}
break;
@ -282,8 +291,8 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
for ( int i=0;i<512-32;i++)
{
wavearray[i][0]=(vol_scale*pcmdataR[i] * scale*0.5f + x);
wavearray[i][1]=( (vol_scale*pcmdataL[i+32]*scale*0.5f + temp_y));
wavearray[i][0]=(pcmdataR[i] * scale*0.5f + x);
wavearray[i][1]=( (pcmdataL[i+32]*scale*0.5f + temp_y));
}
break;
@ -301,8 +310,8 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
for (int i=0; i<512-32; i++)
{
xx[i] = -1.0f + 2.0f*(i/(512.0f-32.0f)) + x;
yy[i] = 0.4f* vol_scale*pcmdataL[i]*0.47f*scale + temp_y;
xx[i] += 0.4f* vol_scale*pcmdataR[i]*0.44f*scale;
yy[i] = 0.4f* pcmdataL[i]*0.47f*scale + temp_y;
xx[i] += 0.4f* pcmdataR[i]*0.44f*scale;
if (i>1)
{
@ -311,7 +320,8 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
}
wavearray[i][0]=xx[i];
wavearray[i][1]=yy[i];
} }
}
}
break;
case Blob5://EXPERIMENTAL
@ -325,8 +335,8 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
for ( int i=0;i<512-32;i++)
{
float x0 = (vol_scale*pcmdataR[i]*vol_scale*pcmdataL[i+32] + vol_scale*pcmdataL[i+32]*vol_scale*pcmdataR[i]);
float y0 = (vol_scale*pcmdataR[i]*vol_scale*pcmdataR[i] - vol_scale*pcmdataL[i+32]*vol_scale*pcmdataL[i+32]);
float x0 = (pcmdataR[i]*pcmdataL[i+32] + pcmdataL[i+32]*pcmdataR[i]);
float y0 = (pcmdataR[i]*pcmdataR[i] - pcmdataL[i+32]*pcmdataL[i+32]);
wavearray[i][0]=((x0*cos_rot - y0*sin_rot)*scale*0.5f*(context.aspectCorrect ? context.aspectRatio : 1.0f) + x);
wavearray[i][1]=( (x0*sin_rot + y0*cos_rot)*scale*0.5f + temp_y);
}
@ -336,16 +346,16 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
wave_x_temp=-2*0.4142f*(fabs(fabs(mystery)-.5f)-.5f);
samples = 512;
rot = -mystery*90;
aspectScale =1.0f+wave_x_temp;
wave_x_temp=-1*(x-1.0f);
samples = context.beatDetect->pcm->numsamples;
for ( int i=0; i<samples; i++)
for ( int i=0; i< samples;i++)
{
wavearray[i][0]=i/(float) samples;
wavearray[i][1]=vol_scale*pcmdataR[i]*.04f*scale+wave_x_temp;
wavearray[i][1]=pcmdataR[i]*.04f*scale+wave_x_temp;
}
// printf("%f %f\n",renderTarget->texsize*wave_y_temp,wave_y_temp);
@ -359,8 +369,7 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
rot = -mystery*90;
aspectScale =1.0f+wave_x_temp;
samples = context.beatDetect->pcm->numsamples;
samples = 512;
two_waves = true;
const float y_adj = y*y*.5f;
@ -370,13 +379,13 @@ void MilkdropWaveform::WaveformMath(RenderContext &context)
for ( int i=0;i<samples;i++)
{
wavearray[i][0]=i/((float) samples);
wavearray[i][1]= vol_scale*pcmdataL[i]*.04f*scale+(wave_y_temp+y_adj);
wavearray[i][1]= pcmdataL[i]*.04f*scale+(wave_y_temp+y_adj);
}
for ( int i=0;i<samples;i++)
{
wavearray2[i][0]=i/((float) samples);
wavearray2[i][1]=vol_scale*pcmdataR[i]*.04f*scale+(wave_y_temp-y_adj);
wavearray2[i][1]=pcmdataR[i]*.04f*scale+(wave_y_temp-y_adj);
}
break;

View File

@ -19,9 +19,8 @@
typedef float floatPair[2];
Waveform::Waveform(int _samples)
: RenderItem(),samples(_samples), points(_samples), pointContext(_samples)
: RenderItem(), samples(_samples), points(_samples), pointContext(_samples)
{
spectrum = false; /* spectrum data or pcm data */
dots = false; /* draw wave as dots or lines */
thick = false; /* draw thicker lines */
@ -43,37 +42,38 @@ void Waveform::InitVertexAttrib() {
}
void Waveform::Draw(RenderContext &context)
{
{
// scale PCM data based on vol_history to make it more or less independent of the application output volume
const float vol_scale = context.beatDetect->getPCMScale();
const float vol_scale = context.beatDetect->getPCMScale();
// Make sure samples<=points.size(). We could reallocate points, but 512 is probably big enough.
size_t samples_count = this->samples;
if (samples_count > this->points.size())
samples_count = this->points.size();
// Make sure samples<=points.size(). We could reallocate points, but 512 is probably big enough.
size_t samples_count = this->samples;
if (samples_count > this->points.size())
samples_count = this->points.size();
float *value1 = new float[samples_count];
float *value2 = new float[samples_count];
context.beatDetect->pcm->getPCM( value1, samples_count, 0, spectrum, smoothing, 0);
context.beatDetect->pcm->getPCM( value2, samples_count, 1, spectrum, smoothing, 0);
float mult = scaling*( spectrum ? 0.015f :1.0f);
#ifdef WIN32
std::transform(&value1[0], &value1[samples_count], &value1[0], bind2nd(std::multiplies<float>(), mult));
std::transform(&value2[0], &value2[samples_count], &value2[0], bind2nd(std::multiplies<float>(), mult));
#else
std::transform(&value1[0], &value1[samples_count], &value1[0], std::bind(std::multiplies<float>(), std::placeholders::_1, mult));
std::transform(&value2[0], &value2[samples_count], &value2[0], std::bind(std::multiplies<float>(), std::placeholders::_1, mult));
#endif /** WIN32 */
float *value1 = new float[samples_count];
float *value2 = new float[samples_count];
if (spectrum)
{
// TODO support smoothing parameter for getSpectrum()
context.beatDetect->pcm->getSpectrum( value1, CHANNEL_0, samples_count, 1.0 );
context.beatDetect->pcm->getSpectrum( value2, CHANNEL_1, samples_count, 1.0 );
}
else
{
context.beatDetect->pcm->getPCM( value1, CHANNEL_0, samples_count, smoothing );
context.beatDetect->pcm->getPCM( value2, CHANNEL_1, samples_count, smoothing );
}
const float mult = scaling * vol_scale * (spectrum ? 0.005f : 1.0f);
WaveformContext waveContext(samples_count, context.beatDetect);
for(size_t x=0;x< samples_count;x++)
for (size_t x=0;x< samples_count;x++)
{
waveContext.sample = x/(float)(samples_count - 1);
waveContext.sample_int = x;
waveContext.left = vol_scale * value1[x];
waveContext.right = vol_scale * value2[x];
waveContext.left = value1[x] * mult;
waveContext.right = value2[x] * mult;
points[x] = PerPoint(points[x],waveContext);
}
@ -129,4 +129,4 @@ void Waveform::Draw(RenderContext &context)
delete[] value1;
delete[] value2;
}
}

View File

@ -19,6 +19,7 @@ bool TestRunner::run()
tests.push_back(Param::test());
tests.push_back(Parser::test());
tests.push_back(Expr::test());
tests.push_back(PCM::test());
}
int count = 0;