experimental pitch shifting

This commit is contained in:
dave griffiths 2015-09-23 18:35:13 +01:00
parent ad64e64984
commit 44be9d76cb
12 changed files with 555 additions and 561 deletions

View File

@ -23,6 +23,7 @@
#include "process_thread.h" #include "process_thread.h"
#include "audio_thread.h" #include "audio_thread.h"
#include "pitchshift.h"
using namespace std; using namespace std;
@ -31,6 +32,8 @@ int main( int argc , char *argv[] ){
MainWindow mainWin; MainWindow mainWin;
mainWin.show(); mainWin.show();
pitchshift::init(44100);
process_thread pt; process_thread pt;
audio_thread at(pt); audio_thread at(pt);
pt.register_renderer(at.m_renderer); pt.register_renderer(at.m_renderer);

View File

@ -25,6 +25,7 @@ SOURCES += MainWindow.cpp \
../src/renderer.cpp \ ../src/renderer.cpp \
../src/status.cpp \ ../src/status.cpp \
../src/window.cpp \ ../src/window.cpp \
../src/pitchshift.cpp \
../src/aquila/filter/MelFilterBank.cpp \ ../src/aquila/filter/MelFilterBank.cpp \
../src/aquila/filter/MelFilter.cpp \ ../src/aquila/filter/MelFilter.cpp \
../src/aquila/transform/Dct.cpp \ ../src/aquila/transform/Dct.cpp \
@ -38,7 +39,7 @@ SOURCES += MainWindow.cpp \
../../../jellyfish/src/core/stream.cpp ../../../jellyfish/src/core/stream.cpp
INCLUDEPATH += ../src INCLUDEPATH += ../src
LIBS += -L.. -lportaudio -lfftw3 -lsndfile -llo -ldl -lpthread -lm LIBS += -L.. -lrubberband -lportaudio -lfftw3 -lsndfile -llo -ldl -lpthread -lm
#CONFIG+=debug #CONFIG+=debug
QMAKE_CXXFLAGS += -Wall -Wno-unused -std=c++11 -DDONT_USE_FLUXA_GRAPH QMAKE_CXXFLAGS += -Wall -Wno-unused -std=c++11 -DDONT_USE_FLUXA_GRAPH

View File

@ -26,274 +26,279 @@ FFT *block::m_fftw;
Aquila::Mfcc *block::m_mfcc_proc; Aquila::Mfcc *block::m_mfcc_proc;
static const int MFCC_FILTERS=12; static const int MFCC_FILTERS=12;
static const int FFT_BIAS=200;
double blend(double a, double b, double t) { double blend(double a, double b, double t) {
return a*(1-t)+b*t; return a*(1-t)+b*t;
} }
double square(double a) { double square(double a) {
return a*a; return a*a;
} }
void normalise(sample &in) { void normalise(sample &in) {
// find min/max // find min/max
float max = 0; float max = 0;
float min = FLT_MAX; float min = FLT_MAX;
for (u32 i=0; i<in.get_length(); ++i) { for (u32 i=0; i<in.get_length(); ++i) {
if (in[i]<min) min=in[i]; if (in[i]<min) min=in[i];
if (in[i]>max) max=in[i]; if (in[i]>max) max=in[i];
} }
float mid = min+(max-min)/2.0f; float mid = min+(max-min)/2.0f;
// remove dc // remove dc
for (u32 i=0; i<in.get_length(); ++i) { for (u32 i=0; i<in.get_length(); ++i) {
in[i]-=mid; in[i]-=mid;
} }
min-=mid; min-=mid;
max-=mid; max-=mid;
// squash so biggest value is 1 or -1 // squash so biggest value is 1 or -1
float div = fabs(min); float div = fabs(min);
if (div<max) div=max; if (div<max) div=max;
div=1/div; div=1/div;
for (u32 i=0; i<in.get_length(); ++i) { for (u32 i=0; i<in.get_length(); ++i) {
in[i]*=div; in[i]*=div;
} }
} }
block::block(u64 id, const string &filename, const sample &pcm, u32 rate, const window &w, bool ditchpcm) : block::block(u64 id, const string &filename, const sample &pcm, u32 rate, const window &w, bool ditchpcm) :
m_id(id), m_id(id),
m_pcm(pcm), m_pcm(pcm),
m_fft(pcm.get_length()), m_fft(pcm.get_length()),
m_mfcc(MFCC_FILTERS), m_mfcc(MFCC_FILTERS),
m_n_pcm(pcm), m_n_pcm(pcm),
m_n_fft(pcm.get_length()), m_n_fft(pcm.get_length()),
m_n_mfcc(MFCC_FILTERS), m_n_mfcc(MFCC_FILTERS),
m_block_size(pcm.get_length()), m_block_size(pcm.get_length()),
m_rate(rate), m_rate(rate),
m_orig_filename(filename), m_orig_filename(filename),
m_usage(0) m_usage(0)
{ {
init_fft(m_pcm.get_length()); init_fft(m_pcm.get_length());
assert(m_mfcc_proc!=NULL); assert(m_mfcc_proc!=NULL);
assert(m_fftw!=NULL); assert(m_fftw!=NULL);
w.run(m_pcm); w.run(m_pcm);
process(m_pcm,m_fft,m_mfcc); process(m_pcm,m_fft,m_mfcc,m_dominant_freq);
// rerun the normalised version // rerun the normalised version
normalise(m_n_pcm); normalise(m_n_pcm);
w.run(m_n_pcm); w.run(m_n_pcm);
process(m_n_pcm,m_n_fft,m_n_mfcc); process(m_n_pcm,m_n_fft,m_n_mfcc,m_n_dominant_freq);
if (ditchpcm) { if (ditchpcm) {
m_pcm.clear(); m_pcm.clear();
m_n_pcm.clear(); m_n_pcm.clear();
} }
} }
void block::init_fft(u32 block_size) void block::init_fft(u32 block_size)
{ {
if (m_fftw == NULL || m_fftw->m_length!=block_size) { if (m_fftw == NULL || m_fftw->m_length!=block_size) {
if (m_fftw == NULL) delete m_fftw; if (m_fftw == NULL) delete m_fftw;
m_fftw = new FFT(block_size,100); m_fftw = new FFT(block_size,100);
if (m_mfcc_proc == NULL) delete m_mfcc_proc; if (m_mfcc_proc == NULL) delete m_mfcc_proc;
m_mfcc_proc = new Aquila::Mfcc(block_size); m_mfcc_proc = new Aquila::Mfcc(block_size);
} }
} }
void block::process(const sample &pcm, sample &fft, sample &mfcc) { void block::process(const sample &pcm, sample &fft, sample &mfcc, float &freq) {
m_fftw->impulse2freq(pcm.get_buffer()); m_fftw->impulse2freq(pcm.get_buffer());
m_fftw->calculate_bins(); m_fftw->calculate_bins();
// calculate fft // calculate fft
std::vector<std::complex<double> > mfspec; std::vector<std::complex<double> > mfspec;
for (u32 i=0; i<m_block_size; ++i) { for (u32 i=0; i<m_block_size; ++i) {
mfspec.push_back(std::complex<double>(m_fftw->m_spectrum[i][0], mfspec.push_back(std::complex<double>(m_fftw->m_spectrum[i][0],
m_fftw->m_spectrum[i][1])); m_fftw->m_spectrum[i][1]));
} }
u32 fft_size = m_block_size; freq = m_fftw->calculate_dominant_freq();
if (fft_size>100) {
fft.crop_to(100);
fft_size=100;
}
for (u32 i=0; i<fft_size; ++i) { u32 fft_size = m_block_size;
fft[i]=m_fftw->m_bin[i]; if (fft_size>100) {
} fft.crop_to(100);
fft_size=100;
}
for (u32 i=0; i<fft_size; ++i) {
fft[i]=m_fftw->m_bin[i];
}
// calculate mfcc // calculate mfcc
std::vector<double> m = m_mfcc_proc->calculate(mfspec,MFCC_FILTERS); std::vector<double> m = m_mfcc_proc->calculate(mfspec,MFCC_FILTERS);
for (u32 i=0; i<MFCC_FILTERS; ++i) { for (u32 i=0; i<MFCC_FILTERS; ++i) {
mfcc[i] = m[i]; mfcc[i] = m[i];
} }
} }
#define FFT_BIAS 200
double block::_compare(const sample &fft_a, const sample &mfcc_a, double block::_compare(const sample &fft_a, const sample &mfcc_a,
const sample &fft_b, const sample &mfcc_b, const sample &fft_b, const sample &mfcc_b,
const search_params &params) const const search_params &params) const
{ {
double mfcc_acc=0; double mfcc_acc=0;
double fft_acc=0; double fft_acc=0;
u32 fft_start = params.m_fft1_start; u32 fft_start = params.m_fft1_start;
u32 fft_end = fmin(params.m_fft1_end,m_fft.get_length()); u32 fft_end = fmin(params.m_fft1_end,m_fft.get_length());
if (params.m_ratio==0) { if (params.m_ratio==0) {
for (u32 i=fft_start; i<fft_end; ++i) {
fft_acc+=square(fft_a[i]-fft_b[i]);
}
return (fft_acc/(float)fft_a.get_length())*FFT_BIAS;
}
if (params.m_ratio==1) {
for (u32 i=0; i<MFCC_FILTERS; ++i) {
mfcc_acc+=square(mfcc_a[i]-mfcc_b[i]);
}
return mfcc_acc/(float)MFCC_FILTERS;
}
// calculate both
for (u32 i=fft_start; i<fft_end; ++i) { for (u32 i=fft_start; i<fft_end; ++i) {
fft_acc+=square(fft_a[i]-fft_b[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]);
} }
return (fft_acc/(float)fft_a.get_length())*FFT_BIAS;
}
return blend(fft_acc/(float)fft_a.get_length(), if (params.m_ratio==1) {
mfcc_acc/(float)MFCC_FILTERS, for (u32 i=0; i<MFCC_FILTERS; ++i) {
params.m_ratio); mfcc_acc+=square(mfcc_a[i]-mfcc_b[i]);
}
return mfcc_acc/(float)MFCC_FILTERS;
}
// calculate both
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]);
}
return blend(fft_acc/(float)fft_a.get_length(),
mfcc_acc/(float)MFCC_FILTERS,
params.m_ratio);
} }
double block::compare(const block &other, const search_params &params) const { double block::compare(const block &other, const search_params &params) const {
return blend( return blend(
blend(_compare(m_fft, m_mfcc, other.m_fft, other.m_mfcc, params), blend(_compare(m_fft, m_mfcc, other.m_fft, other.m_mfcc, params),
_compare(m_n_fft, m_n_mfcc, other.m_n_fft, other.m_n_mfcc, params), _compare(m_n_fft, m_n_mfcc, other.m_n_fft, other.m_n_mfcc, params),
params.m_n_ratio), params.m_n_ratio),
other.m_usage, params.m_usage_importance); other.m_usage, params.m_usage_importance);
} }
ios &spiralcore::operator||(ios &s, block &b) { ios &spiralcore::operator||(ios &s, block &b) {
u32 version=2; u32 version=3;
string id("block"); string id("block");
s||id||version; s||id||version;
if (version>1) s||b.m_id; 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_pcm||b.m_fft||b.m_mfcc;
s||b.m_n_pcm||b.m_n_fft||b.m_n_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; s||b.m_block_size||b.m_rate||b.m_orig_filename;
stream_vector(s,b.m_synapse); stream_vector(s,b.m_synapse);
return s; return s;
} }
bool block::unit_test() { bool block::unit_test() {
stream_unit_test(); stream_unit_test();
sample ntest(3); sample ntest(3);
u32 idx=0; u32 idx=0;
ntest[idx++]=-1; ntest[idx++]=-1;
ntest[idx++]=1; ntest[idx++]=1;
ntest[idx++]=-1; ntest[idx++]=-1;
idx=0; idx=0;
normalise(ntest); normalise(ntest);
assert(feq(ntest[idx++],-1)); assert(feq(ntest[idx++],-1));
assert(feq(ntest[idx++],1)); assert(feq(ntest[idx++],1));
assert(feq(ntest[idx++],-1)); assert(feq(ntest[idx++],-1));
idx=0; idx=0;
ntest[idx++]=-2; ntest[idx++]=-2;
ntest[idx++]=2; ntest[idx++]=2;
ntest[idx++]=-2; ntest[idx++]=-2;
normalise(ntest); normalise(ntest);
idx=0; idx=0;
assert(feq(ntest[idx++],-1)); assert(feq(ntest[idx++],-1));
assert(feq(ntest[idx++],1)); assert(feq(ntest[idx++],1));
assert(feq(ntest[idx++],-1)); assert(feq(ntest[idx++],-1));
idx=0; idx=0;
ntest[idx++]=19; ntest[idx++]=19;
ntest[idx++]=20; ntest[idx++]=20;
ntest[idx++]=19; ntest[idx++]=19;
normalise(ntest); normalise(ntest);
idx=0; idx=0;
assert(feq(ntest[idx++],-1)); assert(feq(ntest[idx++],-1));
assert(feq(ntest[idx++],1)); assert(feq(ntest[idx++],1));
assert(feq(ntest[idx++],-1)); assert(feq(ntest[idx++],-1));
sample data(200); sample data(200);
for (u32 i=0; i<data.get_length(); i++) { for (u32 i=0; i<data.get_length(); i++) {
data[i]=i/(float)data.get_length(); data[i]=i/(float)data.get_length();
} }
window w; window w;
w.init(data.get_length()); w.init(data.get_length());
w.set_current_type(window::RECTANGLE); w.set_current_type(window::RECTANGLE);
block bb(0,"test",data,44100,w); block bb(0,"test",data,44100,w);
assert(bb.m_pcm.get_length()==data.get_length()); assert(bb.m_pcm.get_length()==data.get_length());
//assert(bb.m_fft.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_mfcc.get_length()==MFCC_FILTERS);
assert(bb.m_orig_filename==string("test")); assert(bb.m_orig_filename==string("test"));
assert(bb.m_rate==44100); assert(bb.m_rate==44100);
assert(bb.m_block_size==data.get_length()); assert(bb.m_block_size==data.get_length());
ofstream of("test_data/blocktest.bin",ios::binary); ofstream of("test_data/blocktest.bin",ios::binary);
of||bb; of||bb;
of.close(); of.close();
cerr<<"written"<<endl; cerr<<"written"<<endl;
ifstream ifs("test_data/blocktest.bin",ios::binary); ifstream ifs("test_data/blocktest.bin",ios::binary);
block bbb; block bbb;
ifs||bbb; ifs||bbb;
ifs.close(); ifs.close();
assert(bbb.m_pcm.get_length()==data.get_length()); assert(bbb.m_pcm.get_length()==data.get_length());
//assert(bb.m_fft.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_mfcc.get_length()==MFCC_FILTERS);
assert(bbb.m_orig_filename==string("test")); assert(bbb.m_orig_filename==string("test"));
assert(bbb.m_rate==44100); assert(bbb.m_rate==44100);
assert(bbb.m_block_size==data.get_length()); assert(bbb.m_block_size==data.get_length());
search_params p(0,0,0,100,0); search_params p(0,0,0,100,0);
block bb2(0,"test",data,44100,w); block bb2(0,"test",data,44100,w);
assert(bb.compare(bb2,p)==0); assert(bb.compare(bb2,p)==0);
p.m_ratio=1; p.m_ratio=1;
assert(bb.compare(bb2,p)==0); 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);
p.m_ratio=0.5; p.m_ratio=0.5;
assert(bb.compare(bb2,p)==0); assert(bb.compare(bb3,p)!=0);
cpy=bb3;
}
sample data2(200); assert(cpy.m_pcm.get_length()==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);
p.m_ratio=0.5;
assert(bb.compare(bb3,p)!=0);
cpy=bb3;
}
assert(cpy.m_pcm.get_length()==200);
return true; return true;
} }

View File

@ -29,8 +29,8 @@
namespace spiralcore { namespace spiralcore {
class block { class block {
public: public:
// runs analysis on pcm // runs analysis on pcm
block(u64 id, const std::string &filename, const sample &pcm, u32 rate, const window &w, bool ditchpcm=false); block(u64 id, const std::string &filename, const sample &pcm, u32 rate, const window &w, bool ditchpcm=false);
block() {} block() {}
@ -47,10 +47,12 @@ public:
std::vector<u32> &get_synapse() { return m_synapse; } std::vector<u32> &get_synapse() { return m_synapse; }
const std::vector<u32> &get_synapse_const() const { return m_synapse; } const std::vector<u32> &get_synapse_const() const { return m_synapse; }
float &get_usage() { return m_usage; } float &get_usage() { return m_usage; }
float get_freq() const { return m_dominant_freq; }
float get_n_freq() const { return m_n_dominant_freq; }
private: private:
void process(const sample &pcm, sample &fft, sample &mfcc); void process(const sample &pcm, sample &fft, sample &mfcc, float &freq);
double _compare(const sample &fft_a, const sample &mfcc_a, double _compare(const sample &fft_a, const sample &mfcc_a,
const sample &fft_b, const sample &mfcc_b, const sample &fft_b, const sample &mfcc_b,
@ -75,11 +77,14 @@ private:
std::vector<u32> m_synapse; std::vector<u32> m_synapse;
float m_usage; float m_usage;
float m_dominant_freq;
float m_n_dominant_freq;
friend ios &operator||(ios &s, block &b); friend ios &operator||(ios &s, block &b);
}; };
ios &operator||(ios &s, block &b); ios &operator||(ios &s, block &b);
} }

View File

@ -71,19 +71,19 @@ void brain::init(u32 block_size, u32 overlap, window::type t, bool ditchpcm) {
u32 count=0; u32 count=0;
for (std::list<sound>::iterator i=m_samples.begin(); i!=m_samples.end(); ++i) { for (std::list<sound>::iterator i=m_samples.begin(); i!=m_samples.end(); ++i) {
count++; count++;
chop_and_add(i->m_sample, count, ditchpcm); chop_and_add(*i, count, ditchpcm);
} }
status::update("all samples processed"); status::update("all samples processed");
} }
void brain::chop_and_add(const sample &s, u32 count, bool ditchpcm) { void brain::chop_and_add(const sound &s, u32 count, bool ditchpcm) {
u32 pos=0; u32 pos=0;
if (m_overlap>=m_block_size) m_overlap=0; if (m_overlap>=m_block_size) m_overlap=0;
while (pos+m_block_size-1<s.get_length()) { while (pos+m_block_size-1<s.m_sample.get_length()) {
status::update("processing sample %d: %d%%",count,(int)(pos/(float)s.get_length()*100)); status::update("processing sample %d: %d%%",count,(int)(pos/(float)s.m_sample.get_length()*100));
sample region; sample region;
s.get_region(region,pos,pos+m_block_size-1); s.m_sample.get_region(region,pos,pos+m_block_size-1);
m_blocks.push_back(block(m_blocks.size(),"",region,44100,m_window,ditchpcm)); m_blocks.push_back(block(m_blocks.size(),s.m_filename,region,44100,m_window,ditchpcm));
pos += (m_block_size-m_overlap); pos += (m_block_size-m_overlap);
} }
} }

View File

@ -84,12 +84,13 @@ public:
private: private:
void chop_and_add(const sample &s, u32 count, bool ditchpcm=false); void chop_and_add(const sound &s, u32 count, bool ditchpcm=false);
void deplete_usage(); void deplete_usage();
u32 stickify(const block &target, u32 closest_index, f32 dist, const search_params &params); u32 stickify(const block &target, u32 closest_index, f32 dist, const search_params &params);
vector<block> m_blocks; vector<block> m_blocks;
std::list<sound> m_samples; std::list<sound> m_samples;
vector<string> m_active_sounds;
u32 m_block_size; u32 m_block_size;
u32 m_overlap; u32 m_overlap;

View File

@ -25,58 +25,73 @@ using namespace std;
static const int MAX_FFT_LENGTH = 4096; static const int MAX_FFT_LENGTH = 4096;
FFT::FFT(u32 length, u32 bins) : FFT::FFT(u32 length, u32 bins) :
m_length(length), m_length(length),
m_num_bins(bins), m_num_bins(bins),
m_in(new double[length]), m_in(new double[length]),
m_spectrum(new fftw_complex[length]), m_spectrum(new fftw_complex[length]),
m_bin(new float[bins]) m_bin(new float[bins])
{ {
memset(m_spectrum,0,sizeof(fftw_complex)*length); memset(m_spectrum,0,sizeof(fftw_complex)*length);
m_plan = fftw_plan_dft_r2c_1d(m_length, m_in, m_spectrum, FFTW_ESTIMATE); m_plan = fftw_plan_dft_r2c_1d(m_length, m_in, m_spectrum, FFTW_ESTIMATE);
} }
FFT::~FFT() FFT::~FFT()
{ {
delete[] m_in; delete[] m_in;
fftw_destroy_plan(m_plan); fftw_destroy_plan(m_plan);
} }
void FFT::impulse2freq(const float *imp) void FFT::impulse2freq(const float *imp)
{ {
unsigned int i; unsigned int i;
for (i=0; i<m_length; i++) for (i=0; i<m_length; i++)
{ {
m_in[i] = imp[i]; m_in[i] = imp[i];
} }
fftw_execute(m_plan); fftw_execute(m_plan);
}
static const float SRATE = 44100;
float FFT::calculate_dominant_freq() {
double highest = 0;
u32 index = 0;
for (u32 i=0; i<m_length/2; ++i) {
double t = m_spectrum[i][0]*m_spectrum[i][0];
if (t>highest) {
index=i;
highest=t;
}
}
return index * (SRATE/(float)m_length);
} }
void FFT::calculate_bins() { void FFT::calculate_bins() {
float useful_area = m_length/2; float useful_area = m_length/2;
for (unsigned int n=0; n<m_num_bins; n++) { for (unsigned int n=0; n<m_num_bins; n++) {
float value = 0; float value = 0;
float f = n/(float)m_num_bins; float f = n/(float)m_num_bins;
float t = (n+1)/(float)m_num_bins; float t = (n+1)/(float)m_num_bins;
//f*=f; //f*=f;
//t*=t; //t*=t;
u32 from = f*useful_area; u32 from = f*useful_area;
u32 to = t*useful_area; u32 to = t*useful_area;
//cerr<<"fft bin:"<<from<<" "<<to<<" - "<<m_length<<endl; //cerr<<"fft bin:"<<from<<" "<<to<<" - "<<m_length<<endl;
for (u32 i=from; i<=to; i++) { for (u32 i=from; i<=to; i++) {
if (i<m_length) { if (i<m_length) {
value += m_spectrum[i][0]; value += m_spectrum[i][0]*m_spectrum[i][0];
} }
} }
if (value<0) value=-value; if (value<0) value=-value;
m_bin[n]=value; m_bin[n]=value;
} }
} }

View File

@ -24,13 +24,14 @@
namespace spiralcore { namespace spiralcore {
class FFT class FFT
{ {
public: public:
FFT(u32 length, u32 num_bins); FFT(u32 length, u32 num_bins);
~FFT(); ~FFT();
void impulse2freq(const float *imp); void impulse2freq(const float *imp);
void calculate_bins(); void calculate_bins();
float calculate_dominant_freq();
fftw_plan m_plan; fftw_plan m_plan;
u32 m_length; u32 m_length;
@ -38,7 +39,7 @@ public:
double *m_in; double *m_in;
fftw_complex *m_spectrum; fftw_complex *m_spectrum;
float *m_bin; float *m_bin;
}; };
} }

View File

@ -0,0 +1,39 @@
// Copyright (C) 2015 Foam Kernow
//
// 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.
#include <pitchshift.h>
using namespace spiralcore;
using namespace std;
using namespace RubberBand;
RubberBandStretcher *pitchshift::m_stretcher = NULL;
void pitchshift::init(u32 srate) {
if (m_stretcher!=NULL) delete m_stretcher;
m_stretcher = new RubberBandStretcher(srate,1,
RubberBandStretcher::OptionProcessRealTime);
}
void pitchshift::process(const sample &in, float freq_scale, sample &out) {
if (freq_scale<1/256) freq_scale=1;
if (freq_scale>255) freq_scale=255;
m_stretcher->setPitchScale(freq_scale);
const float *in_ptr = in.get_buffer();
m_stretcher->process(&in_ptr, in.get_length(), false);
float *out_ptr = out.get_non_const_buffer();
m_stretcher->retrieve(&out_ptr, out.get_length());
}

View File

@ -0,0 +1,35 @@
// Copyright (C) 2015 Foam Kernow
//
// 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.
#ifndef SPIRALCORE_PITCHSHIFT
#define SPIRALCORE_PITCHSHIFT
#include <jellyfish/core/types.h>
#include <jellyfish/fluxa/sample.h>
#include <rubberband/RubberBandStretcher.h>
namespace spiralcore {
class pitchshift {
public:
static void init(u32 srate);
static void process(const sample &in, float freq_change, sample &out);
static RubberBand::RubberBandStretcher *m_stretcher;
};
}
#endif

View File

@ -16,76 +16,78 @@
#include "renderer.h" #include "renderer.h"
#include <iostream> #include <iostream>
#include "pitchshift.h"
using namespace spiralcore; using namespace spiralcore;
using namespace std; using namespace std;
void renderer::init(brain &source, brain &target) { void renderer::init(brain &source, brain &target) {
m_volume=1; m_volume=1;
m_playing=false; m_playing=false;
m_source=source; m_source=source;
m_target=target; m_target=target;
m_target_time=0; m_target_time=0;
m_render_time=0; m_render_time=0;
m_n_mix=0; m_n_mix=0;
m_target_mix=0; m_target_mix=0;
m_render_blocks.clear(); m_render_blocks.clear();
m_search_algo=BASIC; m_search_algo=BASIC;
m_slide_error=1; m_slide_error=1;
m_target_index=0; m_target_index=0;
m_render_index=0; m_render_index=0;
m_stretch=1; m_stretch=1;
m_last_tgt_shift=0; m_last_tgt_shift=0;
m_autotune=0;
} }
static int ratio_time = 0; static int ratio_time = 0;
void renderer::reset() { void renderer::reset() {
m_target_time=0; m_target_time=0;
m_render_time=0; m_render_time=0;
m_target_index=0; m_target_index=0;
m_render_index=0; m_render_index=0;
m_render_blocks.clear(); m_render_blocks.clear();
m_source.jiggle(); m_source.jiggle();
} }
void renderer::process(u32 nframes, float *buf) { void renderer::process(u32 nframes, float *buf) {
if (!m_playing) return; if (!m_playing) return;
if (!find_render_blocks(nframes)) return; if (!find_render_blocks(nframes)) return;
render(nframes,buf); render(nframes,buf);
clean_up(); clean_up();
m_render_time+=nframes; m_render_time+=nframes;
//m_target_time+=nframes/(float)m_stretch; //m_target_time+=nframes/(float)m_stretch;
} }
// target_time = samples time into target stream // target_time = samples time into target stream
// render_time = position in output stream, updated per process - used for offsets // render_time = position in output stream, updated per process - used for offsets
bool renderer::find_render_blocks(u32 nframes) { bool renderer::find_render_blocks(u32 nframes) {
// get new blocks from source for the current buffer // get new blocks from source for the current buffer
// where are we phase? // where are we phase?
u32 tgt_shift = m_target.get_block_size()-m_target.get_overlap(); u32 tgt_shift = m_target.get_block_size()-m_target.get_overlap();
m_target_index = m_target_time/(float)tgt_shift; m_target_index = m_target_time/(float)tgt_shift;
u32 tgt_end = (m_target_time+nframes)/(float)tgt_shift; u32 tgt_end = (m_target_time+nframes)/(float)tgt_shift;
u32 rnd_end = (m_render_time+nframes)/(float)tgt_shift; u32 rnd_end = (m_render_time+nframes)/(float)tgt_shift;
// stuff has changed - recompute and abort // stuff has changed - recompute and abort
if (tgt_shift!=m_last_tgt_shift || if (tgt_shift!=m_last_tgt_shift ||
tgt_end>=m_target.get_num_blocks() || tgt_end>=m_target.get_num_blocks() ||
m_source.get_num_blocks()==0) { m_source.get_num_blocks()==0) {
reset(); reset();
m_last_tgt_shift = tgt_shift; m_last_tgt_shift = tgt_shift;
// next time... // next time...
return false; return false;
} }
/* /*
cerr<<"-----------------"<<endl; cerr<<"-----------------"<<endl;
cerr<<"tgt start:"<<m_target_index<<endl; cerr<<"tgt start:"<<m_target_index<<endl;
cerr<<"tgt end:"<<tgt_end<<endl; cerr<<"tgt end:"<<tgt_end<<endl;
@ -97,280 +99,161 @@ bool renderer::find_render_blocks(u32 nframes) {
cerr<<"render time (index) "<<m_render_index*tgt_shift<<endl; cerr<<"render time (index) "<<m_render_index*tgt_shift<<endl;
cerr<<"real vs index = "<<(s32)m_render_time-(s32)(m_render_index*tgt_shift)<<endl; cerr<<"real vs index = "<<(s32)m_render_time-(s32)(m_render_index*tgt_shift)<<endl;
cerr<<m_render_blocks.size()<<endl; cerr<<m_render_blocks.size()<<endl;
*/ */
// search phase // search phase
// get indices for current buffer // get indices for current buffer
u32 counter = m_render_index; u32 counter = m_render_index;
//u32 cur_time = m_render_time; //u32 cur_time = m_render_time;
while (counter<=rnd_end) { while (counter<=rnd_end) {
u32 time=m_render_index*tgt_shift; u32 time=m_render_index*tgt_shift;
u32 src_index=0; u32 src_index=0;
switch (m_search_algo) { switch (m_search_algo) {
case BASIC: case BASIC:
src_index = m_source.search(m_target.get_block(m_target_index), m_search_params); src_index = m_source.search(m_target.get_block(m_target_index), m_search_params);
break; break;
case REV_BASIC: case REV_BASIC:
src_index = m_source.rev_search(m_target.get_block(m_target_index), m_search_params); src_index = m_source.rev_search(m_target.get_block(m_target_index), m_search_params);
break; break;
case SYNAPTIC: case SYNAPTIC:
case SYNAPTIC_SLIDE: case SYNAPTIC_SLIDE:
src_index = m_source.search_synapses(m_target.get_block(m_target_index), m_search_params); src_index = m_source.search_synapses(m_target.get_block(m_target_index), m_search_params);
break; break;
}
if (m_search_algo==SYNAPTIC_SLIDE) {
m_render_blocks.push_back(render_block(src_index,m_target_index,time));
if (m_source.get_current_error()<m_slide_error &&
m_render_index%m_stretch==0) {
m_target_index++;
m_target_time+=tgt_shift;
}
} else {
// put them in the index list
m_render_blocks.push_back(render_block(src_index,m_target_index,time));
if (m_render_index%m_stretch==0) {
m_target_index++;
m_target_time+=tgt_shift;
}
}
counter++;
m_render_index++;
} }
return true;
if (m_search_algo==SYNAPTIC_SLIDE) {
m_render_blocks.push_back(render_block(src_index,m_target_index,time));
if (m_source.get_current_error()<m_slide_error &&
m_render_index%m_stretch==0) {
m_target_index++;
m_target_time+=tgt_shift;
}
} else {
// put them in the index list
m_render_blocks.push_back(render_block(src_index,m_target_index,time));
if (m_render_index%m_stretch==0) {
m_target_index++;
m_target_time+=tgt_shift;
}
}
counter++;
m_render_index++;
}
return true;
} }
void renderer::render(u32 nframes, float *buf) { void renderer::render(u32 nframes, float *buf) {
// render phase sample render_pcm(m_source.get_block_size());
// render all blocks in list
for (std::list<render_block>::iterator i=m_render_blocks.begin(); i!=m_render_blocks.end(); ++i) {
const sample &pcm=m_source.get_block(i->m_index).get_pcm();
const sample &n_pcm=m_source.get_block(i->m_index).get_n_pcm();
const sample &target_pcm=m_target.get_block(i->m_tgt_index).get_pcm();
// get the sample offset into the buffer
s32 offset = i->m_time-m_render_time;
// assume midway through block // render phase
u32 block_start = offset; // render all blocks in list
u32 buffer_start = 0; for (std::list<render_block>::iterator i=m_render_blocks.begin(); i!=m_render_blocks.end(); ++i) {
if (offset<0) { const sample &pcm=m_source.get_block(i->m_index).get_pcm();
block_start=-offset; const sample &n_pcm=m_source.get_block(i->m_index).get_n_pcm();
if (block_start>=pcm.get_length()) i->m_finished=true; const sample &target_pcm=m_target.get_block(i->m_tgt_index).get_pcm();
} else { // block is midway through buffer // get the sample offset into the buffer
block_start=0; s32 offset = i->m_time-m_render_time;
buffer_start=offset;
}
// cerr<<"-----------------"<<endl;
// cerr<<"block start:"<<block_start<<endl;
// cerr<<"buffer start:"<<buffer_start<<endl;
if (!i->m_finished) {
// mix in
u32 buffer_pos = buffer_start;
u32 block_pos = block_start;
u32 block_end = pcm.get_length();
while (block_pos<block_end && buffer_pos<nframes) { // assume midway through block
// mix with normalised version u32 block_start = offset;
float brain_sample = (pcm[block_pos]*(1-m_n_mix)+ u32 buffer_start = 0;
n_pcm[block_pos]*m_n_mix); if (offset<0) {
block_start=-offset;
// for mixing with target audio if (block_start>=pcm.get_length() ||
float target_sample = target_pcm[block_pos]; i->m_position>=pcm.get_length()) i->m_finished=true;
} else { // block is midway through buffer
buf[buffer_pos]+=(brain_sample*(1-m_target_mix) + block_start=0;
target_sample*m_target_mix)*0.2*m_volume; buffer_start=offset;
++buffer_pos;
++block_pos;
}
}
} }
// cerr<<"-----------------"<<endl;
// cerr<<"block start:"<<block_start<<endl;
// cerr<<"buffer start:"<<buffer_start<<endl;
float pitch_scale = m_target.get_block(i->m_tgt_index).get_freq() /
m_source.get_block(i->m_index).get_freq();
// fade in/out autotune
pitch_scale = pitch_scale*m_autotune + 1.0f*(1-m_autotune);
//pitchshift::process(pcm,pitch_scale,render_pcm);
if (!i->m_finished) {
// mix in
u32 buffer_pos = buffer_start;
u32 block_pos = block_start;
u32 block_end = pcm.get_length();
while (block_pos<block_end && buffer_pos<nframes) {
// mix with normalised version
float brain_sample = (pcm[i->m_position]*(1-m_n_mix)+
n_pcm[block_pos]*m_n_mix);
// for mixing with target audio
float target_sample = target_pcm[block_pos];
buf[buffer_pos]+=(brain_sample*(1-m_target_mix) +
target_sample*m_target_mix)*0.2*m_volume;
i->m_position+=pitch_scale;
++buffer_pos;
++block_pos;
}
}
}
} }
void renderer::clean_up() { void renderer::clean_up() {
// cleanup phase // cleanup phase
// delete old ones // delete old ones
std::list<render_block>::iterator i=m_render_blocks.begin(); std::list<render_block>::iterator i=m_render_blocks.begin();
std::list<render_block>::iterator ni=m_render_blocks.begin(); std::list<render_block>::iterator ni=m_render_blocks.begin();
while(i!=m_render_blocks.end()) { while(i!=m_render_blocks.end()) {
ni++; ni++;
if (i->m_finished) m_render_blocks.erase(i); if (i->m_finished) m_render_blocks.erase(i);
i=ni; i=ni;
} }
} }
void renderer::old_process(u32 nframes, float *buf) {
if (!m_playing) return;
// get new blocks from source for the current buffer
u32 tgt_shift = m_target.get_block_size()-m_target.get_overlap();
u32 tgt_end = (m_target_time+nframes)/(float)tgt_shift;
if (tgt_shift!=m_last_tgt_shift ||
tgt_end>=m_target.get_num_blocks() || m_source.get_num_blocks()==0) {
reset();
m_last_tgt_shift = tgt_shift;
// next time...
return;
}
cerr<<"-----------------"<<endl;
cerr<<"tgt start:"<<m_target_index<<endl;
cerr<<"tgt end:"<<tgt_end<<endl;
cerr<<":"<<tgt_end-m_target_index<<endl;
cerr<<"block time "<<m_target_index*tgt_shift<<endl;
cerr<<"render time "<<m_render_time<<endl;
cerr<<": "<<(s32)m_render_time-(s32)(m_target_index*tgt_shift)<<endl;
// cerr<<"-----------------"<<endl;
// cerr<<"tgt start:"<<m_target_index<<endl;
// cerr<<"tgt end:"<<tgt_end<<endl;
// get indices for current buffer
u32 counter = m_target_index;
//u32 cur_time = m_render_time;
while (counter<=tgt_end) {
u32 time=m_render_index*tgt_shift;
u32 src_index=0;
switch (m_search_algo) {
case BASIC:
src_index = m_source.search(m_target.get_block(m_target_index), m_search_params);
break;
case REV_BASIC:
src_index = m_source.rev_search(m_target.get_block(m_target_index), m_search_params);
break;
case SYNAPTIC:
case SYNAPTIC_SLIDE:
src_index = m_source.search_synapses(m_target.get_block(m_target_index), m_search_params);
break;
}
if (m_search_algo==SYNAPTIC_SLIDE) {
m_render_blocks.push_back(render_block(src_index,m_target_index,time));
if (m_source.get_current_error()<m_slide_error) {
m_target_index++;
}
else{ cerr<<"skip"<<endl; }
} else {
// put them in the index list
m_render_blocks.push_back(render_block(src_index,m_target_index,time));
m_target_index++;
}
counter++;
}
// render all blocks in list
for (std::list<render_block>::iterator i=m_render_blocks.begin(); i!=m_render_blocks.end(); ++i) {
const sample &pcm=m_source.get_block(i->m_index).get_pcm();
const sample &n_pcm=m_source.get_block(i->m_index).get_n_pcm();
const sample &target_pcm=m_target.get_block(i->m_tgt_index).get_pcm();
// get the sample offset into the buffer
s32 offset = i->m_time-m_render_time;
// assume midway through block
u32 block_start = offset;
u32 buffer_start = 0;
if (offset<0) {
block_start=-offset;
if (block_start>=pcm.get_length()) i->m_finished=true;
} else { // block is midway through buffer
block_start=0;
buffer_start=offset;
}
// cerr<<"-----------------"<<endl;
// cerr<<"block start:"<<block_start<<endl;
// cerr<<"buffer start:"<<buffer_start<<endl;
if (!i->m_finished) {
// mix in
u32 buffer_pos = buffer_start;
u32 block_pos = block_start;
u32 block_end = pcm.get_length();
while (block_pos<block_end && buffer_pos<nframes) {
// mix with normalised version
float brain_sample = (pcm[block_pos]*(1-m_n_mix)+
n_pcm[block_pos]*m_n_mix);
// for mixing with target audio
float target_sample = target_pcm[block_pos];
buf[buffer_pos]+=(brain_sample*(1-m_target_mix) +
target_sample*m_target_mix)*0.2*m_volume;
++buffer_pos;
++block_pos;
}
}
}
// delete old ones
std::list<render_block>::iterator i=m_render_blocks.begin();
std::list<render_block>::iterator ni=m_render_blocks.begin();
while(i!=m_render_blocks.end()) {
ni++;
if (i->m_finished) m_render_blocks.erase(i);
i=ni;
}
m_render_time+=nframes;
m_target_time+=nframes;
}
bool renderer::unit_test() { bool renderer::unit_test() {
brain source; brain source;
source.load_sound("test_data/up.wav"); source.load_sound("test_data/up.wav");
source.init(10,0,window::RECTANGLE); source.init(10,0,window::RECTANGLE);
brain target; brain target;
target.load_sound("test_data/up.wav"); target.load_sound("test_data/up.wav");
target.init(10,0,window::RECTANGLE); target.init(10,0,window::RECTANGLE);
renderer rr(source,target); renderer rr(source,target);
rr.set_playing(true); rr.set_playing(true);
float *buf=new float[400]; float *buf=new float[400];
rr.process(10,buf); rr.process(10,buf);
rr.process(10,buf); rr.process(10,buf);
assert(rr.m_render_blocks.size()==2); assert(rr.m_render_blocks.size()==2);
rr.process(10,buf); rr.process(10,buf);
assert(rr.m_render_blocks.size()==2); assert(rr.m_render_blocks.size()==2);
delete[] buf; delete[] buf;
buf=new float[20]; buf=new float[20];
rr.process(20,buf); rr.process(20,buf);
assert(rr.m_render_blocks.size()==3); assert(rr.m_render_blocks.size()==3);
rr.process(5,buf); rr.process(5,buf);
assert(rr.m_render_blocks.size()==1); assert(rr.m_render_blocks.size()==1);
target.init(10,5,window::RECTANGLE); target.init(10,5,window::RECTANGLE);
rr.process(10,buf); rr.process(10,buf);
rr.process(10,buf); rr.process(10,buf);
rr.process(10,buf); rr.process(10,buf);
rr.process(10,buf); rr.process(10,buf);
assert(rr.m_render_blocks.size()==4); assert(rr.m_render_blocks.size()==4);
delete[] buf; delete[] buf;
return true; return true;
} }

