Chris@767: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@767: Chris@767: /* Chris@767: Sonic Visualiser Chris@767: An audio file viewer and annotation editor. Chris@767: Centre for Digital Music, Queen Mary, University of London. Chris@767: Chris@767: This program is free software; you can redistribute it and/or Chris@767: modify it under the terms of the GNU General Public License as Chris@767: published by the Free Software Foundation; either version 2 of the Chris@767: License, or (at your option) any later version. See the file Chris@767: COPYING included with this distribution for more information. Chris@767: */ Chris@767: Chris@767: #include "LinearAligner.h" Chris@767: Chris@767: #include "system/System.h" Chris@767: Chris@767: #include "data/model/Path.h" Chris@767: #include "data/model/AlignmentModel.h" Chris@767: Chris@767: #include "framework/Document.h" Chris@767: Chris@770: #include "svcore/data/model/DenseTimeValueModel.h" Chris@770: Chris@770: #include Chris@770: Chris@767: LinearAligner::LinearAligner(Document *doc, Chris@767: ModelId reference, Chris@767: ModelId toAlign, Chris@767: bool trimmed) : Chris@767: m_document(doc), Chris@767: m_reference(reference), Chris@767: m_toAlign(toAlign), Chris@767: m_trimmed(trimmed) Chris@767: { Chris@767: } Chris@767: Chris@767: LinearAligner::~LinearAligner() Chris@767: { Chris@767: } Chris@767: Chris@767: void Chris@767: LinearAligner::begin() Chris@767: { Chris@767: bool ready = false; Chris@767: while (!ready) { Chris@767: { // scope so as to release input shared_ptr before sleeping Chris@767: auto reference = ModelById::get(m_reference); Chris@767: auto toAlign = ModelById::get(m_toAlign); Chris@767: if (!reference || !reference->isOK() || Chris@767: !toAlign || !toAlign->isOK()) { Chris@767: return; Chris@767: } Chris@770: ready = (reference->isReady() && toAlign->isReady()); Chris@767: } Chris@767: if (!ready) { Chris@767: SVDEBUG << "LinearAligner: Waiting for models..." << endl; Chris@770: QApplication::processEvents(QEventLoop::ExcludeUserInputEvents | Chris@770: QEventLoop::ExcludeSocketNotifiers, Chris@770: 500); Chris@767: } Chris@767: } Chris@767: Chris@767: auto reference = ModelById::get(m_reference); Chris@767: auto toAlign = ModelById::get(m_toAlign); Chris@767: Chris@767: if (!reference || !reference->isOK() || Chris@767: !toAlign || !toAlign->isOK()) { Chris@767: return; Chris@767: } Chris@770: Chris@770: sv_frame_t s0, e0, s1, e1; Chris@770: s0 = reference->getStartFrame(); Chris@770: e0 = reference->getEndFrame(); Chris@770: s1 = toAlign->getStartFrame(); Chris@770: e1 = toAlign->getEndFrame(); Chris@770: Chris@770: if (m_trimmed) { Chris@770: getTrimmedExtents(m_reference, s0, e0); Chris@770: getTrimmedExtents(m_toAlign, s1, e1); Chris@770: SVCERR << "Trimmed extents: reference: " << s0 << " to " << e0 Chris@770: << ", toAlign: " << s1 << " to " << e1 << endl; Chris@770: } Chris@770: Chris@767: sv_frame_t d0 = e0 - s0, d1 = e1 - s1; Chris@767: Chris@767: if (d1 == 0) { Chris@767: return; Chris@767: } Chris@767: Chris@767: double ratio = double(d0) / double(d1); Chris@770: int resolution = 1024; Chris@767: Chris@767: Path path(reference->getSampleRate(), resolution); Chris@767: Chris@767: for (sv_frame_t f = s1; f < e1; f += resolution) { Chris@770: sv_frame_t target = s0 + sv_frame_t(double(f - s1) * ratio); Chris@767: path.add(PathPoint(f, target)); Chris@767: } Chris@767: Chris@767: auto alignment = std::make_shared(m_reference, Chris@767: m_toAlign, Chris@767: ModelId()); Chris@767: Chris@767: auto alignmentModelId = ModelById::add(alignment); Chris@767: Chris@767: alignment->setPath(path); Chris@773: alignment->setCompletion(100); Chris@767: toAlign->setAlignment(alignmentModelId); Chris@767: m_document->addNonDerivedModel(alignmentModelId); Chris@770: Chris@770: emit complete(alignmentModelId); Chris@767: } Chris@767: Chris@770: bool Chris@770: LinearAligner::getTrimmedExtents(ModelId modelId, Chris@770: sv_frame_t &start, Chris@770: sv_frame_t &end) Chris@770: { Chris@770: auto model = ModelById::getAs(modelId); Chris@770: if (!model) return false; Chris@770: Chris@770: sv_frame_t chunksize = 1024; Chris@770: double threshold = 1e-2; Chris@770: Chris@770: auto rms = [](const floatvec_t &samples) { Chris@770: double rms = 0.0; Chris@770: for (auto s: samples) { Chris@770: rms += s * s; Chris@770: } Chris@770: rms /= double(samples.size()); Chris@770: rms = sqrt(rms); Chris@770: return rms; Chris@770: }; Chris@770: Chris@770: while (start < end) { Chris@770: floatvec_t samples = model->getData(-1, start, chunksize); Chris@770: if (samples.empty()) { Chris@770: return false; // no non-silent content found Chris@770: } Chris@770: if (rms(samples) > threshold) { Chris@770: for (auto s: samples) { Chris@770: if (fabsf(s) > threshold) { Chris@770: break; Chris@770: } Chris@770: ++start; Chris@770: } Chris@770: break; Chris@770: } Chris@770: start += chunksize; Chris@770: } Chris@770: Chris@770: if (start >= end) { Chris@770: return false; Chris@770: } Chris@770: Chris@770: while (end > start) { Chris@770: sv_frame_t probe = end - chunksize; Chris@770: if (probe < 0) probe = 0; Chris@770: floatvec_t samples = model->getData(-1, probe, chunksize); Chris@770: if (samples.empty()) { Chris@770: break; Chris@770: } Chris@770: if (rms(samples) > threshold) { Chris@770: break; Chris@770: } Chris@770: end = probe; Chris@770: } Chris@770: Chris@770: return (end > start); Chris@770: }