current version.. forgot to commit

This commit is contained in:
2018-02-03 21:02:04 +01:00
parent fb0ff1c076
commit 431436a822
17 changed files with 599 additions and 101 deletions

114
lib/BassDetection.h Executable file
View File

@@ -0,0 +1,114 @@
#ifndef BASSDETECTION_H
#define BASSDETECTION_H
//#define PLOT_ME
#ifdef PLOT_ME
#include <KLib/misc/gnuplot/Gnuplot.h>
#include <KLib/misc/gnuplot/GnuplotPlot.h>
#include <KLib/misc/gnuplot/GnuplotPlotElementLines.h>
#include <KLib/misc/gnuplot/GnuplotSplot.h>
#endif
#include "BiquadFilterGate.h"
#include "MovingAVG.h"
#include "EndlessAVG.h"
#include <iostream>
template <typename Scalar> class BassDetection {
#ifdef PLOT_ME
K::Gnuplot gp;
K::GnuplotPlot plot;
K::GnuplotPlotElementLines lines0;
#endif
BiquadFilterGate<1> filter;
MovingAVG<float> avg;
MovingAVG<float> avgLong;
public:
/** setup */
BassDetection() : avg(2500), avgLong(100000) {
setSampleRate(44100);
#ifdef PLOT_ME
plot.add(&lines0);
#endif
avgLong.add(1);
}
void setSampleRate(int srate) {
std::cout << "setting sample-rate to " << srate << std::endl;
filter.setLowPass(80, 1, srate);
}
/** add single value */
float add(Scalar s) {
const float val = s / 32768.0f;
const float res = filter.filter(0, val);
avg.add(res*res);
avgLong.add(res*res);
const float res2 = avg.get() / avgLong.get();
avgLong.add(res2);
static float prev = 0;
static int x = 0; ++x;
if (x % 1000 == 0) {
const float delta = res2 - prev;
prev = res2;
#ifdef PLOT_ME
lines0.add(K::GnuplotPoint2(x, delta));
#endif
// debug view?
//if (x % 6000 == 0) {show();}
#ifdef PLOT_ME
show();
#endif
if (x % 1000 == 0) {
return delta;
}
}
return 0;
}
#ifdef PLOT_ME
void show() {
int limit = 500;
int d0 = lines0.size() - limit;
if (d0 > 0) {
lines0.remove(0, d0);
}
int x0 = lines0[0].x;
int x1 = lines0[lines0.size()-1].x;
plot.getAxisX().setRange(x0, x1);
plot.getAxisY().setRange(-1, +5);
//plot.getAxisY().setRange(-32768, +32768);
gp.draw(plot);
gp.flush();
}
#endif
};
#endif // BASSDETECTION_H

189
lib/BeatDetection.h Executable file
View File

