2022-09-08 08:21:53 -03:00
|
|
|
// Copyright (C) 2022 Then Try This
|
2015-07-21 10:58:13 -03:00
|
|
|
//
|
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This program 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 General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
|
|
2015-07-09 17:50:03 -03:00
|
|
|
#include <assert.h>
|
2015-07-21 14:13:39 -03:00
|
|
|
#include <float.h>
|
2015-07-09 17:50:03 -03:00
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
#include "block.h"
|
|
|
|
|
|
|
|
|
|
using namespace spiralcore;
|
|
|
|
|
|
|
|
|
|
FFT *block::m_fftw;
|
|
|
|
|
Aquila::Mfcc *block::m_mfcc_proc;
|
|
|
|
|
|
|
|
|
|
static const int MFCC_FILTERS=12;
|
2015-09-23 14:35:13 -03:00
|
|
|
static const int FFT_BIAS=200;
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2015-07-27 11:36:34 -03:00
|
|
|
double blend(double a, double b, double t) {
|
2015-09-23 14:35:13 -03:00
|
|
|
return a*(1-t)+b*t;
|
2015-07-27 11:36:34 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double square(double a) {
|
2015-09-23 14:35:13 -03:00
|
|
|
return a*a;
|
2015-07-27 11:36:34 -03:00
|
|
|
}
|
|
|
|
|
|
2022-09-24 17:44:38 -03:00
|
|
|
void normalise(spiralcore::sample &in) {
|
2015-09-23 14:35:13 -03:00
|
|
|
// find min/max
|
|
|
|
|
float max = 0;
|
|
|
|
|
float min = FLT_MAX;
|
|
|
|
|
for (u32 i=0; i<in.get_length(); ++i) {
|
|
|
|
|
if (in[i]<min) min=in[i];
|
|
|
|
|
if (in[i]>max) max=in[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float mid = min+(max-min)/2.0f;
|
|
|
|
|
|
|
|
|
|
// remove dc
|
|
|
|
|
for (u32 i=0; i<in.get_length(); ++i) {
|
|
|
|
|
in[i]-=mid;
|
|
|
|
|
}
|
|
|
|
|
min-=mid;
|
|
|
|
|
max-=mid;
|
|
|
|
|
|
|
|
|
|
// squash so biggest value is 1 or -1
|
|
|
|
|
float div = fabs(min);
|
|
|
|
|
if (div<max) div=max;
|
|
|
|
|
div=1/div;
|
|
|
|
|
|
|
|
|
|
for (u32 i=0; i<in.get_length(); ++i) {
|
|
|
|
|
in[i]*=div;
|
|
|
|
|
}
|
2015-07-21 14:13:39 -03:00
|
|
|
}
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2015-09-14 07:03:12 -03:00
|
|
|
block::block(u64 id, const string &filename, const sample &pcm, u32 rate, const window &w, bool ditchpcm) :
|
2015-09-23 14:35:13 -03:00
|
|
|
m_id(id),
|
|
|
|
|
m_pcm(pcm),
|
|
|
|
|
m_fft(pcm.get_length()),
|
|
|
|
|
m_mfcc(MFCC_FILTERS),
|
|
|
|
|
m_n_pcm(pcm),
|
|
|
|
|
m_n_fft(pcm.get_length()),
|
|
|
|
|
m_n_mfcc(MFCC_FILTERS),
|
|
|
|
|
m_block_size(pcm.get_length()),
|
|
|
|
|
m_rate(rate),
|
|
|
|
|
m_orig_filename(filename),
|
|
|
|
|
m_usage(0)
|
2015-07-09 17:50:03 -03:00
|
|
|
{
|
2015-09-23 14:35:13 -03:00
|
|
|
init_fft(m_pcm.get_length());
|
|
|
|
|
assert(m_mfcc_proc!=NULL);
|
|
|
|
|
assert(m_fftw!=NULL);
|
|
|
|
|
|
|
|
|
|
w.run(m_pcm);
|
|
|
|
|
process(m_pcm,m_fft,m_mfcc,m_dominant_freq);
|
|
|
|
|
|
|
|
|
|
// rerun the normalised version
|
|
|
|
|
normalise(m_n_pcm);
|
|
|
|
|
w.run(m_n_pcm);
|
|
|
|
|
process(m_n_pcm,m_n_fft,m_n_mfcc,m_n_dominant_freq);
|
|
|
|
|
|
2016-07-08 07:41:30 -03:00
|
|
|
// don't need to keep pcm for the target
|
2015-09-23 14:35:13 -03:00
|
|
|
if (ditchpcm) {
|
|
|
|
|
m_pcm.clear();
|
|
|
|
|
m_n_pcm.clear();
|
|
|
|
|
}
|
2015-07-21 14:13:39 -03:00
|
|
|
}
|
2015-07-09 17:50:03 -03:00
|
|
|
|
|
|
|
|
|
2016-07-08 07:41:30 -03:00
|
|
|
void block::init_fft(u32 block_size) {
|
2015-09-23 14:35:13 -03:00
|
|
|
if (m_fftw == NULL || m_fftw->m_length!=block_size) {
|
|
|
|
|
if (m_fftw == NULL) delete m_fftw;
|
|
|
|
|
m_fftw = new FFT(block_size,100);
|
|
|
|
|
if (m_mfcc_proc == NULL) delete m_mfcc_proc;
|
|
|
|
|
m_mfcc_proc = new Aquila::Mfcc(block_size);
|
|
|
|
|
}
|
2015-07-21 14:13:39 -03:00
|
|
|
}
|
|
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
void block::process(const sample &pcm, sample &fft, sample &mfcc, float &freq) {
|
|
|
|
|
m_fftw->impulse2freq(pcm.get_buffer());
|
|
|
|
|
m_fftw->calculate_bins();
|
2015-07-21 14:13:39 -03:00
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
// calculate fft
|
|
|
|
|
std::vector<std::complex<double> > mfspec;
|
|
|
|
|
for (u32 i=0; i<m_block_size; ++i) {
|
|
|
|
|
mfspec.push_back(std::complex<double>(m_fftw->m_spectrum[i][0],
|
2016-09-24 06:33:16 -03:00
|
|
|
m_fftw->m_spectrum[i][1]));
|
2015-09-23 14:35:13 -03:00
|
|
|
}
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
freq = m_fftw->calculate_dominant_freq();
|
2015-07-22 11:32:36 -03:00
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
u32 fft_size = m_block_size;
|
|
|
|
|
if (fft_size>100) {
|
|
|
|
|
fft.crop_to(100);
|
|
|
|
|
fft_size=100;
|
|
|
|
|
}
|
2015-07-22 11:32:36 -03:00
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
for (u32 i=0; i<fft_size; ++i) {
|
|
|
|
|
fft[i]=m_fftw->m_bin[i];
|
|
|
|
|
}
|
2015-07-09 17:50:03 -03:00
|
|
|
|
|
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
// calculate mfcc
|
|
|
|
|
std::vector<double> m = m_mfcc_proc->calculate(mfspec,MFCC_FILTERS);
|
|
|
|
|
for (u32 i=0; i<MFCC_FILTERS; ++i) {
|
|
|
|
|
mfcc[i] = m[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2015-07-09 19:54:05 -03:00
|
|
|
|
2015-07-21 14:13:39 -03:00
|
|
|
double block::_compare(const sample &fft_a, const sample &mfcc_a,
|
|
|
|
|
const sample &fft_b, const sample &mfcc_b,
|
|
|
|
|
const search_params ¶ms) const
|
|
|
|
|
{
|
2015-09-23 14:35:13 -03:00
|
|
|
double mfcc_acc=0;
|
|
|
|
|
double fft_acc=0;
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
u32 fft_start = params.m_fft1_start;
|
|
|
|
|
u32 fft_end = fmin(params.m_fft1_end,m_fft.get_length());
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2016-07-08 07:41:30 -03:00
|
|
|
// first check for only fft
|
2015-09-23 14:35:13 -03:00
|
|
|
if (params.m_ratio==0) {
|
2015-07-09 20:11:12 -03:00
|
|
|
for (u32 i=fft_start; i<fft_end; ++i) {
|
2015-09-23 14:35:13 -03:00
|
|
|
fft_acc+=square(fft_a[i]-fft_b[i]);
|
2015-07-09 17:50:03 -03:00
|
|
|
}
|
2015-09-23 14:35:13 -03:00
|
|
|
return (fft_acc/(float)fft_a.get_length())*FFT_BIAS;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-08 07:41:30 -03:00
|
|
|
// only mfcc
|
2015-09-23 14:35:13 -03:00
|
|
|
if (params.m_ratio==1) {
|
2015-07-09 17:50:03 -03:00
|
|
|
for (u32 i=0; i<MFCC_FILTERS; ++i) {
|
2015-09-23 14:35:13 -03:00
|
|
|
mfcc_acc+=square(mfcc_a[i]-mfcc_b[i]);
|
2015-07-09 17:50:03 -03:00
|
|
|
}
|
2015-09-23 14:35:13 -03:00
|
|
|
return mfcc_acc/(float)MFCC_FILTERS;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-08 07:41:30 -03:00
|
|
|
// calculate mix of em both
|
2015-09-23 14:35:13 -03:00
|
|
|
for (u32 i=fft_start; i<fft_end; ++i) {
|
|
|
|
|
fft_acc+=square(fft_a[i]-fft_b[i]);
|
|
|
|
|
}
|
|
|
|
|
for (u32 i=0; i<MFCC_FILTERS; ++i) {
|
|
|
|
|
mfcc_acc+=square(mfcc_a[i]-mfcc_b[i]);
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-08 07:41:30 -03:00
|
|
|
// weight them based on ratio
|
2015-09-23 14:35:13 -03:00
|
|
|
return blend(fft_acc/(float)fft_a.get_length(),
|
|
|
|
|
mfcc_acc/(float)MFCC_FILTERS,
|
|
|
|
|
params.m_ratio);
|
2015-07-09 17:50:03 -03:00
|
|
|
}
|
|
|
|
|
|
2015-07-21 14:13:39 -03:00
|
|
|
double block::compare(const block &other, const search_params ¶ms) const {
|
2016-07-08 07:41:30 -03:00
|
|
|
return blend(blend(_compare(m_fft, m_mfcc, other.m_fft, other.m_mfcc, params),
|
2015-09-23 14:35:13 -03:00
|
|
|
_compare(m_n_fft, m_n_mfcc, other.m_n_fft, other.m_n_mfcc, params),
|
|
|
|
|
params.m_n_ratio),
|
|
|
|
|
other.m_usage, params.m_usage_importance);
|
2015-07-21 14:13:39 -03:00
|
|
|
}
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2016-07-08 07:41:30 -03:00
|
|
|
// serialise in/out to file
|
2015-08-03 05:18:20 -03:00
|
|
|
ios &spiralcore::operator||(ios &s, block &b) {
|
2015-09-23 14:35:13 -03:00
|
|
|
u32 version=3;
|
|
|
|
|
string id("block");
|
|
|
|
|
s||id||version;
|
|
|
|
|
|
|
|
|
|
if (version>1) s||b.m_id;
|
|
|
|
|
if (version>2) {
|
|
|
|
|
s||b.m_dominant_freq;
|
|
|
|
|
s||b.m_n_dominant_freq;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s||b.m_pcm||b.m_fft||b.m_mfcc;
|
|
|
|
|
s||b.m_n_pcm||b.m_n_fft||b.m_n_mfcc;
|
|
|
|
|
|
|
|
|
|
s||b.m_block_size||b.m_rate||b.m_orig_filename;
|
|
|
|
|
stream_vector(s,b.m_synapse);
|
|
|
|
|
return s;
|
2015-08-03 05:18:20 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-07-09 17:50:03 -03:00
|
|
|
bool block::unit_test() {
|
2015-07-21 14:13:39 -03:00
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
stream_unit_test();
|
|
|
|
|
|
|
|
|
|
sample ntest(3);
|
|
|
|
|
u32 idx=0;
|
|
|
|
|
ntest[idx++]=-1;
|
|
|
|
|
ntest[idx++]=1;
|
|
|
|
|
ntest[idx++]=-1;
|
|
|
|
|
idx=0;
|
|
|
|
|
normalise(ntest);
|
|
|
|
|
assert(feq(ntest[idx++],-1));
|
|
|
|
|
assert(feq(ntest[idx++],1));
|
|
|
|
|
assert(feq(ntest[idx++],-1));
|
|
|
|
|
idx=0;
|
|
|
|
|
ntest[idx++]=-2;
|
|
|
|
|
ntest[idx++]=2;
|
|
|
|
|
ntest[idx++]=-2;
|
|
|
|
|
normalise(ntest);
|
|
|
|
|
idx=0;
|
|
|
|
|
assert(feq(ntest[idx++],-1));
|
|
|
|
|
assert(feq(ntest[idx++],1));
|
|
|
|
|
assert(feq(ntest[idx++],-1));
|
|
|
|
|
idx=0;
|
|
|
|
|
ntest[idx++]=19;
|
|
|
|
|
ntest[idx++]=20;
|
|
|
|
|
ntest[idx++]=19;
|
|
|
|
|
normalise(ntest);
|
|
|
|
|
idx=0;
|
|
|
|
|
assert(feq(ntest[idx++],-1));
|
|
|
|
|
assert(feq(ntest[idx++],1));
|
|
|
|
|
assert(feq(ntest[idx++],-1));
|
|
|
|
|
|
|
|
|
|
sample data(200);
|
|
|
|
|
for (u32 i=0; i<data.get_length(); i++) {
|
|
|
|
|
data[i]=i/(float)data.get_length();
|
|
|
|
|
}
|
|
|
|
|
window w;
|
|
|
|
|
w.init(data.get_length());
|
|
|
|
|
w.set_current_type(window::RECTANGLE);
|
|
|
|
|
|
|
|
|
|
block bb(0,"test",data,44100,w);
|
|
|
|
|
|
|
|
|
|
assert(bb.m_pcm.get_length()==data.get_length());
|
|
|
|
|
//assert(bb.m_fft.get_length()==data.get_length());
|
|
|
|
|
assert(bb.m_mfcc.get_length()==MFCC_FILTERS);
|
|
|
|
|
assert(bb.m_orig_filename==string("test"));
|
|
|
|
|
assert(bb.m_rate==44100);
|
|
|
|
|
assert(bb.m_block_size==data.get_length());
|
|
|
|
|
|
|
|
|
|
ofstream of("test_data/blocktest.bin",ios::binary);
|
|
|
|
|
of||bb;
|
|
|
|
|
of.close();
|
|
|
|
|
|
|
|
|
|
cerr<<"written"<<endl;
|
|
|
|
|
|
|
|
|
|
ifstream ifs("test_data/blocktest.bin",ios::binary);
|
|
|
|
|
block bbb;
|
|
|
|
|
ifs||bbb;
|
|
|
|
|
ifs.close();
|
|
|
|
|
|
|
|
|
|
assert(bbb.m_pcm.get_length()==data.get_length());
|
|
|
|
|
//assert(bb.m_fft.get_length()==data.get_length());
|
|
|
|
|
assert(bbb.m_mfcc.get_length()==MFCC_FILTERS);
|
|
|
|
|
assert(bbb.m_orig_filename==string("test"));
|
|
|
|
|
assert(bbb.m_rate==44100);
|
|
|
|
|
assert(bbb.m_block_size==data.get_length());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
search_params p(0,0,0,100,0);
|
|
|
|
|
block bb2(0,"test",data,44100,w);
|
|
|
|
|
assert(bb.compare(bb2,p)==0);
|
|
|
|
|
p.m_ratio=1;
|
|
|
|
|
assert(bb.compare(bb2,p)==0);
|
|
|
|
|
p.m_ratio=0.5;
|
|
|
|
|
assert(bb.compare(bb2,p)==0);
|
|
|
|
|
|
|
|
|
|
sample data2(200);
|
|
|
|
|
for (u32 i=0; i<data.get_length(); i++) {
|
|
|
|
|
data2[i]=i%10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
block cpy(0,"test",data,100,w);
|
|
|
|
|
{
|
|
|
|
|
block bb3(0,"test",data2,44100,w);
|
|
|
|
|
p.m_ratio=0.0;
|
|
|
|
|
assert(bb.compare(bb3,p)!=0);
|
|
|
|
|
assert(bb.compare(bb3,p)!=0);
|
2015-07-11 08:29:51 -03:00
|
|
|
p.m_ratio=0.5;
|
2015-09-23 14:35:13 -03:00
|
|
|
assert(bb.compare(bb3,p)!=0);
|
|
|
|
|
cpy=bb3;
|
|
|
|
|
}
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
assert(cpy.m_pcm.get_length()==200);
|
2015-07-09 17:50:03 -03:00
|
|
|
|
2015-07-21 14:13:39 -03:00
|
|
|
|
|
|
|
|
|
2015-09-23 14:35:13 -03:00
|
|
|
return true;
|
2015-07-09 17:50:03 -03:00
|
|
|
}
|