// Copyright (C) 2022 Then Try This // // 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 #include #include #include #include #include "brain.h" #include "status.h" using namespace std; using namespace spiralcore; static const u32 NUM_FIXED_SYNAPSES = 1000; static const double usage_factor = 1000; brain::brain() : m_current_block_index(0), m_current_error(0), m_average_error(0), m_usage_falloff(0.9) { status::update("brain ready..."); } // load, chop up and add to brain // todo: add tags void brain::load_sound(std::string filename, stereo_mode mode) { SF_INFO sfinfo; sfinfo.format=0; SNDFILE* f=sf_open(filename.c_str(), SFM_READ, &sfinfo); if (f!=NULL) { sample s(sfinfo.frames); float *temp = new float[sfinfo.channels * sfinfo.frames]; sf_read_float(f, temp, sfinfo.channels * sfinfo.frames); if (mode==MIX) { for(u32 i=0; i1) si++; s[i]=temp[si]; } } delete[] temp; m_samples.push_back(sound(filename,s)); status::update("loaded %s",filename.c_str()); } } void brain::delete_sound(std::string filename) { for (auto i=m_samples.begin(); i!=m_samples.end(); ++i) { if (i->m_filename==filename) { m_samples.erase(i); status::update("deleted %s",filename.c_str()); return; } } recompute_sample_sections(); } void brain::activate_sound(std::string filename, bool active) { for (auto i=m_samples.begin(); i!=m_samples.end(); ++i) { if (filename==i->m_filename) { i->m_enabled=active; } } } void brain::clear() { m_blocks.clear(); m_samples.clear(); m_active_sounds.clear(); } // rewrites whole brain void brain::init(u32 block_size, u32 overlap, window::type t, bool ditchpcm) { m_blocks.clear(); m_block_size = block_size; m_overlap = overlap; m_window.init(block_size); m_window.set_current_type(t); u32 count=0; for (auto &s:m_samples) { status::sound_item(s.m_filename,"lightgrey"); } for (auto &s:m_samples) { status::sound_item(s.m_filename,"yellow"); count++; chop_and_add(s, count, ditchpcm); if (count%2==0) status::sound_item(s.m_filename,"lightblue"); else status::sound_item(s.m_filename,"pink"); } status::sound_item_refresh(); status::update("all samples processed"); } void brain::chop_and_add(sound &s, u32 count, bool ditchpcm) { s.m_start = m_blocks.size(); u32 pos=0; if (m_overlap>=m_block_size) m_overlap=0; u32 len = s.m_sample.get_length(); // need to stop the progress updates flooding osc u32 update_period = (len/m_block_size)/100; u32 update_tick = 0; while (pos+m_block_size-1update_period) { status::update("processing sample %d: %d%%",count,(int)(pos/(float)s.m_sample.get_length()*100)); update_tick=0; } update_tick++; } s.m_end = m_blocks.size()-1; s.m_num_blocks = s.m_end-s.m_start; } // needed after we delete a sample from the brain void brain::recompute_sample_sections() { u32 pos=0; for (auto &s : m_samples) { s.m_start = pos; pos += s.m_num_blocks; s.m_end = pos+1; } } const block &brain::get_block(u32 index) const { return m_blocks[index]; } // helper to do the stickyness comparison and sort out current_block_index u32 brain::stickify(const block &target, u32 closest_index, f32 dist, const search_params ¶ms) { u32 next_index = m_current_block_index+1; // if we have stickyness turned on and the next block exists if (params.m_stickyness>0 && next_indexfurthest) { furthest=diff; furthest_index = i; } } } } deplete_usage(); m_blocks[furthest_index].get_usage()+=usage_factor; m_current_block_index = furthest_index; return furthest_index; } // really slow - every to every comparison of blocks calculating average distance double brain::calc_average_diff(search_params ¶ms) { double diff=0; for (auto &i:m_blocks) { for (auto &j:m_blocks) { diff += j.compare(i,params); } diff/=(double)m_blocks.size(); } return diff; } void brain::build_synapses_thresh(search_params ¶ms, double thresh) { m_average_error = calc_average_diff(params)*thresh; double err = m_average_error*thresh; u32 brain_size = m_blocks.size(); u32 outer_index = 0; for (auto &i : m_blocks) { u32 index = 0; status::update("building synapses %d%%",(int)(outer_index/(float)brain_size*100)); for (auto &j : m_blocks) { if (index!=outer_index) { // collect connections that are under threshold in closeness double diff = i.compare(j,params); if (diff=m_blocks.size()) num_synapses=m_blocks.size()-1; for (auto &i:m_blocks) { status::update("building synapses %d%%",(int)(outer_index/(float)brain_size*100)); u32 index = 0; vector> collect; // collect comparisons to all other blocks for (auto &j:m_blocks) { assert(index(index,diff)); } ++index; } // sort them by closeness sort(collect.begin(),collect.end(), [](const pair &a, const pair &b) -> bool { return a.second0) { m_current_block_index=rand()%m_blocks.size(); } else { m_current_block_index=0; } } bool brain::is_block_active(u32 index) { // check each sample section for (auto &s:m_samples) { if (index>=s.m_start && indexparams.m_num_synapses); u32 synapse_count=0; // use m_num_synapses to restrict search // only makes sense when ordered by closeness in fixed mode vector::const_iterator i=current.get_synapse_const().begin(); while (i!=current.get_synapse_const().end() && synapse_count::iterator i=m_blocks.begin(); i!=m_blocks.end(); ++i) { i->get_usage()*=m_usage_falloff; } } // take another brain and rebuild this brain from bits of that one // (presumably this one is made from a single sample) /*void brain::resynth(const string &filename, const brain &other, const search_params ¶ms){ sample out((m_block_size-m_overlap)*m_blocks.size()); out.zero(); u32 pos = 0; u32 count = 0; cerr<::iterator i=m_blocks.begin(); i!=m_blocks.end(); ++i) { cerr<<'\r'; cerr<<"searching: "<0) { s||b.m_num_blocks||b.m_start||b.m_end||b.m_enabled; } return s; } ios &spiralcore::operator||(ios &s, brain &b) { u32 version=1; string id("brain"); // changes here need to be reflected in interface loading s||id||version; stream_vector(s,b.m_blocks); stream_list(s,b.m_samples); s||b.m_block_size||b.m_overlap||b.m_window; s||b.m_current_block_index||b.m_current_error|| b.m_average_error||b.m_usage_falloff; return s; } bool brain::unit_test() { brain b; assert(b.m_samples.size()==0); assert(b.m_blocks.size()==0); b.load_sound("test_data/100f32.wav",MIX); b.load_sound("test_data/100i16.wav",MIX); assert(b.m_samples.size()==2); b.init(10, 0, window::RECTANGLE); assert(b.m_blocks.size()==20); b.init(10, 5, window::RECTANGLE); assert(b.m_samples.size()==2); assert(b.m_blocks.size()==38); b.init(20, 5, window::RECTANGLE); assert(b.m_samples.size()==2); assert(b.m_blocks.size()==12); // replicate brains brain b2; b2.load_sound("test_data/up.wav",MIX); brain b3; b3.load_sound("test_data/up.wav",MIX); b2.init(512, 0, window::BLACKMAN); b3.init(512, 0, window::BLACKMAN); search_params p(1,0,0,100,0); assert(b3.search(b2.m_blocks[0],p)==0); //assert(b3.search(b2.m_blocks[9],p)==9); //assert(b3.search(b2.m_blocks[19],p)==19); //assert(b3.search(b2.m_blocks[29],p)==29); ofstream of("test_data/test.brain",ios::binary); of||b3; of.close(); brain b4; ifstream ifs("test_data/test.brain",ios::binary); ifs||b4; ifs.close(); assert(b3.m_samples.size()==b4.m_samples.size()); assert(b3.m_blocks.size()==b4.m_blocks.size()); assert(b4.search(b2.m_blocks[0],p)==0); //assert(b4.search(b2.m_blocks[9],p)==9); //assert(b4.search(b2.m_blocks[19],p)==19); //assert(b4.search(b2.m_blocks[29],p)==29); //cerr<<"!!!"<