@@ -0,0 +1,189 @@
#ifndef ANALYZER_H
#define ANALYZER_H
//#define PLOT_ME
#ifdef PLOT_ME
#include <KLib/misc/gnuplot/Gnuplot.h>
#include <KLib/misc/gnuplot/GnuplotPlot.h>
#include <KLib/misc/gnuplot/GnuplotPlotElementLines.h>
#include <KLib/misc/gnuplot/GnuplotSplot.h>
#endif
#include "BiquadFilterGate.h"
#include "MovingAVG.h"
#include "EndlessAVG.h"
#include <iostream>
template <typename Scalar> struct BeatBand {
BiquadFilterGate<1> filter;
MovingAVG<Scalar> avgShort;
MovingAVG<Scalar> avgLong;
EndlessAVG<Scalar> avgAll;
BeatBand() : avgShort(800), avgLong(40000) {
avgAll.add(1);
}
float getMul() const {
return avgAll.get();
}
float add(Scalar s) {
const Scalar filtered = filter.filter(0, s) / getMul();
const Scalar filtered2 = filtered*filtered;
avgShort.add(filtered2); // short-term average (1/40 sec)
avgLong.add(filtered2); // long-term average (1sec)
avgAll.add(filtered2); // whole average (everything)
if (avgLong.get() < 0.0001) {return 0;}
const float delta = std::abs(avgShort.get()) / std::abs(avgLong.get());
//return (delta);
return avgShort.get();
}
};
template <typename Scalar> class BeatDetection {
#ifdef PLOT_ME
K::Gnuplot gp;
K::GnuplotPlot plot;
K::GnuplotPlotElementLines lines0;
#endif
BeatBand<float> band0;
BeatBand<float> band1;
BeatBand<float> band2;
BeatBand<float> band3;
MovingAVG<float> avg1;
MovingAVG<float> avg2;
public:
/** setup */
BeatDetection() : avg1(600), avg2(10000) {
setSampleRate(44100);
#ifdef PLOT_ME
plot.add(&lines0);
#endif
}
void setSampleRate(int srate) {
std::cout << "setting sample-rate to " << srate << std::endl;
//band0.filter.setBandPass(70, 1, srate); // base
band0.filter.setLowPass(100, 1, srate);
//band0.mul = 1;
band1.filter.setBandPass(3500, 1.0, srate); // claps
//band1.mul = 3.5;
band2.filter.setBandPass(10000, 0.4, srate); // snare???
//band2.mul = 5.5;
}
float area = 0;
int block = 0;
/** add single value */
bool add(Scalar s) {
// debug view
static int xxx = 0; ++xxx;
bool beat = false;
const float val = s / 32768.0f; // not needed
static int x = 0; x += 1;
float y0 = band0.add(val);
float y1 = 0;//band1.add(val);
float y2 = 0;//band2.add(val);
float y = std::abs(y0)+std::abs(y1*0.5)+std::abs(y2*0.7);
if (block > 0) {--block;}
if (y == y) {
//yy = yy * 0.99 + y * 0.01;
avg1.add(y);
avg2.add(y);
float yy = avg1.get() - avg2.get();
if (yy > 0.5 && !block) {
beat = true;
block = 9000;
}
// worked somewhat
// float limit = 0.10;
// if (yy > limit && block == 0) {
// area += (yy-limit);
// if (area > 1000) {
// beat = true;
// area = 0;
// block = 9000;
// }
// } else {
// area = 0;
// }
#ifdef PLOT_ME
if (xxx % 8 == 0) {
lines0.add(K::GnuplotPoint2(x, yy));
}
#endif
}
// debug view?
#ifdef PLOT_ME
if (xxx % 5000 == 0) {show();}
#endif
if (beat) {
std::cout << band0.getMul() << " " << band1.getMul() << " " << band2.getMul() << std::endl;
}
return beat;
}
/** add multiple values */
bool add(Scalar* s, int num) {
bool beat = false;
for (int i = 0; i < num; ++i) {
if (add(s[i])) {beat = true;}
}
return beat;
}
#ifdef PLOT_ME
void show() {
int limit = 8000;
int d0 = lines0.size() - limit;
if (d0 > 0) {
lines0.remove(0, d0);
}
int x0 = lines0[0].x;
int x1 = lines0[lines0.size()-1].x;
plot.getAxisX().setRange(x0, x1);
plot.getAxisY().setRange(-1, +5);
//plot.getAxisY().setRange(-32768, +32768);
gp.draw(plot);
gp.flush();
}
#endif
};
#endif // ANALYZER_H

244
lib/BeatDetection2.h Executable file
View File

@@ -0,0 +1,244 @@
#ifndef BEATDETECTION2_H
#define BEATDETECTION2_H
#include "BiquadFilterGate.h"
#include "MovingAVG.h"
#include "EndlessAVG.h"
#include <iostream>
//#define PLOT_ME
#ifdef PLOT_ME
#include <KLib/misc/gnuplot/Gnuplot.h>
#include <KLib/misc/gnuplot/GnuplotPlot.h>
#include <KLib/misc/gnuplot/GnuplotPlotElementLines.h>
#include <KLib/misc/gnuplot/GnuplotSplot.h>
#endif
#define BD2_SHORT 1024
template <typename Scalar> struct BeatBand2 {
BiquadFilterGate<2> filter;
MovingAVG<float> avgShort;
MovingAVG<float> avgLong;
MovingAVG<float> avgLongSquared;
MovingAVG<float> avgDiff;
MovingAVG<float> avgDiffSquared;
BeatBand2() : avgShort(BD2_SHORT), avgLong(1024*64), avgLongSquared(1024*64), avgDiff(1024*80), avgDiffSquared(1024*80) {
;
}
void add(Scalar left, Scalar right) {
// filter
const Scalar fLeft = std::abs(filter.filter(0, left));
const Scalar fRight = std::abs(filter.filter(1, right));
//const Scalar energy = fLeft*fLeft + fRight*fRight;
//const Scalar energy = fLeft + fRight;
const Scalar energy = fLeft*fLeft*fLeft + fRight*fRight*fRight;
// update
avgShort.add(energy);
avgLong.add(energy);
avgLongSquared.add(energy*energy);
const float diff = avgShort.get() - avgLong.get();
avgDiff.add(diff);
avgDiffSquared.add(diff*diff);
}
float getRatio() const {
return avgShort.get() / avgLong.get();
}
float getVariance() const {
return avgLongSquared.get() - (avgLong.get() * avgLong.get());
}
float getStdDev() const {
return std::sqrt(getVariance());
}
float getDiffVariance() const {
return avgDiffSquared.get() - (avgDiff.get() * avgDiff.get());
}
float getDiffStdDev() const {
return std::sqrt(getDiffVariance());
}
};
enum class Mode {
BASS,
SNARE,
};
template <typename Scalar> class BeatDetection2 {
private:
#ifdef PLOT_ME
K::Gnuplot gp;
K::GnuplotPlot plot;
K::GnuplotPlotElementLines lines0;
K::GnuplotPlotElementLines linesVar;
K::GnuplotPlotElementLines linesAvgLong;
#endif
int cnt = 0;
BeatBand2<float> band0;
Mode mode;
float thresholdMul;
public:
BeatDetection2() : BeatDetection2(Mode::BASS) {
;
}
/** setup */
BeatDetection2(Mode mode) : mode(mode) {
setSampleRate(44100);
switch(mode) {
case Mode::BASS: thresholdMul = 0.9; break;
case Mode::SNARE: thresholdMul = 1.5; break;
default: throw "invalid mode";
}
#ifdef PLOT_ME
gp.setTerminal("wxt", K::GnuplotSize(20, 12));
plot.add(&lines0);
plot.add(&linesVar); linesVar.getStroke().getColor().setHexStr("#0000ff");
plot.add(&linesAvgLong); linesAvgLong.getStroke().getColor().setHexStr("#00aa00");
#endif
}
void setSampleRate(int srate) {
std::cout << "setting sample-rate to " << srate << std::endl;
//band0.filter.setLowPass(110, 1, srate);
switch(mode) {
case Mode::BASS: band0.filter.setBandPass(40, 0.90f, srate); break;
case Mode::SNARE: band0.filter.setBandPass(200, 0.20f, srate); break;
default: throw "invalid mode";
}
//band0.filter.setBandPass(5000, 1, srate);
}
int block = 0;
bool lastWasBeat = false;
bool gapFound = false;
/** add single value */
bool add(Scalar left, Scalar right) {
static int x = 0; ++x;
left /= 36768.0f;
right /= 36768.0f;
++cnt;
band0.add(left, right);
bool curIsBeat = false;
if (cnt == BD2_SHORT) {
cnt = 0;
//const float ratio0 = band0.getRatio();
//const float var0 = band0.getVariance();
const float stdDev0 = band0.getDiffStdDev();
//const float diff0 = band0.avgShort.get() - band0.avgLong.get();
const float avgShort0 = band0.avgShort.get();
//const float avgLong0 = band0.avgLong.get();
const float threshold0 = band0.avgLong.get() + stdDev0 * thresholdMul; // HERE!
//avgRes.add(avgShort0);
//const float zz = avgRes.get();
const float zz = avgShort0;
//const float C = 2.7 - (20 * var0);
//const float C = 1.0 + (var0);
//if (ratio0 > C) {curIsBeat = true;}
//if (zz > 0.05 && zz > threshold0) {curIsBeat = true;}
if (zz > threshold0) {curIsBeat = true;}
#ifdef PLOT_ME
lines0.add(K::GnuplotPoint2(x, zz));
linesVar.add(K::GnuplotPoint2(x, threshold0));
//linesAvgLong.add(K::GnuplotPoint2(x, avgLong0));
static int xx = 0; ++xx;
if (xx % 5 == 0) {
show();
}
#endif
if (!curIsBeat) {gapFound = true;}
//if (block > 0 && !curIsBeat) {--block;}
if (block > 0 && gapFound) {--block;}
if (block == 0 && curIsBeat) {
block = 8; // very short!
gapFound = false;
//std::cout << ratio0 << " : " << var0 << " : " << C << std::endl;
return true;
}
//const bool res = curIsBeat && !lastWasBeat;
//lastWasBeat = curIsBeat;
// if (res) {
// std::cout << ratio0 << " : " << var0 << " : " << C << std::endl;
// }
//return res;
}
return false;
}
#ifdef PLOT_ME
void show() {
int limit = 200;
int d0 = lines0.size() - limit;
if (d0 > 0) {
lines0.remove(0, d0);
}
int x0 = lines0[0].x;
int x1 = lines0[lines0.size()-1].x;
plot.getAxisX().setRange(x0, x1);
//plot.getAxisY().setRange(-0.1, 0.25);
//plot.getAxisY().setRange(-32768, +32768);
gp.draw(plot);
gp.flush();
}
#endif
};
#endif // BEATDETECTION2_H

316
lib/BiquadFilterGate.h Executable file
View File

@@ -0,0 +1,316 @@
#ifndef BIQUADFILTERGATE_H
#define BIQUADFILTERGATE_H
/** lower frequency limit. about 20 Hz at 48.000 Hz */
#define BFG_MIN 0.0005
#define BFG_MAX 0.4995
using Frequency = float;
using Amplitude = float;
using SampleRate = int;
#include <cstring>
#include <cmath>
#define K_PI M_PI
/**
* a simple biquad filter that can be used
* for low- or high-pass filtering
*
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
*/
template <int channels> class BiquadFilterGate {
public:
/** ctor */
BiquadFilterGate() : in(), out(), disabled(false) {
reset();
}
/** filter the given amplitude of the given channel (history) */
Amplitude filter( const unsigned int ch, const Amplitude aIn ) {
Amplitude aOut = 0;
aOut += aIn *(b0a0);
aOut += in[ch][0] *(b1a0);
aOut += in[ch][1] *(b2a0);
aOut -= out[ch][0]*(a1a0);
aOut -= out[ch][1]*(a2a0);
//aOut = clamp1(aOut);
in[ch][1] = in[ch][0];
in[ch][0] = aIn;
out[ch][1] = out[ch][0];
out[ch][0] = aOut;
return aOut;
}
/** reset (disable) the filter */
void reset() {
b0a0 = 1.0;
b1a0 = 0.0;
b2a0 = 0.0;
a1a0 = 0.0;
a2a0 = 0.0;
memset(in, 0, sizeof(in));
memset(out, 0, sizeof(out));
}
/** configure the filter as low-pass. freqFact between ]0;0.5[ */
void setLowPass( double freqFact, const float octaves ) {
if (freqFact < BFG_MIN) {freqFact = BFG_MIN;}
if (freqFact > BFG_MAX) {disabled = true; return;}
disabled = false;
double w0 = 2.0 * K_PI * freqFact;
double alpha = sin(w0)*sinh( log(2)/2 * octaves * w0/sin(w0) );
double b0 = (1.0 - cos(w0))/2.0;
double b1 = 1.0 - cos(w0);
double b2 = (1.0 - cos(w0))/2.0;
double a0 = 1.0 + alpha;
double a1 = -2.0*cos(w0);
double a2 = 1.0 - alpha;
setValues(a0, a1, a2, b0, b1, b2);
}
/** configure the filter as low-pass */
void setLowPass( const Frequency freq, const float octaves, const SampleRate sRate ) {
double freqFact = double(freq) / double(sRate);
setLowPass(freqFact, octaves);
}
//http://dspwiki.com/index.php?title=Lowpass_Resonant_Biquad_Filter
//http://www.opensource.apple.com/source/WebCore/WebCore-7536.26.14/platform/audio/Biquad.cpp
/**
* configure as low-pass filter with resonance
* @param freqFact the frequency factor between ]0;0.5[
* @param res
*/
void setLowPassResonance( double freqFact, float res ) {
if (freqFact < BFG_MIN) {freqFact = BFG_MIN;}
if (freqFact > BFG_MAX) {disabled = true; return;}
disabled = false;
res *= 10;
double g = pow(10.0, 0.05 * res);
double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2);
double theta = K_PI * freqFact;
double sn = 0.5 * d * sin(theta);
double beta = 0.5 * (1 - sn) / (1 + sn);
double gamma = (0.5 + beta) * cos(theta);
double alpha = 0.25 * (0.5 + beta - gamma);
double a0 = 1.0;
double b0 = 2.0 * alpha;
double b1 = 2.0 * 2.0 * alpha;
double b2 = 2.0 * alpha;
double a1 = 2.0 * -gamma;
double a2 = 2.0 * beta;
setValues(a0, a1, a2, b0, b1, b2);
}
/** configure the filter as high-pass. freqFact between ]0;0.5[ */
void setHighPass( double freqFact, const float octaves ) {
if (freqFact < BFG_MIN) {disabled = true; return;}
if (freqFact > BFG_MAX) {freqFact = BFG_MAX;}
disabled = false;
double w0 = 2.0 * K_PI * freqFact;
double alpha = sin(w0)*sinh( log(2)/2 * octaves * w0/sin(w0) );
double b0 = (1.0 + cos(w0))/2.0;
double b1 = -(1.0 + cos(w0));
double b2 = (1.0 + cos(w0))/2.0;
double a0 = 1.0 + alpha;
double a1 = -2.0*cos(w0);
double a2 = 1.0 - alpha;
setValues(a0, a1, a2, b0, b1, b2);
}
/** configure the filter as high-pass */
void setHighPass( const Frequency freq, const float octaves, const SampleRate sRate ) {
double freqFact = double(freq) / double(sRate);
setHighPass(freqFact, octaves);
}
/** configure the filter as band-pass. freqFact between ]0;0.5[ */
void setBandPass( double freqFact, const float octaves ) {
if (freqFact < BFG_MIN) {disabled = true; return;}
if (freqFact > BFG_MAX) {disabled = true; return;}
disabled = false;
//double w0 = 2 * K_PI * / 2 / freqFact;
double w0 = 2.0 * K_PI * freqFact;
double alpha = sin(w0)*sinh( log(2)/2 * octaves * w0/sin(w0) );
double b0 = sin(w0)/2.0;
double b1 = 0.0;
double b2 = -sin(w0)/2.0;
double a0 = 1.0 + alpha;
double a1 = -2.0*cos(w0);
double a2 = 1.0 - alpha;
setValues(a0, a1, a2, b0, b1, b2);
}
/** configure the filter as band-pass */
void setBandPass( const Frequency freq, const float octaves, SampleRate sRate ) {
double freqFact = double(freq) / double(sRate);
setBandPass(freqFact, octaves);
}
/** configure the filter as all-pass. freqFact between ]0;0.5[ */
void setAllPass( double freqFact, const float octaves ) {
double w0 = 2.0 * K_PI * freqFact;
double alpha = sin(w0)*sinh( log(2)/2 * octaves * w0/sin(w0) );
double b0 = 1 - alpha;
double b1 = -2*cos(w0);
double b2 = 1 + alpha;
double a0 = 1 + alpha;
double a1 = -2*cos(w0);
double a2 = 1 - alpha;
setValues(a0, a1, a2, b0, b1, b2);
}
/** configure the filter as all-pass */
void setAllPass( const Frequency freq, const float octaves, const SampleRate sRate ) {
double freqFact = double(freq) / double(sRate);
setAllPass(freqFact, octaves);
}
/** configure as notch filter. freqFact between ]0;0.5[ */
void setNotch( double freqFact, const float octaves ) {
double w0 = 2.0 * K_PI * freqFact;
double alpha = sin(w0)*sinh( log(2)/2 * octaves * w0/sin(w0) );
double b0 = 1.0;
double b1 = -2.0*cos(w0);
double b2 = 1.0;
double a0 = 1.0 + alpha;
double a1 = -2.0*cos(w0);
double a2 = 1.0 - alpha;
setValues(a0, a1, a2, b0, b1, b2);
}
/** configure as notch filter */
void setNotch( const Frequency freq, const float octaves, const SampleRate sRate ) {
double freqFact = double(freq) / double(sRate);
setNotch(freqFact, octaves);
}
/** configure the filter as low-shelf. increase all aplitudes below freq? freqFact between ]0;0.5[ */
void setLowShelf( double freqFact, const float octaves, const float gain ) {
double A = sqrt( pow(10, (gain/20.0)) );
double w0 = 2.0 * K_PI * freqFact;
double alpha = sin(w0)*sinh( log(2)/2 * octaves * w0/sin(w0) );
double b0 = A*( (A+1.0) - (A-1.0)*cos(w0) + 2.0*sqrt(A)*alpha );
double b1 = 2.0*A*( (A-1.0) - (A+1.0)*cos(w0) );
double b2 = A*( (A+1.0) - (A-1.0)*cos(w0) - 2.0*sqrt(A)*alpha );
double a0 = (A+1.0) + (A-1.0)*cos(w0) + 2.0*sqrt(A)*alpha;
double a1 = -2.0*( (A-1.0) + (A+1.0)*cos(w0) );
double a2 = (A+1.0) + (A-1.0)*cos(w0) - 2.0*sqrt(A)*alpha;
setValues(a0, a1, a2, b0, b1, b2);
}
/** configure the filter as low-shelf. increase all aplitudes below freq? */
void setLowShelf( const Frequency freq, const float octaves, const float gain, const SampleRate sRate ) {
double freqFact = double(freq) / double(sRate);
setLowShelf(freqFact, octaves, gain);
}
/** configure the filter as high-shelf. increase all amplitues above freq? freqFact between ]0;0.5[ */
void setHighShelf( double freqFact, const float octaves, const float gain ) {
double A = sqrt( pow(10, (gain/20.0)) );
double w0 = 2.0 * K_PI * freqFact;
double alpha = sin(w0)*sinh( log(2)/2 * octaves * w0/sin(w0) );
double b0 = A*( (A+1.0) + (A-1.0)*cos(w0) + 2.0*sqrt(A)*alpha );
double b1 = -2.0*A*( (A-1.0) + (A+1.0)*cos(w0) );
double b2 = A*( (A+1.0) + (A-1.0)*cos(w0) - 2.0*sqrt(A)*alpha );
double a0 = (A+1.0) - (A-1.0)*cos(w0) + 2.0*sqrt(A)*alpha;
double a1 = 2.0*( (A-1.0) - (A+1.0)*cos(w0) );
double a2 = (A+1.0) - (A-1.0)*cos(w0) - 2.0*sqrt(A)*alpha;
setValues(a0, a1, a2, b0, b1, b2);
}
/** configure the filter as high-shelf. increase all amplitues above freq? */
void setHighShelf( const Frequency freq, const float octaves, const float gain, const SampleRate sRate ) {
double freqFact = double(freq) / double(sRate);
setHighShelf(freqFact, octaves, gain);
}
protected:
/** pre-calculate the quotients for the filtering */
void setValues(double a0, double a1, double a2, double b0, double b1, double b2) {
b0a0 = float(b0/a0);
b1a0 = float(b1/a0);
b2a0 = float(b2/a0);
a2a0 = float(a2/a0);
a1a0 = float(a1/a0);
}
/** the bi-quad filter params */
float b0a0;
float b1a0;
float b2a0;
float a1a0;
float a2a0;
/** history for input values, per channel */
Amplitude in[channels][2];
/** history for ouput values, per channel */
Amplitude out[channels][2];
/** filter disabled due to wrong params? */
bool disabled;
};
#endif // BIQUADFILTERGATE_H

38
lib/EndlessAVG.h Executable file
View File

@@ -0,0 +1,38 @@
#ifndef ENDLESSAVG_H
#define ENDLESSAVG_H
template <typename T> class EndlessAVG {
private:
/** track the current sum of the vector's values */
T curSum;
/** the number of elements to average */
int count;
public:
/** ctor */
EndlessAVG() : curSum(), count(0) {;}
/** add a new value */
void add(const T val) {
curSum += val;
++count;
}
/** get the current average */
T get() const {
return curSum / count;
}
/** get number of entries to average */
int getSize() const {
return count;
}
};
#endif // ENDLESSAVG_H

63
lib/MovingAVG.h Executable file
View File

@@ -0,0 +1,63 @@
#ifndef MOVINGAVG_H
#define MOVINGAVG_H
#include <vector>
#include "RingBuffer.h"
template <typename T> class MovingAVG {
private:
/** track the current sum of the vector's values */
T curSum;
/** the number of elements to average */
int size;
/** up to "size" elements */
RingBuffer<T> values;
public:
/** ctor */
MovingAVG(const int size) : curSum(), size(size), values(size) {;}
/** add a new value */
void add(const T val) {
// add new value
//values.push_back(val);
values.add(val);
curSum += val;
// too many values?
//if ((int) values.size() > size) {
// curSum -= values.front();
// values.erase(values.begin());
//}
if (values.size() >= size) {
curSum -= values.get();
}
}
/** get the current average */
T get() const {
return curSum / values.size();
}
/** get the number of used entries */
int getNumUsed() const {
return (int) values.size();
}
/** get number of entries to average */
int getSize() const {
return size;
}
};
#endif // MOVINGAVG_H

