Chris@33: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@37: /* Chris@37: This file is Copyright (c) 2012 Chris Cannam Chris@37: Chris@37: Permission is hereby granted, free of charge, to any person Chris@37: obtaining a copy of this software and associated documentation Chris@37: files (the "Software"), to deal in the Software without Chris@37: restriction, including without limitation the rights to use, copy, Chris@37: modify, merge, publish, distribute, sublicense, and/or sell copies Chris@37: of the Software, and to permit persons to whom the Software is Chris@37: furnished to do so, subject to the following conditions: Chris@37: Chris@37: The above copyright notice and this permission notice shall be Chris@37: included in all copies or substantial portions of the Software. Chris@37: Chris@37: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@37: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@37: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND Chris@37: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR Chris@37: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@37: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@37: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@37: */ Chris@33: Chris@38: #include "NoteHypothesis.h" Chris@33: Chris@38: std::ostream &operator<<(std::ostream &out, const NoteHypothesis::Estimate &n) Chris@33: { Chris@38: return out << "[" << n.freq << "@" << n.time << ":" << n.confidence << "]" << std::endl; Chris@33: } Chris@33: Chris@38: std::ostream &operator<<(std::ostream &out, const NoteHypothesis::Estimates &e) Chris@38: { Chris@38: out << "( "; Chris@38: for (int i = 0; i < (int)e.size(); ++i) out << e[i] << "; "; Chris@38: out << " )"; Chris@38: return out; Chris@38: } Chris@38: Chris@38: std::ostream &operator<<(std::ostream &out, const NoteHypothesis::Note &n) Chris@38: { Chris@38: return out << "[" << n.freq << "@" << n.time << ":" << n.duration << "]" << std::endl; Chris@38: } Chris@38: Chris@38: #define BOOST_TEST_DYN_LINK Chris@38: #define BOOST_TEST_MAIN Chris@38: Chris@38: #include Chris@38: Chris@38: using Vamp::RealTime; Chris@38: Chris@38: BOOST_AUTO_TEST_SUITE(TestNoteHypothesis) Chris@38: Chris@38: BOOST_AUTO_TEST_CASE(emptyAccept) Chris@38: { Chris@58: // An empty hypothesis should accept any estimate with a Chris@58: // non-negligible confidence, and enter provisional state Chris@38: NoteHypothesis h; Chris@58: NoteHypothesis::Estimate e; // default estimate has confidence 1 Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@38: BOOST_CHECK(h.accept(e)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: } Chris@58: Chris@58: BOOST_AUTO_TEST_CASE(noConfidence) Chris@58: { Chris@58: // A hypothesis should reject any estimate that has a negligible Chris@58: // confidence Chris@58: NoteHypothesis h; Chris@58: NoteHypothesis::Estimate e; Chris@58: e.confidence = 0; Chris@58: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@58: BOOST_CHECK(!h.accept(e)); Chris@58: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Rejected); Chris@58: } Chris@60: Chris@60: BOOST_AUTO_TEST_CASE(noConfidenceIgnore) Chris@60: { Chris@60: // But if we're already in process we don't go to rejected state, Chris@60: // we just ignore this hypothesis Chris@60: NoteHypothesis h; Chris@60: NoteHypothesis::Estimate e; Chris@60: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@60: BOOST_CHECK(h.accept(e)); Chris@60: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@60: e.confidence = 0; Chris@60: BOOST_CHECK(!h.accept(e)); Chris@60: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@60: } Chris@45: Chris@45: BOOST_AUTO_TEST_CASE(tooSlow) Chris@45: { Chris@45: // Having accepted a first estimate, a hypothesis should reject a Chris@45: // second (and enter rejected state) if there is too long a gap Chris@45: // between them for them to belong to a single note Chris@45: NoteHypothesis h; Chris@45: NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1); Chris@45: NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(50), 1); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@45: BOOST_CHECK(h.accept(e1)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@45: BOOST_CHECK(!h.accept(e2)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Rejected); Chris@45: } Chris@38: Chris@38: BOOST_AUTO_TEST_CASE(simpleSatisfy) Chris@38: { Chris@45: // A hypothesis should enter satisfied state after accepting three Chris@45: // consistent estimates, and then remain satisfied while accepting Chris@45: // further consistent estimates Chris@45: NoteHypothesis h; Chris@45: NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1); Chris@45: NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1); Chris@45: NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1); Chris@45: NoteHypothesis::Estimate e4(500, RealTime::fromMilliseconds(30), 1); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@45: BOOST_CHECK(h.accept(e1)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@45: BOOST_CHECK(h.accept(e2)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@45: BOOST_CHECK(h.accept(e3)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@45: BOOST_CHECK(h.accept(e4)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@45: } Chris@45: Chris@45: BOOST_AUTO_TEST_CASE(expiry) Chris@45: { Chris@45: // A hypothesis that has been satisfied, but that is subsequently Chris@45: // offered an estimate that follows too long a gap, should enter Chris@45: // expired state rather than rejected state (showing that it has a Chris@45: // valid note but that the note has apparently finished) Chris@38: NoteHypothesis h; Chris@38: NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1); Chris@38: NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1); Chris@38: NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1); Chris@38: NoteHypothesis::Estimate e4(500, RealTime::fromMilliseconds(30), 1); Chris@38: NoteHypothesis::Estimate e5(500, RealTime::fromMilliseconds(80), 1); Chris@38: NoteHypothesis::Estimate e6(500, RealTime::fromMilliseconds(90), 1); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@38: BOOST_CHECK(h.accept(e1)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e2)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e3)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: BOOST_CHECK(h.accept(e4)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: BOOST_CHECK(!h.accept(e5)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Expired); Chris@38: BOOST_CHECK(!h.accept(e6)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Expired); Chris@38: } Chris@38: Chris@45: BOOST_AUTO_TEST_CASE(strayReject1) Chris@38: { Chris@45: // A wildly different frequency occurring in the middle of a Chris@45: // provisionally accepted note should be ignored Chris@38: NoteHypothesis h; Chris@38: NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1); Chris@38: NoteHypothesis::Estimate e2(1000, RealTime::fromMilliseconds(10), 1); Chris@38: NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1); Chris@38: NoteHypothesis::Estimate e4(500, RealTime::fromMilliseconds(30), 1); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@38: BOOST_CHECK(h.accept(e1)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(!h.accept(e2)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e3)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e4)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: } Chris@45: Chris@45: BOOST_AUTO_TEST_CASE(strayReject2) Chris@38: { Chris@45: // A wildly different frequency occurring in the middle of a Chris@45: // satisfied note should be ignored Chris@38: NoteHypothesis h; Chris@38: NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 1); Chris@45: NoteHypothesis::Estimate e2(500, RealTime::fromMilliseconds(10), 1); Chris@45: NoteHypothesis::Estimate e3(500, RealTime::fromMilliseconds(20), 1); Chris@45: NoteHypothesis::Estimate e4(1000, RealTime::fromMilliseconds(30), 1); Chris@45: NoteHypothesis::Estimate e5(500, RealTime::fromMilliseconds(40), 1); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@38: BOOST_CHECK(h.accept(e1)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@45: BOOST_CHECK(h.accept(e2)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@45: BOOST_CHECK(h.accept(e3)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@45: BOOST_CHECK(!h.accept(e4)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@45: BOOST_CHECK(h.accept(e5)); Chris@45: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: } Chris@38: Chris@38: BOOST_AUTO_TEST_CASE(weakSatisfy) Chris@38: { Chris@45: // Behaviour with slightly varying frequencies should be as for Chris@45: // that with fixed frequency Chris@38: NoteHypothesis h; Chris@38: NoteHypothesis::Estimate e1(500, RealTime::fromMilliseconds(0), 0.5); Chris@38: NoteHypothesis::Estimate e2(502, RealTime::fromMilliseconds(10), 0.5); Chris@38: NoteHypothesis::Estimate e3(504, RealTime::fromMilliseconds(20), 0.5); Chris@38: NoteHypothesis::Estimate e4(506, RealTime::fromMilliseconds(30), 0.5); Chris@38: NoteHypothesis::Estimate e5(508, RealTime::fromMilliseconds(40), 0.5); Chris@38: NoteHypothesis::Estimate e6(510, RealTime::fromMilliseconds(90), 0.5); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@38: BOOST_CHECK(h.accept(e1)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e2)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e3)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e4)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e5)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: BOOST_CHECK(!h.accept(e6)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Expired); Chris@38: } Chris@38: Chris@38: BOOST_AUTO_TEST_CASE(frequencyRange) Chris@38: { Chris@45: // But there's a limit: outside a certain range we should reject Chris@45: //!!! (but what is this range? is it part of the spec?) Chris@38: NoteHypothesis h; Chris@38: NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1); Chris@38: NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1); Chris@38: NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1); Chris@38: NoteHypothesis::Estimate e4(470, RealTime::fromMilliseconds(30), 1); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@38: BOOST_CHECK(h.accept(e1)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e2)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK(h.accept(e3)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: BOOST_CHECK(!h.accept(e4)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: } Chris@38: Chris@38: BOOST_AUTO_TEST_CASE(acceptedEstimates) Chris@38: { Chris@45: // Check that getAcceptedEstimates() returns the right result Chris@38: NoteHypothesis h; Chris@38: NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1); Chris@38: NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1); Chris@38: NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1); Chris@38: NoteHypothesis::Estimate e4(470, RealTime::fromMilliseconds(30), 1); Chris@38: NoteHypothesis::Estimate e5(444, RealTime::fromMilliseconds(90), 1); Chris@38: NoteHypothesis::Estimates es; Chris@38: es.push_back(e1); Chris@38: es.push_back(e2); Chris@38: es.push_back(e3); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::New); Chris@38: BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), NoteHypothesis::Estimates()); Chris@38: BOOST_CHECK(h.accept(e1)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), NoteHypothesis::Estimates()); Chris@38: BOOST_CHECK(h.accept(e2)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Provisional); Chris@38: BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), NoteHypothesis::Estimates()); Chris@38: BOOST_CHECK(h.accept(e3)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), es); Chris@38: BOOST_CHECK(!h.accept(e4)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Satisfied); Chris@38: BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), es); Chris@38: BOOST_CHECK(!h.accept(e5)); Chris@38: BOOST_CHECK_EQUAL(h.getState(), NoteHypothesis::Expired); Chris@38: BOOST_CHECK_EQUAL(h.getAcceptedEstimates(), es); Chris@38: } Chris@38: Chris@38: BOOST_AUTO_TEST_CASE(meanFrequency) Chris@38: { Chris@45: // Check that the mean frequency is the mean of the frequencies Chris@38: NoteHypothesis h; Chris@38: NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(0), 1); Chris@38: NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(10), 1); Chris@38: NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(20), 1); Chris@38: BOOST_CHECK(h.accept(e1)); Chris@38: BOOST_CHECK(h.accept(e2)); Chris@38: BOOST_CHECK(h.accept(e3)); Chris@38: BOOST_CHECK_EQUAL(h.getMeanFrequency(), 444.0); Chris@38: } Chris@38: Chris@38: BOOST_AUTO_TEST_CASE(averagedNote) Chris@38: { Chris@45: // Check that getAveragedNote returns something sane Chris@38: NoteHypothesis h; Chris@38: NoteHypothesis::Estimate e1(440, RealTime::fromMilliseconds(10), 1); Chris@38: NoteHypothesis::Estimate e2(448, RealTime::fromMilliseconds(20), 1); Chris@38: NoteHypothesis::Estimate e3(444, RealTime::fromMilliseconds(30), 1); Chris@38: BOOST_CHECK(h.accept(e1)); Chris@38: BOOST_CHECK(h.accept(e2)); Chris@38: BOOST_CHECK(h.accept(e3)); Chris@38: BOOST_CHECK_EQUAL(h.getAveragedNote(), NoteHypothesis::Note Chris@38: (444, Chris@38: RealTime::fromMilliseconds(10), Chris@38: RealTime::fromMilliseconds(20))); Chris@38: } Chris@38: Chris@45: //!!! Not yet tested: Confidence scores Chris@45: Chris@38: BOOST_AUTO_TEST_SUITE_END() Chris@38: