/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * samplesorter.cc * * Mon Nov 30 07:45:58 CET 2009 * Copyright 2009 Bent Bisballe Nyeng * deva@aasimon.org ****************************************************************************/ /* * This file is part of DrumGizmo. * * DrumGizmo 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. * * DrumGizmo 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 DrumGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "samplesorter.h" #include "project.h" #include #include #include #include #include #include #ifndef MAXFLOAT #define MAXFLOAT (3.40282347e+38F) #endif SampleSorter::SampleSorter(Ranges& s, Ranges& p, Instrument& instrument) : ranges(s) , ranges_preview(p) , instrument(instrument) { setMouseTracking(true); setFocusPolicy(Qt::ClickFocus); // Enable keyboard event on click data = NULL; size = 0; attlen = 666; // Magical constants needs biblical proportions... sel_moving = SEL_NONE; setSpreadFactor(1000); } void SampleSorter::setShowPreview(bool s) { show_preview = s; update(); } void SampleSorter::setWavData(const float* data, size_t size) { this->data = data; this->size = size; relayout(); } void SampleSorter::setPositionData(const float* positionData1, size_t positionSize1, const float* positionData2, size_t positionSize2) { this->positionData1 = positionData1; this->positionSize1 = positionSize1; this->positionData2 = positionData2; this->positionSize2 = positionSize2; relayout(); } int SampleSorter::attackLength() { return attlen; } void SampleSorter::setSpreadFactor(int s) { spread = (double)s / 1000.0; spread *= spread; relayout(); } void SampleSorter::setAttackLength(int len) { attlen = len; relayout(); } void SampleSorter::addRange(sel_id_t id) { auto range = ranges.get(id); double energy = 0.0; for(size_t idx = range.from; (idx < (size_t)range.from + (size_t)attackLength()) && (idx < (size_t)range.to) && (idx < size); idx++) { energy += data[idx] * data[idx]; } range.energy = pow(energy, spread); // constexpr int window_size{10}; // TODO: Specify in ms // // std::vector corr; // corr.resize(window_size); // // double position = 0.0; // // std::cout << "\n\nRange from: " << range.from << "\n"; // // // float max_val1{0}; // for(int idx = range.from - window_size / 2; idx < range.from + window_size / 2; ++idx) // { // if(std::abs(positionData1[idx]) > max_val1) // { // max_val1 = std::abs(positionData1[idx]); // } // } // float norm1 = 1.0f / max_val1; // // float max_val2{0}; // for(int idx = range.from - window_size / 2; idx < range.from + window_size / 2; ++idx) // { // if(std::abs(positionData2[idx]) > max_val2) // { // max_val2 = std::abs(positionData2[idx]); // } // } // float norm2 = 1.0f / max_val2; // // // //constexpr auto lpf_beta = 0.025f; // 0<ß<1 // constexpr auto lpf_beta = 1.0f; // 0<ß<1 // // auto filter1 = // [lpf_beta](auto in) // { // static auto v{0.0f}; // v = v - (lpf_beta * (v - in)); // return v; // }; // auto filter2 = // [lpf_beta](auto in) // { // static auto v{0.0f}; // v = v - (lpf_beta * (v - in)); // return v; // }; // // //#if 0 // Cross-correlation // // if(positionData1 && positionSize1 && // positionData2 && positionSize2) // { // // From: http://eceweb1.rutgers.edu/~gajic/solmanual/slides/chapter9_CORR.pdf // for(int k = 0; k < window_size; ++k) // { // corr[k] = 0.0f; // for(int m = range.from - window_size / 2; m < range.from + window_size / 2; ++m) // { // corr[k] += // (filter1(positionData1[m] * norm1)) * // (filter2(positionData2[m - k] * norm2)); // } // } // } // // float max_corr{0.0f}; // for(int idx = 0; idx < corr.size(); ++idx) // { // std::cout << idx << ": " << corr[idx] << "\n"; // if(corr[idx] > max_corr) // { // max_corr = corr[idx]; // position = idx; // } // } // // std::cout << "Maximum found: " << max_corr << " at idx " << position << "\n"; // //#else // Rising edge detection // // float threshold = instrument.getPositionThreshold();//{0.003f}; // int pos_window_size{100}; // +/- 50 from detected, // // ~40 samples between left/right impirically detected when at edge, same for main mic to each of // // the piezos // norm1 = norm2 = 1.0f; // int pos1{0}; // for(int idx = range.from - pos_window_size / 2; // idx < range.from + pos_window_size / 2; // ++idx) // { // if(std::abs(filter1(positionData1[idx])) * norm1 > threshold) // { // pos1 = idx; // range.pos1 = pos1; // break; // } // } // // int pos2{0}; // for(int idx = range.from - pos_window_size / 2; // idx < range.from + pos_window_size / 2; // ++idx) // { // if(std::abs(filter2(positionData2[idx])) * norm2 > threshold) // { // pos2 = idx; // range.pos2 = pos1; // break; // } // } // // position = std::abs(pos1 - pos2); //#endif // // std::cout << "[" << pos1 << " <-> " << pos2 << "]\n"; // // constexpr double speed_of_sound{3431000}; // mm/s // constexpr double samplerate{48000}; // 48kHz - TODO get from audio // range.position = position * speed_of_sound / samplerate / 2.0; // ranges.update(id, range); relayout(); } void SampleSorter::addRangePreview(sel_id_t id) { Range s = ranges_preview.get(id); double energy = 0.0; for(size_t idx = s.from; (idx < (size_t)s.from + (size_t)attackLength()) && (idx < (size_t)s.to) && (idx < size); idx++) { energy += data[idx] * data[idx]; } s.energy = pow(energy, spread); ranges_preview.update(id, s); relayout(); } void SampleSorter::relayout() { min = MAXFLOAT; max = 0.0; { QVector ids = ranges.ids(); QVector::iterator i = ids.begin(); while(i != ids.end()) { Range sel = ranges.get(*i); if(sel.energy < min) { min = sel.energy; } if(sel.energy > max) { max = sel.energy; } i++; } } if(show_preview) { QVector ids = ranges_preview.ids(); QVector::iterator i = ids.begin(); while(i != ids.end()) { Range sel = ranges_preview.get(*i); if(sel.energy < min) { min = sel.energy; } if(sel.energy > max) { max = sel.energy; } i++; } } update(); } #define MAP(p) (height()-(int)(p*((float)height()/(float)width()))) #define unmapX(x) ((double)x/(double)(width()-1)*(1.0/0.9)) #define unmapY(x) x #define mapX(x) (((double)x)*(width()-1)) #define mapY(x) x #define C_RADIUS 2 static void drawCircle(QPainter& p, int x, int y) { p.drawEllipse(x - C_RADIUS, y - C_RADIUS, 2 * C_RADIUS, 2 * C_RADIUS); } void SampleSorter::paintEvent(QPaintEvent* event) { QPainter painter(this); QColor colBg = QColor(180, 200, 180, 160); QColor colFg = QColor(160, 180, 160, 160); QColor colPt = QColor(255, 100, 100, 160); QColor colPtPreview = QColor(0, 0, 255, 160); QColor colPtSel = QColor(255, 255, 100, 160); painter.setPen(colBg); painter.setBrush(colBg); painter.drawRect(event->rect()); painter.setPen(colFg); painter.drawLine(0,height(),width(),0); { QVector ids = ranges.ids(); QVector::iterator i = ids.begin(); while(i != ids.end()) { Range sel = ranges.get(*i); if(*i == ranges.active()) { painter.setPen(colPtSel); } else { painter.setPen(colPt); } float x = (sel.energy / max); x = sqrt(x); x *= (float)width() * 0.9; drawCircle(painter, x, MAP(x)); i++; } } if(show_preview) { QVector ids = ranges_preview.ids(); QVector::iterator i = ids.begin(); while(i != ids.end()) { Range sel = ranges_preview.get(*i); painter.setPen(colPtPreview); float x = (sel.energy / max); x = sqrt(x); x *= (float)width() * 0.9; drawCircle(painter, x, MAP(x)); i++; } } } sel_id_t SampleSorter::getRangeByCoordinate(int px, int py) { // Hit radius is slithly larger than the circles themselves. int hit_r = C_RADIUS + 1; QVector ids = ranges.ids(); QVector::iterator i = ids.begin(); while(i != ids.end()) { Range sel = ranges.get(*i); float x = (sel.energy/max); x = sqrt(x); x *= (float)width() * 0.9; if(px < (x + hit_r) && px > (x - hit_r) && py < (MAP(x) + hit_r) && py > (MAP(x) - hit_r)) { return *i; } i++; } return SEL_NONE; } void SampleSorter::mouseMoveEvent(QMouseEvent* event) { if(sel_moving != SEL_NONE) { Range sel = ranges.get(sel_moving); if(sel_moving != SEL_NONE) { double power = unmapX(event->x()); power *= power; power *= max; sel.energy = power; ranges.update(sel_moving, sel); } update(); return; } else { sel_id_t psel = getRangeByCoordinate(event->x(), event->y()); if(psel != SEL_NONE) { setCursor(Qt::OpenHandCursor); } else { setCursor(Qt::ArrowCursor); } } } void SampleSorter::mousePressEvent(QMouseEvent* event) { if(event->button() == Qt::LeftButton) { sel_id_t psel = getRangeByCoordinate(event->x(), event->y()); sel_moving = psel; ranges.setActive(psel); if(psel != SEL_NONE) { setCursor(Qt::ClosedHandCursor); } } } void SampleSorter::mouseReleaseEvent(QMouseEvent* event) { if(event->button() == Qt::LeftButton) { sel_moving = SEL_NONE; sel_id_t psel = getRangeByCoordinate(event->x(), event->y()); if(psel != SEL_NONE) { setCursor(Qt::OpenHandCursor); } else { setCursor(Qt::ArrowCursor); } } } void SampleSorter::keyReleaseEvent(QKeyEvent* event) { if(ranges.active() != SEL_NONE && event->key() == Qt::Key_Delete) { ranges.remove(ranges.active()); } }