216
lib/NetworkAddress.h Normal file
View File

@@ -0,0 +1,216 @@
#ifndef NETWORKADDRESS_H
#define NETWORKADDRESS_H
#ifndef K_SOCKETS_NETWORKADDRESS_H
#define K_SOCKETS_NETWORKADDRESS_H
#include "../exception.h"
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
class NetworkAddress {
public:
/**
* @brief create NetworkAddress from the given socket address. This will
* mainly be used for incoming data frames. The ctor re-calculuates the
* host-ip and port-number from the given address
* @param address the socket address to parse
*/
NetworkAddress(const struct sockaddr_in& address) : port(0), sockAddr(address) {
port = ntohs(sockAddr.sin_port);
ipFromAddressStruct();
}
/**
* @brief create a new NetworkAddress identified by hostname and port-number
* @param host the host to identify (e.g. "127.0.0.1" or "google.de")
* @param port the 16-bit port-number to use
*/
NetworkAddress(const std::string& host, const uint16_t port) : port(port) {
// convert hostname to ip
struct hostent* he = gethostbyname( host.c_str() );
if (!he) {throw Exception("error while retrieving IP for hostname: '" + host + "'");}
// sanity checks
// https://www.cs.rutgers.edu/~pxk/417/notes/sockets/udp.html
// build address struct
memset( &sockAddr, 0, sizeof(sockAddr) );
sockAddr.sin_family = AF_INET;
//sockAddr.sin_addr = *((struct in_addr*)he->h_addr);
sockAddr.sin_port = htons(port);
memcpy((void*)&sockAddr.sin_addr, he->h_addr_list[0], he->h_length);
ipFromAddressStruct();
}
/**
* @brief create a new NetworkAddress identified by the given port-number and ANY host-name
* @param port the 16-bit port-number to use
*/
NetworkAddress(const uint16_t port) : port(port) {
// build address struct
memset( &sockAddr, 0, sizeof(sockAddr) );
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockAddr.sin_port = htons(port);
ipFromAddressStruct();
}
/**
* @brief create a new NetworkAddress matching ANY host and ANY port
* e.g. used for receiving all UDP-datagrams sent to any local port / interface
*/
NetworkAddress() : port(0) {
// build address struct
memset( &sockAddr, 0, sizeof(sockAddr) );
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockAddr.sin_port = htons(0);
ipFromAddressStruct();
}
/** copy ctor */
NetworkAddress(const NetworkAddress& other) :
port(other.port), sockAddr(other.sockAddr) {
ipFromAddressStruct();
}
/** get a NetworkAddress to broadcast to the given port */
static NetworkAddress getForBroadcast(const uint16_t port) {
NetworkAddress adr;
memset(&adr.sockAddr, 0, sizeof(adr.sockAddr));
adr.sockAddr.sin_family = AF_INET;
adr.sockAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
adr.sockAddr.sin_port = htons(port);
adr.port = port;
return adr;
}
// /** assignment operator */
// void operator = (const NetworkAddress& other) {
// this->hostName = other.hostName;
// this->hostIP = other.hostIP;
// this->port = other.port;
// this->sockAddr = other.sockAddr;
// }
/** dtor */
~NetworkAddress() {
;
}
/** get the network address as sockaddr_in struct */
const struct sockaddr_in& getAsSocketAddress() const {
return sockAddr;
}
// /** get the remote's port */
// uint16_t getPort() const {
// return port;
// }
// /** get the host's IP as string */
// const std::string& getHostIP() const {
// return hostIP;
// }
// /** get the host's name as string */
// const std::string& getHostName() {
// if (hostName.empty()) { hostNameFromAddressStruct(); }
// return hostName;
// }
/** is the internal port valid for an outbound packet? */
bool isValidTargetPort() const {
return port != 0;
}
/** is the internal ip valid for an outbound packet? */
bool isValidTargetHost() const {
return sockAddr.sin_addr.s_addr != htonl(INADDR_ANY);
}
// /** check whether both NetworkAddresses are equal */
// bool operator == (const NetworkAddress& other) const {
// return memcmp( &this->sockAddr, &other.sockAddr, sizeof(sockAddr)) == 0;
// }
private:
/** fill the hostIP string from the address struct */
void ipFromAddressStruct() {
uint32_t ip = sockAddr.sin_addr.s_addr;
hostIP = std::to_string( (ip>> 0) & 0xFF ) + "." +
std::to_string( (ip>> 8) & 0xFF ) + "." +
std::to_string( (ip>>16) & 0xFF ) + "." +
std::to_string( (ip>>24) & 0xFF );
}
// /** fill the hostName string via reverse-lookup of the address struct */
// void hostNameFromAddressStruct() {
// char hostNameBuf[128];
// const int flags = 0;
// // reverse lookup
// getnameinfo( (struct sockaddr*) &sockAddr, sizeof(sockAddr), hostNameBuf, 128, nullptr, 0, flags );
// this->hostName = std::string(hostNameBuf);
// }
private:
/** the host's ip as string */
std::string hostIP;
/** the host's name as string */
std::string hostName;
/** the port-number from the ctor */
uint16_t port;
/** the resulting socket-address-struct */
struct sockaddr_in sockAddr;
};
#endif // K_SOCKETS_NETWORKADDRESS_H
#endif // NETWORKADDRESS_H