View File

@ -23,19 +23,19 @@
namespace spiralcore { namespace spiralcore {
class renderer { class renderer {
public: public:
renderer(brain &source, brain &target) : renderer(brain &source, brain &target) :
m_source(source), m_source(source),
m_target(target), m_target(target),
m_search_params(0,0,0,100,0) m_search_params(0,0,0,100,0)
{ init(source,target); } { init(source,target); }
enum search_algo { enum search_algo {
BASIC = 0, BASIC = 0,
REV_BASIC, REV_BASIC,
SYNAPTIC, SYNAPTIC,
SYNAPTIC_SLIDE SYNAPTIC_SLIDE
}; };
void init(brain &source, brain &target); void init(brain &source, brain &target);
@ -58,7 +58,7 @@ renderer(brain &source, brain &target) :
static bool unit_test(); static bool unit_test();
private: private:
bool find_render_blocks(u32 nframes); bool find_render_blocks(u32 nframes);
void render(u32 nframes, float *buf); void render(u32 nframes, float *buf);
@ -68,12 +68,17 @@ private:
// realtime stuff // realtime stuff
class render_block { class render_block {
public: public:
render_block(u32 index, u32 tgt_index, u32 time) : render_block(u32 index, u32 tgt_index, u32 time) :
m_index(index), m_tgt_index(tgt_index), m_time(time), m_finished(false) {} m_index(index),
u32 m_index; m_tgt_index(tgt_index),
u32 m_tgt_index; // original target block m_time(time),
u32 m_time; // in samples m_finished(false),
bool m_finished; m_position(0) {}
u32 m_index;
u32 m_tgt_index; // original target block
u32 m_time; // in samples
bool m_finished;
float m_position; // in samples
}; };
brain &m_source; brain &m_source;
@ -89,13 +94,14 @@ private:
u32 m_stretch; u32 m_stretch;
float m_n_mix; float m_n_mix;
float m_target_mix; float m_target_mix;
float m_autotune;
search_algo m_search_algo; search_algo m_search_algo;
double m_slide_error; double m_slide_error;
u32 m_last_tgt_shift; u32 m_last_tgt_shift;
std::list<render_block> m_render_blocks; std::list<render_block> m_render_blocks;
}; };
} }