107
lib/RingBuffer.h Executable file
View File

@@ -0,0 +1,107 @@
#ifndef RINGBUFFER_H
#define RINGBUFFER_H
#include <vector>
template <typename Element> class RingBuffer {
private:
std::vector<Element> data;
int iWrite;
int iRead;
int available;
public:
/** ctor with the size of the buffer */
RingBuffer(const int size) : data(size), iWrite(0), iRead(0), available(0) {
;
}
/** add a new element WITH overflow check! */
void addSafe(const Element& element) {
//Assert::isTrue(available != (int)data.size(), "buffer overflow");
add(element);
}
/** add a new element WIUTHOUT checking for overflows */
void add(const Element& element) {
data[iWrite] = element;
++iWrite;
iWrite %= data.size();
++available;
}
/** get the next element */
const Element& get() {
//Assert::isNot0(available, "buffer is empty");
const Element& tmp = data[iRead];
++iRead;
iRead %= data.size();
--available;
if (available < 0) {available = 0;}
return tmp;
}
/** peek into the given element without removing it */
const Element& peek(const int idx) const {
const Element& tmp = data[(iRead + idx) % data.size()];
return tmp;
}
/** peek into the given element without removing it */
const Element& operator [] (const int idx) const {
return peek(idx);
}
/** does the buffer contain the given element? */
bool contains(const Element& e) const {
for (int i = 0; i < available; ++i) {
if (peek(i) == e) {return true;}
}
return false;
}
/** reset the ringbuffer */
void reset() {
iWrite = 0;
iRead = 0;
available = 0;
}
/** is the buffer empty? */
bool empty() const {
return available == 0;
}
/** get the number of available entries */
int size() const {
return available;
}
struct Iterator {
int idx;
int available;
const std::vector<Element>& data;
/** ctor */
Iterator(const int idx, const int available, const std::vector<Element>& data) : idx(idx), available(available), data(data) {;}
bool operator != (const Iterator& other) {return other.available != available;}
void operator ++ () {idx = (idx+1) % data.size(); --available;}
const Element& operator * () const {return data[idx];}
};
Iterator begin() const {return Iterator(iRead, available, data);}
Iterator end() const {return Iterator(iWrite, 0, data);}
};
#endif // RINGBUFFER_H

199
lib/Socket.h Normal file
View File

@@ -0,0 +1,199 @@
#ifndef SOCKET_H
#define SOCKET_H
#include <sys/socket.h>
#include <vector>
#include <errno.h>
#include <netinet/in.h>
#include "NetworkAddress.h"
#include "../exception.h"
class SocketUDP {
private:
static constexpr int MAX_DATAGRAM_SIZE = 64*1024;
bool bound = false;
public:
/** ctor */
SocketUDP() : handle(0) {
// create socket
handle = socket(AF_INET, SOCK_DGRAM, 0);
if (handle == -1) {throw Exception("error while creating socket");}
}
/** dtor */
~SocketUDP() {
close();
}
/**
* @brief bind the socket to receive all datagrams sent to the given (local) port
* a localPort of 0 will let the OS determine a free one.
* @param localPort the local endpoint for datagrams sent from a remote
*/
void bind(const uint16_t localPort) {
if (bound) {throw Exception("socket already bound!");}
// local endpoint AF_INET struct
struct sockaddr_in srvAddr;
memset((char*)&srvAddr, 0, sizeof(srvAddr));
srvAddr.sin_family = AF_INET;
srvAddr.sin_addr.s_addr = htonl(INADDR_ANY); // every interface
srvAddr.sin_port = htons(localPort);
// bind the socket to the given port
int ret = ::bind(handle, (struct sockaddr*) &srvAddr, sizeof(srvAddr));
if (ret < 0) {throw Exception("error while binding socket");}
// mark as bound
bound = true;
}
/** close the socket */
void close() {
// cleanup
if (handle) {
::close(handle);
handle = 0;
}
}
// /** is the socket currently closed? */
// bool isClosed() const {
// return handle == 0;
// }
// /** allow to broadcast packets using this socket? */
// void allowBroadcast(const bool allow) {
// #if defined(__GNUC__)
// const int broadcastEnable = (allow) ? (1) : (0);
// const int ret = setsockopt(handle, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));
// if (ret < 0) { throw SocketException("error while enabling broadcast", errno); }
// #elif defined(_WIN32)
// ;
// #endif
// }
/**
* @brief send a datagram to the given destination address
* @param data the data to send
* @param len the length of the data to send (max 64k!)
* @param addr the destination address
*/
void sendDatagram(const uint8_t* data, uint32_t len, const NetworkAddress& addr) {
// sanity check
if (!bound) {throw Exception("bind() the socket first!");}
// ensure max datagram size
if (len > MAX_DATAGRAM_SIZE) {throw Exception("max datagram size is " + std::to_string(MAX_DATAGRAM_SIZE)+ " bytes!");}
// ensure correct destination address
if (!addr.isValidTargetPort()) {throw Exception("the given destination address has no valid port number");}
if (!addr.isValidTargetHost()) {throw Exception("the given destination address has no valid hostname");}
// send datagram to the given destionation
const struct sockaddr_in& sockAddr = addr.getAsSocketAddress();
const int options = 0;
const int res = sendto(handle, (const char*)data, len, options, (struct sockaddr*) &sockAddr, sizeof(sockaddr));
// check
if (res < 0) {throw Exception("error while sending datagram");}
}
/**
* @brief send a datagram to the given destination address
* @param data the data to send (max 64k!)
* @param addr the destination address
*/
void sendDatagram(const std::vector<uint8_t>& data, const NetworkAddress& addr) {
sendDatagram(data.data(), (uint32_t) data.size(), addr);
}
void sendDatagram(const std::string& data, const NetworkAddress& addr) {
sendDatagram((const uint8_t*)data.data(), data.length(), addr);
}
// /**
// * @brief send the given datagram to its internal destination address
// * @param d the datagram to send
// */
// void sendDatagram(const Datagram& d) {
// sendDatagram(d.getData(), d.getLength(), d.getAddress());
// }
// /**
// * @brief convenience function for receiveDatagram(d) which returns a new
// * datagram instead of reusing an existing one.
// * @return a newly created datagram holding the received message
// */
// DefaultDatagram receiveDatagram() {
// DefaultDatagram dd;
// receiveDatagram(dd);
// return dd;
// }
// /**
// * @brief this method will block until a new datagram is received on the bound
// * port of the underlying socket. the received data will be stored within the
// * given datagram
// * @param d the buffer to store the received datagram to
// */
// void receiveDatagram(Datagram& d) {
// const int flags = 0;
// const int maxSize = MAX_DATAGRAM_SIZE;
// // sanity check
// if (!bound) {throw SocketException("bind() the socket first!");}
// // ensure the target buffer may hold the largest possible datagram
// d.ensureSpace(maxSize);
// // the datagrams sender will be stored here
// struct sockaddr_in senderAddr;
// socklen_t senderLength = sizeof(senderAddr);
// // receive datagram from socket and store sender information
// int len = recvfrom(handle, (char*) d.getDataWriteable(), maxSize, flags, (struct sockaddr*) &senderAddr, &senderLength);
// if (len < 0) {throw SocketException("error while receiving datagram", errno);}
// // store senders network-address in the datagram
// NetworkAddress na(senderAddr);
// d.setAddress(na);
// // inform buffer about the actual size of the datagram
// d.setLength( (uint32_t) len );
// }
private:
/** the socket handle */
int handle;
/** the local address the socket is bound to */
NetworkAddress localAddress;
};
#endif // SOCKET_H