link-Link-3.0.2/ 0000755 0001750 0001750 00000000000 13303226525 013577 5 ustar zmoelnig zmoelnig link-Link-3.0.2/.appveyor.yml 0000644 0001750 0001750 00000004653 13303226525 016255 0 ustar zmoelnig zmoelnig clone_depth: 50
branches:
only:
- master
# Also includes VS2013 pre-installed
image:
- Visual Studio 2015
- Visual Studio 2017
environment:
matrix:
# Visual Studio 2013, 32-bit Release, Asio driver
- AUDIO_DRIVER: Asio
CONFIGURATION: Release
GENERATOR: Visual Studio 12 2013
QT_VERSION: 5.6
QT: msvc2013
# Visual Studio 2013, 64-bit Release, Asio driver
- AUDIO_DRIVER: Asio
CONFIGURATION: Release
GENERATOR: Visual Studio 12 2013 Win64
QT_VERSION: 5.10
QT: msvc2013_64
# Visual Studio 2013, 64-bit Debug, Wasapi driver
- AUDIO_DRIVER: Wasapi
CONFIGURATION: Debug
GENERATOR: Visual Studio 12 2013 Win64
QT_VERSION: 5.10
QT: msvc2013_64
# Visual Studio 2015, 32-bit Release, Asio driver
- AUDIO_DRIVER: Asio
CONFIGURATION: Release
GENERATOR: Visual Studio 14 2015
QT_VERSION: 5.10
QT: msvc2015
# Visual Studio 2015, 64-bit Debug, Asio driver
- AUDIO_DRIVER: Asio
CONFIGURATION: Debug
GENERATOR: Visual Studio 14 2015 Win64
QT_VERSION: 5.10
QT: msvc2015_64
# Visual Studio 2015, 64-bit Release, Asio driver
- AUDIO_DRIVER: Asio
CONFIGURATION: Release
GENERATOR: Visual Studio 14 2015 Win64
QT_VERSION: 5.10
QT: msvc2015_64
# Visual Studio 2015, 64-bit Release, Wasapi driver
- AUDIO_DRIVER: Wasapi
CONFIGURATION: Release
GENERATOR: Visual Studio 14 2015 Win64
QT_VERSION: 5.10
QT: msvc2015_64
# Visual Studio 2017, 64-bit Release, Asio driver
- AUDIO_DRIVER: Asio
CONFIGURATION: Release
GENERATOR: Visual Studio 15 2017 Win64
QT_VERSION: 5.10
QT: msvc2017_64
matrix:
exclude:
- image: Visual Studio 2015
GENERATOR: Visual Studio 15 2017 Win64
- image: Visual Studio 2017
GENERATOR: Visual Studio 12 2013
- image: Visual Studio 2017
GENERATOR: Visual Studio 12 2013 Win64
- image: Visual Studio 2017
GENERATOR: Visual Studio 14 2015
- image: Visual Studio 2017
GENERATOR: Visual Studio 14 2015 Win64
install:
- git submodule update --init --recursive
- set PATH=%PATH%;C:\Qt\%QT_VERSION%\%QT%\bin
build_script:
- python ci/configure.py -q -a %AUDIO_DRIVER% -g "%GENERATOR%"
- python ci/build.py --configuration %CONFIGURATION%
test_script:
- python ci/run-tests.py --target LinkCoreTest
- python ci/run-tests.py --target LinkDiscoveryTest
link-Link-3.0.2/src/ 0000755 0001750 0001750 00000000000 13303226525 014366 5 ustar zmoelnig zmoelnig link-Link-3.0.2/src/ableton/ 0000755 0001750 0001750 00000000000 13303226525 016012 5 ustar zmoelnig zmoelnig link-Link-3.0.2/src/ableton/test/ 0000755 0001750 0001750 00000000000 13303226525 016771 5 ustar zmoelnig zmoelnig link-Link-3.0.2/src/ableton/test/serial_io/ 0000755 0001750 0001750 00000000000 13303226525 020737 5 ustar zmoelnig zmoelnig link-Link-3.0.2/src/ableton/test/serial_io/SchedulerTree.cpp 0000644 0001750 0001750 00000005633 13303226525 024210 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
namespace ableton
{
namespace test
{
namespace serial_io
{
void SchedulerTree::run()
{
while (handlePending())
{
}
}
std::shared_ptr SchedulerTree::makeChild()
{
auto newChild = std::make_shared();
mChildren.push_back(newChild);
return newChild;
}
void SchedulerTree::cancelTimer(const TimerId timerId)
{
const auto it = std::find_if(
begin(mTimers), end(mTimers), [timerId](const TimerMap::value_type& timer) {
return timer.first.second == timerId;
});
if (it != end(mTimers))
{
auto handler = std::move(it->second);
mPendingHandlers.push_back([handler]() {
handler(1); // truthy indicates error
});
mTimers.erase(it);
}
}
SchedulerTree::TimePoint SchedulerTree::nextTimerExpiration()
{
auto nextTimePoint = TimePoint::max();
withChildren([&nextTimePoint](SchedulerTree& child) {
nextTimePoint = (std::min)(nextTimePoint, child.nextOwnTimerExpiration());
});
return (std::min)(nextTimePoint, nextOwnTimerExpiration());
}
void SchedulerTree::triggerTimersUntil(const TimePoint t)
{
using namespace std;
withChildren([t](SchedulerTree& child) { child.triggerTimersUntil(t); });
const auto it = mTimers.upper_bound(make_pair(t, numeric_limits::max()));
for_each(begin(mTimers), it, [this](const TimerMap::value_type& timer) {
mPendingHandlers.push_back([timer]() {
timer.second(0); // 0 indicates no error
});
});
mTimers.erase(begin(mTimers), it);
}
bool SchedulerTree::handlePending()
{
bool subtreeWorked = false;
withChildren(
[&subtreeWorked](SchedulerTree& child) { subtreeWorked |= child.handlePending(); });
if (mPendingHandlers.empty())
{
return subtreeWorked;
}
else
{
mPendingHandlers.front()();
mPendingHandlers.pop_front();
return true;
}
}
SchedulerTree::TimePoint SchedulerTree::nextOwnTimerExpiration()
{
return mTimers.empty() ? TimePoint::max() : begin(mTimers)->first.first;
}
} // namespace serial_io
} // namespace test
} // namespace ableton
link-Link-3.0.2/src/ableton/test/catch/ 0000755 0001750 0001750 00000000000 13303226525 020053 5 ustar zmoelnig zmoelnig link-Link-3.0.2/src/ableton/test/catch/CatchMain.cpp 0000644 0001750 0001750 00000001632 13303226525 022410 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#define CATCH_CONFIG_MAIN
#include
link-Link-3.0.2/src/ableton/link/ 0000755 0001750 0001750 00000000000 13303226525 016747 5 ustar zmoelnig zmoelnig link-Link-3.0.2/src/ableton/link/tst_Tempo.cpp 0000644 0001750 0001750 00000005713 13303226525 021437 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
namespace ableton
{
namespace link
{
TEST_CASE("Tempo | ConstructFromBpm", "[Tempo]")
{
const auto tempo = Tempo{120.};
CHECK(120. == Approx(tempo.bpm()));
CHECK(std::chrono::microseconds{500000} == tempo.microsPerBeat());
}
TEST_CASE("Tempo | ConstructFromMicros", "[Tempo]")
{
const auto tempo = Tempo{std::chrono::microseconds{500000}};
CHECK(120. == Approx(tempo.bpm()));
CHECK(std::chrono::microseconds{500000} == tempo.microsPerBeat());
}
TEST_CASE("Tempo | MicrosToBeats", "[Tempo]")
{
const auto tempo = Tempo{120.};
CHECK(Beats{2.} == tempo.microsToBeats(std::chrono::microseconds{1000000}));
}
TEST_CASE("Tempo | BeatsToMicros", "[Tempo]")
{
const auto tempo = Tempo{120.};
CHECK(std::chrono::microseconds{1000000} == tempo.beatsToMicros(Beats{2.}));
}
TEST_CASE("Tempo | ComparisonLT", "[Tempo]")
{
const auto tempo1 = Tempo{100.};
const auto tempo2 = Tempo{200.};
CHECK(tempo1 < tempo2);
}
TEST_CASE("Tempo | ComparisonGT", "[Tempo]")
{
const auto tempo1 = Tempo{100.};
const auto tempo2 = Tempo{200.};
CHECK(tempo2 > tempo1);
}
TEST_CASE("Tempo | ComparisonLE", "[Tempo]")
{
const auto tempo1 = Tempo{100.};
const auto tempo2 = Tempo{200.};
CHECK(tempo1 <= tempo2);
CHECK(tempo2 <= tempo2);
}
TEST_CASE("Tempo | ComparisonGE", "[Tempo]")
{
const auto tempo1 = Tempo{100.};
const auto tempo2 = Tempo{200.};
CHECK(tempo2 >= tempo1);
CHECK(tempo2 >= tempo2);
}
TEST_CASE("Tempo | ComparisonEQ", "[Tempo]")
{
const auto tempo1 = Tempo{100.};
const auto tempo2 = Tempo{100.};
CHECK(tempo1 == tempo2);
}
TEST_CASE("Tempo | ComparisonNE", "[Tempo]")
{
const auto tempo1 = Tempo{100.};
const auto tempo2 = Tempo{200.};
CHECK(tempo1 != tempo2);
}
TEST_CASE("Tempo | RoundtripByteStreamEncoding", "[Tempo]")
{
const auto tempo = Tempo{120.};
std::vector bytes(sizeInByteStream(tempo));
const auto end = toNetworkByteStream(tempo, begin(bytes));
const auto result = Tempo::fromNetworkByteStream(begin(bytes), end);
CHECK(tempo == result.first);
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_ClientSessionTimelines.cpp 0000644 0001750 0001750 00000013602 13303226525 025003 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
namespace ableton
{
namespace link
{
namespace
{
using std::chrono::microseconds;
// Make the constants non-zero to make sure we're not hiding bugs
const auto b0 = Beats{-1.};
const auto t0 = microseconds{-1};
const auto xform = GhostXForm{1., microseconds{-1000000}};
} // namespace
TEST_CASE("session->client | UpdatesTempo", "[ClientSessionTimelines]")
{
const auto curClient = Timeline{Tempo{60.}, b0, t0};
const auto session = Timeline{Tempo{90.}, b0, xform.hostToGhost(t0)};
const auto newClient = updateClientTimelineFromSession(curClient, session, t0, xform);
CHECK(newClient.tempo == Tempo{90.});
}
TEST_CASE("session->client | PreservesClientBeatMagnitude", "[ClientSessionTimelines]")
{
const auto curClient = Timeline{Tempo{60.}, b0, t0};
const auto session = Timeline{Tempo{120.}, Beats{12.32}, microseconds{-123456789}};
// Transition to the session timeline at t1
const auto t1 = t0 + microseconds{5000000};
const auto b1 = curClient.toBeats(t1);
const auto newClient = updateClientTimelineFromSession(curClient, session, t1, xform);
// The new client timeline should have the same magnitude as the old
// one at the transition point
CHECK(newClient.toBeats(t1) == b1);
// At t1 + dt, the new tempo should be in effect
CHECK(newClient.toBeats(t1 + microseconds{1000000}) == b1 + Beats{2.});
}
TEST_CASE("session->client | AtDifferentTimesGivesSameResult", "[ClientSessionTimelines]")
{
const auto curClient = Timeline{Tempo{60.}, b0, t0};
const auto session = Timeline{Tempo{120.}, Beats{-11.}, microseconds{-12356789}};
const auto t1 = t0 + microseconds{5000000};
const auto t2 = t1 + microseconds{6849353};
// Once a given session timeline has been converted to a client
// timeline, converting again from the same session at different
// times should not change the result because the result is colinear
// and the origin should always be anchored at the session's beat 0.
const auto updated1 = updateClientTimelineFromSession(curClient, session, t1, xform);
const auto updated2 = updateClientTimelineFromSession(updated1, session, t2, xform);
CHECK(updated1 == updated2);
}
TEST_CASE("session->client | EncodesSessionOrigin", "[ClientSessionTimelines]")
{
const auto curClient = Timeline{Tempo{60.}, b0, t0};
const auto session = Timeline{Tempo{21.3}, Beats{-421.3}, microseconds{15003240}};
const auto newClient =
updateClientTimelineFromSession(curClient, session, microseconds{-1345298}, xform);
// The new client timeline's origin should be at beat 0 on the
// session timeline. This is how we encode the session origin in the
// client timeline.
CHECK(xform.hostToGhost(newClient.timeOrigin) == session.fromBeats(Beats{0.}));
}
TEST_CASE("client->session | ShiftedForward", "[ClientSessionTimeline]")
{
const auto session = Timeline{Tempo{60.}, b0, t0};
auto client = Timeline{Tempo{60.}, Beats{111.}, xform.ghostToHost(t0)};
client = updateClientTimelineFromSession(client, session, t0, xform);
// Shift the phase one beat forward
client = shiftClientTimeline(client, Beats{1.});
const auto newSession =
updateSessionTimelineFromClient(session, client, xform.ghostToHost(t0), xform);
CHECK(newSession.toBeats(t0) == b0 + Beats{1.});
}
TEST_CASE("client->session | ShiftedBackward", "[ClientSessionTimeline]")
{
const auto session = Timeline{Tempo{60.}, b0, t0};
auto client = Timeline{Tempo{60.}, Beats{983.}, xform.ghostToHost(t0)};
client = updateClientTimelineFromSession(client, session, t0, xform);
// Shift the phase one beat backward
client = shiftClientTimeline(client, Beats{-1.});
const auto newSession =
updateSessionTimelineFromClient(session, client, xform.ghostToHost(t0), xform);
CHECK(newSession.toBeats(t0) == b0 - Beats{1.});
}
TEST_CASE("session->client->session | Roundtrip", "[ClientSessionTimeline]")
{
const auto session = Timeline{Tempo{60.}, b0, t0};
// Initial client timeline values shouldn't matter
auto client = Timeline{Tempo{213.5}, Beats{432.}, microseconds{5143503}};
client = updateClientTimelineFromSession(client, session, t0, xform);
auto newSession =
updateSessionTimelineFromClient(session, client, microseconds{42905944}, xform);
CHECK(session == newSession);
// Now verify that modifying the client timeline and then routing it
// through a session update results in the same client timeline.
const auto updateTime = microseconds{-35023900};
newSession = updateSessionTimelineFromClient(newSession, client, updateTime, xform);
const auto newClient =
updateClientTimelineFromSession(client, newSession, updateTime, xform);
CHECK(client == newClient);
}
TEST_CASE("shiftClientTimelineOrigin | Shift", "[ClientSessionTimeline]")
{
const auto shift = Beats{1.1};
const auto timeline = Timeline{Tempo{60.}, b0, t0};
CHECK(shiftClientTimeline(timeline, shift).toBeats(t0) == timeline.toBeats(t0) + shift);
CHECK(
shiftClientTimeline(timeline, -shift).toBeats(t0) == timeline.toBeats(t0) - shift);
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_PingResponder.cpp 0000644 0001750 0001750 00000010423 13303226525 023124 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ableton
{
namespace link
{
namespace
{
struct RpFixture
{
RpFixture()
: mAddress(asio::ip::address_v4::from_string("127.0.0.1"))
, mResponder(mAddress,
NodeId::random(),
GhostXForm{1.0, std::chrono::microseconds{0}},
util::injectVal(util::test::IoService{}),
MockClock{},
util::injectVal(util::NullLog{}))
{
}
discovery::test::Socket responderSocket()
{
return mResponder.socket();
}
std::size_t numSentMessages()
{
return responderSocket().sentMessages.size();
}
struct MockClock
{
std::chrono::microseconds micros() const
{
return std::chrono::microseconds{4};
}
};
asio::ip::address_v4 mAddress = asio::ip::address_v4::from_string("127.0.0.1");
PingResponder
mResponder;
};
} // anonymous namespace
TEST_CASE("PingResponder | ReplyToPing", "[PingResponder]")
{
using std::chrono::microseconds;
RpFixture fixture;
// Construct and send Ping Message. Check if Responder sent back a Message
const auto payload =
discovery::makePayload(HostTime{microseconds(2)}, PrevGHostTime{microseconds(1)});
v1::MessageBuffer buffer;
const auto msgBegin = std::begin(buffer);
const auto msgEnd = v1::pingMessage(payload, msgBegin);
const auto endpoint = asio::ip::udp::endpoint(fixture.mAddress, 8888);
fixture.responderSocket().incomingMessage(endpoint, msgBegin, msgEnd);
CHECK(1 == fixture.numSentMessages());
// Check Responder's message
const auto messageBuffer = fixture.responderSocket().sentMessages[0].first;
const auto result = v1::parseMessageHeader(begin(messageBuffer), end(messageBuffer));
const auto& hdr = result.first;
std::chrono::microseconds ghostTime{0};
std::chrono::microseconds prevGHostTime{0};
std::chrono::microseconds hostTime{0};
discovery::parsePayload(result.second,
std::end(messageBuffer),
[&ghostTime](GHostTime gt) { ghostTime = std::move(gt.time); },
[&prevGHostTime](PrevGHostTime gt) { prevGHostTime = std::move(gt.time); },
[&hostTime](HostTime ht) { hostTime = std::move(ht.time); });
CHECK(v1::kPong == hdr.messageType);
CHECK(std::chrono::microseconds{2} == hostTime);
CHECK(std::chrono::microseconds{1} == prevGHostTime);
CHECK(std::chrono::microseconds{4} == ghostTime);
}
TEST_CASE("PingResponder | PingSizeExceeding", "[PingResponder]")
{
using std::chrono::microseconds;
RpFixture fixture;
const auto ht = HostTime{microseconds(2)};
const auto payload = discovery::makePayload(ht, ht, ht);
v1::MessageBuffer buffer;
const auto msgBegin = std::begin(buffer);
const auto msgEnd = v1::pingMessage(payload, msgBegin);
const auto endpoint = asio::ip::udp::endpoint(fixture.mAddress, 8888);
fixture.responderSocket().incomingMessage(endpoint, msgBegin, msgEnd);
CHECK(0 == fixture.numSentMessages());
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_Phase.cpp 0000644 0001750 0001750 00000015000 13303226525 021401 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
namespace ableton
{
namespace link
{
namespace
{
const auto zero = Beats{0.};
const auto one = Beats{1.};
const auto two = Beats{2.};
const auto three = Beats{3.};
const auto four = Beats{4.};
using std::chrono::microseconds;
const auto tl0 = Timeline{Tempo{120.}, one, microseconds{0}};
const auto tl1 = Timeline{Tempo{60.}, Beats{-9.5}, microseconds{2000000}};
} // namespace
TEST_CASE("phase | phase(x, 0) == 0", "[Phase]")
{
CHECK(phase(zero, zero) == zero);
CHECK(phase(Beats{0.1}, zero) == zero);
CHECK(phase(one, zero) == zero);
CHECK(phase(-one, zero) == zero);
}
TEST_CASE("phase | phase(x, y) == x % y when x and y >= 0", "[Phase]")
{
CHECK(phase(zero, zero) == zero % zero);
CHECK(phase(one, zero) == one % zero);
CHECK(phase(zero, one) == zero % one);
CHECK(phase(Beats{0.1}, one) == Beats{0.1} % one);
CHECK(phase(Beats{2.3}, one) == Beats{2.3} % one);
CHECK(phase(Beats{9.5}, Beats{2.3}) == Beats{9.5} % Beats{2.3});
}
TEST_CASE("phase | phase of negatives is not mirrored around zero", "[Phase]")
{
CHECK(phase(-one, one) == zero);
CHECK(phase(Beats{-0.1}, one) == Beats{0.9});
CHECK(phase(Beats{-2.3}, one) == Beats{0.7});
CHECK(phase(Beats{-9.5}, Beats{2.3}) == Beats{2.});
}
TEST_CASE("nextPhaseMatch | result == x when quantum == 0", "[Phase]")
{
CHECK(nextPhaseMatch(Beats{0.1}, one, zero) == Beats{0.1});
CHECK(nextPhaseMatch(Beats{2.3}, Beats{9.5}, zero) == Beats{2.3});
CHECK(nextPhaseMatch(Beats{-0.1}, Beats{-2.3}, zero) == Beats{-0.1});
}
TEST_CASE("nextPhaseMatch | result == target when 0 <= x < target < quantum", "[Phase]")
{
CHECK(nextPhaseMatch(zero, Beats{0.1}, one) == Beats{0.1});
CHECK(nextPhaseMatch(Beats{0.1}, one, two) == one);
CHECK(nextPhaseMatch(one, two, Beats{2.3}) == two);
CHECK(nextPhaseMatch(two, Beats{2.3}, three) == Beats{2.3});
}
TEST_CASE("nextPhaseMatch | some example cases", "[Phase]")
{
CHECK(nextPhaseMatch(one, Beats{2.3}, two) == Beats{2.3});
CHECK(nextPhaseMatch(Beats{2.3}, Beats{-0.1}, two) == Beats{3.9});
CHECK(nextPhaseMatch(Beats{-9.5}, Beats{0.1}, two) == Beats{-7.9});
CHECK(nextPhaseMatch(Beats{-2.3}, Beats{0.1}, Beats{9.5}) == Beats{0.1});
}
TEST_CASE("toPhaseEncodedBeats | result == tl.toBeats when quantum == 0", "[Phase]")
{
const auto t0 = microseconds{0};
const auto t1 = microseconds{2000000};
const auto t2 = microseconds{-3200000};
CHECK(toPhaseEncodedBeats(tl1, t0, zero) == tl1.toBeats(t0));
CHECK(toPhaseEncodedBeats(tl0, t1, zero) == tl0.toBeats(t1));
CHECK(toPhaseEncodedBeats(tl0, t2, zero) == tl0.toBeats(t2));
}
TEST_CASE("toPhaseEncodedBeats | result is the nearest quantum boundary", "[Phase]")
{
const auto sec = microseconds{1000000};
// Takes the previous boundary
CHECK(toPhaseEncodedBeats(tl0, sec, Beats{2.2}) == two);
// Takes the next boundary
CHECK(toPhaseEncodedBeats(tl0, sec, Beats{1.8}) == Beats{3.8});
// Takes the previous boundary when exactly in the middle
CHECK(toPhaseEncodedBeats(tl0, sec, two) == two);
}
TEST_CASE("toPhaseEncodedBeats | some example cases", "[Phase]")
{
CHECK(toPhaseEncodedBeats(tl0, microseconds{-2000000}, four) == -four);
CHECK(toPhaseEncodedBeats(tl0, microseconds{-3000000}, four) == Beats{-6.});
CHECK(toPhaseEncodedBeats(tl0, microseconds{3200000}, three) == Beats{6.4});
CHECK(toPhaseEncodedBeats(tl1, microseconds{0}, three) == Beats{-11.});
CHECK(toPhaseEncodedBeats(tl1, microseconds{1500000}, Beats{2.4}) == Beats{-10.1});
}
namespace
{
std::chrono::microseconds phaseEncodingRoundtrip(
const Timeline& tl, const std::chrono::microseconds t, const Beats quantum)
{
return fromPhaseEncodedBeats(tl, toPhaseEncodedBeats(tl, t, quantum), quantum);
}
} // namespace
TEST_CASE("fromPhaseEncodedBeats | inverse of toPhaseEncodedBeats", "[Phase]")
{
const auto t0 = microseconds{0};
const auto t1 = microseconds{2000000};
const auto t2 = microseconds{-3200000};
const auto t3 = microseconds{87654321};
CHECK(phaseEncodingRoundtrip(tl0, t0, zero) == t0);
CHECK(phaseEncodingRoundtrip(tl0, t1, zero) == t1);
CHECK(phaseEncodingRoundtrip(tl0, t2, zero) == t2);
CHECK(phaseEncodingRoundtrip(tl0, t3, zero) == t3);
CHECK(phaseEncodingRoundtrip(tl0, t0, one) == t0);
CHECK(phaseEncodingRoundtrip(tl0, t1, one) == t1);
CHECK(phaseEncodingRoundtrip(tl0, t2, one) == t2);
CHECK(phaseEncodingRoundtrip(tl0, t3, one) == t3);
CHECK(phaseEncodingRoundtrip(tl0, t0, two) == t0);
CHECK(phaseEncodingRoundtrip(tl0, t1, two) == t1);
CHECK(phaseEncodingRoundtrip(tl0, t2, two) == t2);
CHECK(phaseEncodingRoundtrip(tl0, t3, two) == t3);
CHECK(phaseEncodingRoundtrip(tl0, t0, three) == t0);
CHECK(phaseEncodingRoundtrip(tl0, t1, three) == t1);
CHECK(phaseEncodingRoundtrip(tl0, t2, three) == t2);
CHECK(phaseEncodingRoundtrip(tl0, t3, three) == t3);
CHECK(phaseEncodingRoundtrip(tl1, t0, zero) == t0);
CHECK(phaseEncodingRoundtrip(tl1, t1, zero) == t1);
CHECK(phaseEncodingRoundtrip(tl1, t2, zero) == t2);
CHECK(phaseEncodingRoundtrip(tl1, t3, zero) == t3);
CHECK(phaseEncodingRoundtrip(tl1, t0, one) == t0);
CHECK(phaseEncodingRoundtrip(tl1, t1, one) == t1);
CHECK(phaseEncodingRoundtrip(tl1, t2, one) == t2);
CHECK(phaseEncodingRoundtrip(tl1, t3, one) == t3);
CHECK(phaseEncodingRoundtrip(tl1, t0, two) == t0);
CHECK(phaseEncodingRoundtrip(tl1, t1, two) == t1);
CHECK(phaseEncodingRoundtrip(tl1, t2, two) == t2);
CHECK(phaseEncodingRoundtrip(tl1, t3, two) == t3);
CHECK(phaseEncodingRoundtrip(tl1, t0, three) == t0);
CHECK(phaseEncodingRoundtrip(tl1, t1, three) == t1);
CHECK(phaseEncodingRoundtrip(tl1, t2, three) == t2);
CHECK(phaseEncodingRoundtrip(tl1, t3, three) == t3);
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_HostTimeFilter.cpp 0000644 0001750 0001750 00000004100 13303226525 023242 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
namespace ableton
{
namespace link
{
struct MockClock
{
MockClock()
: time(std::chrono::microseconds(0))
{
}
std::chrono::microseconds micros()
{
const auto current = time;
time += std::chrono::microseconds(1);
return current;
}
std::chrono::microseconds time;
};
using Filter = ableton::link::HostTimeFilter;
TEST_CASE("HostTimeFilter | OneValue", "[HostTimeFilter]")
{
Filter filter;
const auto ht = filter.sampleTimeToHostTime(5);
CHECK(0 == ht.count());
}
TEST_CASE("HostTimeFilter | MultipleValues", "[HostTimeFilter]")
{
Filter filter;
const auto numValues = 600;
auto ht = std::chrono::microseconds(0);
for (int i = 0; i <= numValues; ++i)
{
ht = filter.sampleTimeToHostTime(i);
}
CHECK(numValues == ht.count());
}
TEST_CASE("HostTimeFilter | Reset", "[HostTimeFilter]")
{
Filter filter;
auto ht = filter.sampleTimeToHostTime(0);
ht = filter.sampleTimeToHostTime(-230);
ht = filter.sampleTimeToHostTime(40);
REQUIRE(2 != ht.count());
filter.reset();
ht = filter.sampleTimeToHostTime(0);
CHECK(3 == ht.count());
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_Controller.cpp 0000644 0001750 0001750 00000031755 13303226525 022503 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
#include
namespace ableton
{
namespace link
{
using namespace std::chrono;
static bool operator==(const IncomingClientState& lhs, const ClientState& rhs)
{
return static_cast(lhs.timeline) && static_cast(lhs.startStopState)
&& std::tie(*lhs.timeline, *lhs.startStopState)
== std::tie(rhs.timeline, rhs.startStopState);
}
namespace
{
struct MockClock
{
template
void advance(std::chrono::duration duration)
{
now() += duration;
}
microseconds micros() const
{
return now();
}
private:
static microseconds& now()
{
static microseconds now{1};
return now;
}
};
struct MockIoContext
{
template
struct Socket
{
std::size_t send(
const uint8_t* const, const size_t numBytes, const asio::ip::udp::endpoint&)
{
return numBytes;
}
template
void receive(Handler)
{
}
asio::ip::udp::endpoint endpoint() const
{
return {};
}
};
template
Socket openUnicastSocket(const asio::ip::address_v4&)
{
return {};
}
template
Socket openMulticastSocket(const asio::ip::address_v4&)
{
return {};
}
std::vector scanNetworkInterfaces()
{
return {};
}
using Timer = util::test::Timer;
Timer makeTimer()
{
return {};
}
template
struct LockFreeCallbackDispatcher
{
LockFreeCallbackDispatcher(Callback callback, Duration)
: mCallback(std::move(callback))
{
}
void invoke()
{
mCallback();
}
Callback mCallback;
};
using Log = util::NullLog;
Log log() const
{
return {};
}
template
void async(Handler handler) const
{
handler();
}
MockIoContext clone() const
{
return {};
}
template
MockIoContext clone(ExceptionHandler) const
{
return {};
}
};
using MockController = Controller;
const auto kAnyBeatTime = Beats{5.};
const auto kAnyTime = std::chrono::microseconds{6};
struct TempoClientCallback
{
void operator()(const Tempo bpm)
{
tempos.push_back(bpm);
}
std::vector tempos;
};
struct StartStopStateClientCallback
{
void operator()(const bool isPlaying)
{
startStopStates.push_back(isPlaying);
}
std::vector startStopStates;
};
template
void testSetAndGetClientState(
SetClientStateFunctionT setClientState, GetClientStateFunctionT getClientState)
{
using namespace std::chrono;
auto clock = MockClock{};
MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {},
clock, util::injectVal(MockIoContext{}));
clock.advance(microseconds{1});
const auto initialTimeline =
Optional{Timeline{Tempo{60.}, Beats{0.}, kAnyTime}};
const auto initialStartStopState =
Optional{StartStopState{true, kAnyBeatTime, clock.micros()}};
const auto initialClientState =
IncomingClientState{initialTimeline, initialStartStopState, clock.micros()};
setClientState(controller, initialClientState);
SECTION("Client state is correct after initial set")
{
CHECK(initialClientState == getClientState(controller));
}
SECTION("Set outdated start stop state (timestamp unchanged)")
{
// Set client state with a StartStopState having the same timestamp as the current
// StartStopState - don't advance clock
const auto outdatedStartStopState =
Optional{StartStopState{false, kAnyBeatTime, clock.micros()}};
setClientState(controller,
IncomingClientState{Optional{}, outdatedStartStopState, clock.micros()});
CHECK(initialClientState == getClientState(controller));
}
clock.advance(microseconds{1});
SECTION("Set outdated start stop state (timestamp in past)")
{
const auto outdatedStartStopState =
Optional{StartStopState{false, kAnyBeatTime, microseconds{0}}};
setClientState(controller,
IncomingClientState{Optional{}, outdatedStartStopState, clock.micros()});
CHECK(initialClientState == getClientState(controller));
}
SECTION("Set empty client state")
{
setClientState(controller, IncomingClientState{Optional{},
Optional{}, clock.micros()});
CHECK(initialClientState == getClientState(controller));
}
SECTION("Set client state with new Timeline and StartStopState")
{
const auto expectedTimeline =
Optional{Timeline{Tempo{80.}, Beats{1.}, kAnyTime}};
const auto expectedStartStopState =
Optional{StartStopState{false, kAnyBeatTime, clock.micros()}};
const auto expectedClientState =
IncomingClientState{expectedTimeline, expectedStartStopState, clock.micros()};
setClientState(controller, expectedClientState);
CHECK(expectedClientState == getClientState(controller));
}
}
template
void testCallbackInvocation(SetClientStateFunctionT setClientState)
{
using namespace std::chrono;
auto clock = MockClock{};
auto tempoCallback = TempoClientCallback{};
auto startStopStateCallback = StartStopStateClientCallback{};
MockController controller(Tempo{100.0}, [](std::size_t) {}, std::ref(tempoCallback),
std::ref(startStopStateCallback), clock, util::injectVal(MockIoContext{}));
clock.advance(microseconds{1});
const auto initialTempo = Tempo{50.};
const auto initialIsPlaying = true;
const auto initialTimeline =
Optional{Timeline{initialTempo, Beats{0.}, kAnyTime}};
const auto initialStartStopState = Optional{
StartStopState{initialIsPlaying, kAnyBeatTime, clock.micros()}};
setClientState(controller, {initialTimeline, initialStartStopState, clock.micros()});
SECTION("Callbacks are called when setting new client state")
{
CHECK(std::vector{initialTempo} == tempoCallback.tempos);
CHECK(std::vector{initialIsPlaying} == startStopStateCallback.startStopStates);
clock.advance(microseconds{1});
tempoCallback.tempos = {};
startStopStateCallback.startStopStates = {};
SECTION("Callbacks mustn't be called if Tempo and isPlaying don't change")
{
const auto timeline =
Optional{Timeline{initialTempo, Beats{1.}, kAnyTime}};
const auto startStopState = Optional{
StartStopState{initialIsPlaying, kAnyBeatTime, clock.micros()}};
setClientState(controller, {timeline, startStopState, clock.micros()});
CHECK(tempoCallback.tempos.empty());
CHECK(startStopStateCallback.startStopStates.empty());
}
}
}
} // namespace
TEST_CASE("Controller | ConstructOptimistically", "[Controller]")
{
MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {},
MockClock{}, util::injectVal(MockIoContext{}));
CHECK(!controller.isEnabled());
CHECK(!controller.isStartStopSyncEnabled());
CHECK(0 == controller.numPeers());
const auto tl = controller.clientState().timeline;
CHECK(Tempo{100.0} == tl.tempo);
}
TEST_CASE("Controller | ConstructWithInvalidTempo", "[Controller]")
{
MockController controllerLowTempo(Tempo{1.0}, [](std::size_t) {}, [](Tempo) {},
[](bool) {}, MockClock{}, util::injectVal(MockIoContext{}));
const auto tlLow = controllerLowTempo.clientState().timeline;
CHECK(Tempo{20.0} == tlLow.tempo);
MockController controllerHighTempo(Tempo{100000.0}, [](std::size_t) {}, [](Tempo) {},
[](bool) {}, MockClock{}, util::injectVal(MockIoContext{}));
const auto tlHigh = controllerHighTempo.clientState().timeline;
CHECK(Tempo{999.0} == tlHigh.tempo);
}
TEST_CASE("Controller | EnableDisable", "[Controller]")
{
MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {},
MockClock{}, util::injectVal(MockIoContext{}));
controller.enable(true);
CHECK(controller.isEnabled());
controller.enable(false);
CHECK(!controller.isEnabled());
}
TEST_CASE("Controller | EnableDisableStartStopSync", "[Controller]")
{
MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {},
MockClock{}, util::injectVal(MockIoContext{}));
controller.enableStartStopSync(true);
CHECK(controller.isStartStopSyncEnabled());
controller.enableStartStopSync(false);
CHECK(!controller.isStartStopSyncEnabled());
}
TEST_CASE("Controller | SetAndGetClientStateThreadSafe", "[Controller]")
{
testSetAndGetClientState(
[](MockController& controller, IncomingClientState clientState) {
controller.setClientState(clientState);
},
[](MockController& controller) { return controller.clientState(); });
}
TEST_CASE("Controller | SetAndGetClientStateRealtimeSafe", "[Controller]")
{
testSetAndGetClientState(
[](MockController& controller, IncomingClientState clientState) {
controller.setClientStateRtSafe(clientState);
},
[](MockController& controller) { return controller.clientStateRtSafe(); });
}
TEST_CASE("Controller | SetClientStateRealtimeSafeAndGetItThreadSafe", "[Controller]")
{
testSetAndGetClientState(
[](MockController& controller, IncomingClientState clientState) {
controller.setClientStateRtSafe(clientState);
},
[](MockController& controller) { return controller.clientState(); });
}
TEST_CASE("Controller | SetClientStateThreadSafeAndGetItRealtimeSafe", "[Controller]")
{
testSetAndGetClientState(
[](MockController& controller, IncomingClientState clientState) {
controller.setClientState(clientState);
},
[](MockController& controller) {
MockClock{}.advance(seconds{2});
return controller.clientStateRtSafe();
});
}
TEST_CASE("Controller | CallbacksCalledBySettingClientStateThreadSafe", "[Controller]")
{
testCallbackInvocation([](MockController& controller, IncomingClientState clientState) {
controller.setClientState(clientState);
});
}
TEST_CASE("Controller | CallbacksCalledBySettingClientStateRealtimeSafe", "[Controller]")
{
testCallbackInvocation([](MockController& controller, IncomingClientState clientState) {
controller.setClientStateRtSafe(clientState);
});
}
TEST_CASE("Controller | GetClientStateRtSafeGracePeriod", "[Controller]")
{
using namespace std::chrono;
auto clock = MockClock{};
auto tempoCallback = TempoClientCallback{};
auto startStopStateCallback = StartStopStateClientCallback{};
MockController controller(Tempo{100.0}, [](std::size_t) {}, std::ref(tempoCallback),
std::ref(startStopStateCallback), clock, util::injectVal(MockIoContext{}));
controller.enable(true);
clock.advance(microseconds{1});
const auto initialTimeline =
Optional{Timeline{Tempo{50.}, Beats{0.}, clock.micros()}};
const auto initialStartStopState =
Optional{StartStopState{true, kAnyBeatTime, clock.micros()}};
const auto initialState =
IncomingClientState{initialTimeline, initialStartStopState, clock.micros()};
controller.setClientStateRtSafe(
{initialTimeline, initialStartStopState, clock.micros()});
REQUIRE(initialState == controller.clientState());
REQUIRE(initialState == controller.clientStateRtSafe());
clock.advance(microseconds{1});
const auto newTimeline =
Optional{Timeline{Tempo{70.}, Beats{1.}, clock.micros()}};
const auto newStartStopState =
Optional{StartStopState{false, kAnyBeatTime, clock.micros()}};
const auto newState =
IncomingClientState{newTimeline, newStartStopState, clock.micros()};
controller.setClientState({newTimeline, newStartStopState, clock.micros()});
clock.advance(milliseconds{500});
CHECK(newState == controller.clientState());
CHECK(initialState == controller.clientStateRtSafe());
clock.advance(milliseconds{500});
CHECK(newState == controller.clientState());
CHECK(newState == controller.clientStateRtSafe());
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_StartStopState.cpp 0000644 0001750 0001750 00000002742 13303226525 023316 0 ustar zmoelnig zmoelnig /* Copyright 2017, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
namespace ableton
{
namespace link
{
TEST_CASE("StartStopState | RoundtripByteStreamEncoding", "[StartStopState]")
{
const auto originalState =
StartStopState{true, Beats{1234.}, std::chrono::microseconds{5678}};
std::vector bytes(sizeInByteStream(originalState));
const auto serializedEndIter = toNetworkByteStream(originalState, begin(bytes));
const auto deserialized =
StartStopState::fromNetworkByteStream(begin(bytes), serializedEndIter);
CHECK(originalState == deserialized.first);
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_CircularFifo.cpp 0000644 0001750 0001750 00000003572 13303226525 022724 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
namespace ableton
{
namespace link
{
TEST_CASE("CircularFifo | PushNPop", "[CircularFifo]")
{
CircularFifo cf;
for (int i = 0; i < 2; ++i)
{
CHECK(cf.push(i));
}
CHECK(!cf.push(0));
for (int i = 0; i < 2; ++i)
{
auto result = cf.pop();
CHECK(result);
CHECK(*result == i);
}
CHECK(!cf.pop());
}
TEST_CASE("CircularFifo | Wrap", "[CircularFifo]")
{
CircularFifo cf;
for (int i = 0; i < 5; ++i)
{
CHECK(cf.push(i));
auto result = cf.pop();
CHECK(result);
CHECK(*result == i);
}
}
TEST_CASE("CircularFifo | IsEmpty", "[CircularFifo]")
{
CircularFifo cf;
CHECK(cf.isEmpty());
CHECK(cf.push(1));
CHECK(!cf.isEmpty());
CHECK(cf.push(2));
CHECK(!cf.isEmpty());
CHECK(!cf.push(3));
CHECK(!cf.isEmpty());
CHECK(cf.pop());
CHECK(!cf.isEmpty());
CHECK(cf.pop());
CHECK(cf.isEmpty());
CHECK(!cf.pop());
CHECK(cf.isEmpty());
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_LinearRegression.cpp 0000644 0001750 0001750 00000004373 13303226525 023627 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
#include
namespace ableton
{
namespace link
{
using Vector = std::vector>;
TEST_CASE("LinearRegression | EmptyVector", "[LinearRegression]")
{
Vector data;
const auto result = linearRegression(data.begin(), data.end());
CHECK(0 == Approx(result.first));
}
TEST_CASE("LinearRegression | OnePoint", "[LinearRegression]")
{
using Array = std::array, 1>;
Array data;
data[0] = {{}, {}};
const auto result = linearRegression(data.begin(), data.end());
CHECK(0 == Approx(result.first));
CHECK(0 == Approx(result.second));
}
TEST_CASE("LinearRegression | TwoPoints", "[LinearRegression]")
{
Vector data;
data.emplace_back(0.0, 0.0);
data.emplace_back(666666.6, 66666.6);
const auto result = linearRegression(data.begin(), data.end());
CHECK(0.1 == Approx(result.first));
CHECK(0.0 == Approx(result.second));
}
TEST_CASE("LinearRegression | 10000Points", "[LinearRegression]")
{
Vector data;
const double slope = -0.2;
const double intercept = -357.53456;
for (int i = 1; i < 10000; ++i)
{
data.emplace_back(i, i * slope + intercept);
}
const auto result = linearRegression(data.begin(), data.end());
CHECK(slope == Approx(result.first));
CHECK(intercept == Approx(result.second));
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_Kalman.cpp 0000644 0001750 0001750 00000003221 13303226525 021546 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
#include
namespace ableton
{
namespace link
{
TEST_CASE("Kalman | Check1", "[Kalman]")
{
int peerTimeDiff = 0;
Kalman<16> filter;
for (int i = 0; i < 5; ++i)
{
filter.iterate(peerTimeDiff);
}
CHECK(peerTimeDiff == Approx(filter.getValue()));
filter.iterate(100);
CHECK(peerTimeDiff != Approx(filter.getValue()));
}
TEST_CASE("Kalman | Check2", "[Kalman]")
{
double peerTimeDiff = 3e11;
Kalman<5> filter;
for (int i = 0; i < 15; ++i)
{
filter.iterate(peerTimeDiff);
}
CHECK(peerTimeDiff == Approx(filter.getValue()));
for (int i = 0; i < 15; ++i)
{
filter.iterate(11);
}
CHECK(peerTimeDiff != Approx(filter.getValue()));
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_Timeline.cpp 0000644 0001750 0001750 00000003437 13303226525 022122 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
namespace ableton
{
namespace link
{
TEST_CASE("Timeline | TimeToBeats", "[Timeline]")
{
const auto tl = Timeline{Tempo{60.}, Beats{-1.}, std::chrono::microseconds{1000000}};
CHECK(Beats{2.5} == tl.toBeats(std::chrono::microseconds{4500000}));
}
TEST_CASE("Timeline | BeatsToTime", "[Timeline]")
{
const auto tl = Timeline{Tempo{60.}, Beats{-1.}, std::chrono::microseconds{1000000}};
CHECK(std::chrono::microseconds{5200000} == tl.fromBeats(Beats{3.2}));
}
TEST_CASE("Timeline | RoundtripByteStreamEncoding", "[Timeline]")
{
const auto tl = Timeline{Tempo{120.}, Beats{5.5}, std::chrono::microseconds{12558940}};
std::vector bytes(sizeInByteStream(tl));
const auto end = toNetworkByteStream(tl, begin(bytes));
const auto result = Timeline::fromNetworkByteStream(begin(bytes), end);
CHECK(tl == result.first);
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_Peers.cpp 0000644 0001750 0001750 00000015562 13303226525 021434 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
namespace ableton
{
namespace link
{
namespace
{
struct SessionMembershipCallback
{
void operator()()
{
++calls;
}
std::size_t calls = 0;
};
struct SessionTimelineCallback
{
void operator()(const SessionId& sessionId, const Timeline& timeline)
{
sessionTimelines.push_back(std::make_pair(sessionId, timeline));
}
std::vector> sessionTimelines;
};
struct SessionStartStopStateCallback
{
void operator()(const SessionId& sessionId, const StartStopState& startStopState)
{
sessionStartStopStates.push_back(std::make_pair(sessionId, startStopState));
}
std::vector> sessionStartStopStates;
};
const auto fooPeer =
PeerState{{NodeId::random(), NodeId::random(),
Timeline{Tempo{60.}, Beats{1.}, std::chrono::microseconds{1234}},
StartStopState{false, Beats{0.}, std::chrono::microseconds{2345}}},
{}};
const auto barPeer =
PeerState{{NodeId::random(), NodeId::random(),
Timeline{Tempo{120.}, Beats{10.}, std::chrono::microseconds{500}}, {}},
{}};
const auto bazPeer =
PeerState{{NodeId::random(), NodeId::random(),
Timeline{Tempo{100.}, Beats{4.}, std::chrono::microseconds{100}}, {}},
{}};
const auto gateway1 = asio::ip::address::from_string("123.123.123.123");
const auto gateway2 = asio::ip::address::from_string("210.210.210.210");
using PeerVector = std::vector::Peer>;
void expectPeers(const PeerVector& expected, const PeerVector& actual)
{
CHECK(expected == actual);
}
void expectSessionTimelines(const std::vector>& expected,
const SessionTimelineCallback& callback)
{
CHECK(expected == callback.sessionTimelines);
}
void expectStartStopStates(
const std::vector>& expected,
const SessionStartStopStateCallback& callback)
{
CHECK(expected == callback.sessionStartStopStates);
}
} // anonymous namespace
TEST_CASE("Peers | EmptySessionPeersAfterInit", "[Peers]")
{
test::serial_io::Fixture io;
auto peers = makePeers(util::injectVal(io.makeIoContext()), SessionMembershipCallback{},
SessionTimelineCallback{}, SessionStartStopStateCallback{});
io.flush();
expectPeers({}, peers.sessionPeers(fooPeer.sessionId()));
}
TEST_CASE("Peers | AddAndFindPeer", "[Peers]")
{
test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto sessions = SessionTimelineCallback{};
auto startStops = SessionStartStopStateCallback{};
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
std::ref(sessions), std::ref(startStops));
auto observer = makeGatewayObserver(peers, gateway1);
sawPeer(observer, fooPeer);
io.flush();
expectPeers({{fooPeer, gateway1}}, peers.sessionPeers(fooPeer.sessionId()));
CHECK(1u == membership.calls);
expectSessionTimelines({make_pair(fooPeer.sessionId(), fooPeer.timeline())}, sessions);
expectStartStopStates(
{make_pair(fooPeer.sessionId(), fooPeer.startStopState())}, startStops);
}
TEST_CASE("Peers | AddAndRemovePeer", "[Peers]")
{
test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
SessionTimelineCallback{}, SessionStartStopStateCallback{});
auto observer = makeGatewayObserver(peers, gateway1);
sawPeer(observer, fooPeer);
peerLeft(observer, fooPeer.ident());
io.flush();
expectPeers({}, peers.sessionPeers(fooPeer.sessionId()));
CHECK(2u == membership.calls);
}
TEST_CASE("Peers | AddTwoPeersRemoveOne", "[Peers]")
{
test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto sessions = SessionTimelineCallback{};
auto startStops = SessionStartStopStateCallback{};
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
std::ref(sessions), std::ref(startStops));
auto observer = makeGatewayObserver(peers, gateway1);
sawPeer(observer, fooPeer);
sawPeer(observer, barPeer);
peerLeft(observer, fooPeer.ident());
io.flush();
expectPeers({}, peers.sessionPeers(fooPeer.sessionId()));
expectPeers({{barPeer, gateway1}}, peers.sessionPeers(barPeer.sessionId()));
CHECK(3u == membership.calls);
}
TEST_CASE("Peers | AddThreePeersTwoOnSameGateway", "[Peers]")
{
test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto sessions = SessionTimelineCallback{};
auto startStops = SessionStartStopStateCallback{};
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
std::ref(sessions), std::ref(startStops));
auto observer1 = makeGatewayObserver(peers, gateway1);
auto observer2 = makeGatewayObserver(peers, gateway2);
sawPeer(observer1, fooPeer);
sawPeer(observer2, fooPeer);
sawPeer(observer1, barPeer);
sawPeer(observer1, bazPeer);
io.flush();
expectPeers(
{{fooPeer, gateway1}, {fooPeer, gateway2}}, peers.sessionPeers(fooPeer.sessionId()));
CHECK(3 == membership.calls);
}
TEST_CASE("Peers | CloseGateway", "[Peers]")
{
test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto sessions = SessionTimelineCallback{};
auto startStops = SessionStartStopStateCallback{};
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
std::ref(sessions), std::ref(startStops));
auto observer1 = makeGatewayObserver(peers, gateway1);
{
// The observer will close the gateway when it goes out of scope
auto observer2 = makeGatewayObserver(peers, gateway2);
sawPeer(observer2, fooPeer);
sawPeer(observer2, barPeer);
sawPeer(observer1, fooPeer);
sawPeer(observer2, bazPeer);
}
io.flush();
expectPeers({{fooPeer, gateway1}}, peers.sessionPeers(fooPeer.sessionId()));
CHECK(4 == membership.calls);
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_Measurement.cpp 0000644 0001750 0001750 00000011372 13303226525 022636 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ableton
{
namespace link
{
namespace
{
using Socket = discovery::test::Socket;
struct MockClock
{
std::chrono::microseconds micros() const
{
return std::chrono::microseconds{4};
}
};
struct TFixture
{
TFixture()
: mAddress(asio::ip::address_v4::from_string("127.0.0.1"))
, mMeasurement(mStateQuery(),
[](std::vector>) {},
mAddress,
MockClock{},
util::injectVal(util::NullLog{}))
{
}
Socket& socket()
{
return *(mMeasurement.mpImpl->mpSocket);
}
struct StateQuery
{
StateQuery()
{
mState.nodeState.sessionId = NodeId::random();
mState.endpoint =
asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 9999);
}
PeerState operator()()
{
return mState;
}
PeerState mState;
};
struct GhostXFormQuery
{
GhostXForm operator()() const
{
return {1.0, std::chrono::microseconds{0}};
}
};
StateQuery mStateQuery;
GhostXFormQuery mGhostXFormQuery;
asio::ip::address_v4 mAddress;
Measurement
mMeasurement;
};
} // anonymous namespace
TEST_CASE("Measurement | SendPingsOnConstruction", "[Measurement]")
{
TFixture fixture;
CHECK(1 == fixture.socket().sentMessages.size());
const auto messageBuffer = fixture.socket().sentMessages[0].first;
const auto result =
v1::parseMessageHeader(std::begin(messageBuffer), std::end(messageBuffer));
const auto& header = result.first;
std::chrono::microseconds gt{0};
std::chrono::microseconds ht{0};
discovery::parsePayload(result.second, std::end(messageBuffer),
[>](GHostTime ghostTime) { gt = std::move(ghostTime.time); },
[&ht](HostTime hostTime) { ht = std::move(hostTime.time); });
CHECK(v1::kPing == header.messageType);
CHECK(std::chrono::microseconds{4} == ht);
CHECK(std::chrono::microseconds{0} == gt);
}
TEST_CASE("Measurement | ReceiveInitPong", "[Measurement]")
{
using Micros = std::chrono::microseconds;
TFixture fixture;
const auto id = SessionMembership{fixture.mStateQuery.mState.nodeState.sessionId};
const auto ht = HostTime{Micros(0)};
const auto gt = GHostTime{Micros(0)};
const auto payload = discovery::makePayload(id, gt, ht);
v1::MessageBuffer buffer;
const auto msgBegin = std::begin(buffer);
const auto msgEnd = v1::pongMessage(payload, msgBegin);
const auto endpoint =
asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 8888);
fixture.socket().incomingMessage(endpoint, msgBegin, msgEnd);
CHECK(2 == fixture.socket().sentMessages.size());
CHECK(0 == fixture.mMeasurement.mpImpl->mData.size());
}
TEST_CASE("Measurement | ReceivePong", "[Measurement]")
{
using Micros = std::chrono::microseconds;
TFixture fixture;
const auto id = SessionMembership{fixture.mStateQuery.mState.nodeState.sessionId};
const auto ht = HostTime{Micros(2)};
const auto gt = GHostTime{Micros(3)};
const auto pgt = PrevGHostTime{Micros(1)};
const auto payload = discovery::makePayload(id, gt, ht, pgt);
v1::MessageBuffer buffer;
const auto msgBegin = std::begin(buffer);
const auto msgEnd = v1::pongMessage(payload, msgBegin);
const auto endpoint =
asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 8888);
fixture.socket().incomingMessage(endpoint, msgBegin, msgEnd);
CHECK(2 == fixture.socket().sentMessages.size());
CHECK(2 == fixture.mMeasurement.mpImpl->mData.size());
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/link/tst_Beats.cpp 0000644 0001750 0001750 00000005030 13303226525 021401 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
namespace ableton
{
namespace link
{
TEST_CASE("Beats | ConstructFromFloating", "[Beats]")
{
const auto beats = Beats{0.5};
CHECK(500000 == beats.microBeats());
CHECK(0.5 == Approx(beats.floating()));
}
TEST_CASE("Beats | ConstructFromMicros", "[Beats]")
{
const auto beats = Beats{INT64_C(100000)};
CHECK(100000 == beats.microBeats());
CHECK(0.1 == Approx(beats.floating()));
}
TEST_CASE("Beats | Negation", "[Beats]")
{
const auto beat = Beats{1.};
CHECK(beat > -beat);
CHECK(beat == -(-beat));
CHECK(-beat < Beats{0.});
}
TEST_CASE("Beats | Addition", "[Beats]")
{
const auto beat1 = Beats{0.5};
const auto beat2 = Beats{INT64_C(200000)};
const auto beat3 = Beats{0.1};
CHECK(beat1 == beat2 + beat2 + beat3);
}
TEST_CASE("Beats | Subtraction", "[Beats]")
{
const auto beat1 = Beats{0.5};
const auto beat2 = Beats{INT64_C(200000)};
const auto beat3 = Beats{0.1};
CHECK(beat3 == beat1 - beat2 - beat2);
}
TEST_CASE("Beats | Modulo", "[Beats]")
{
const auto beat1 = Beats{0.1};
const auto beat2 = Beats{0.5};
const auto beat3 = Beats{0.6};
const auto beat4 = Beats{0.};
CHECK(beat1 == beat3 % beat2);
CHECK(beat4 == beat3 % beat4);
}
TEST_CASE("Beats | SizeInByteStream", "[Beats]")
{
Beats beats{0.5};
CHECK(8 == sizeInByteStream(beats));
}
TEST_CASE("Beats | RoundtripByteStreamEncoding", "[Beats]")
{
Beats beats{0.5};
std::vector bytes(sizeInByteStream(beats));
const auto end = toNetworkByteStream(beats, begin(bytes));
const auto result = Beats::fromNetworkByteStream(begin(bytes), end);
CHECK(beats == result.first);
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/discovery/ 0000755 0001750 0001750 00000000000 13303226525 020021 5 ustar zmoelnig zmoelnig link-Link-3.0.2/src/ableton/discovery/v1/ 0000755 0001750 0001750 00000000000 13303226525 020347 5 ustar zmoelnig zmoelnig link-Link-3.0.2/src/ableton/discovery/v1/tst_Messages.cpp 0000644 0001750 0001750 00000005025 13303226525 023516 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
namespace ableton
{
namespace discovery
{
namespace v1
{
namespace
{
// For testing just use a single byte identifier
using NodeId = uint8_t;
} // anonymous namespace
TEST_CASE("ParseEmptyBuffer", "[Messages]")
{
const std::array buffer{};
auto result = parseMessageHeader(begin(buffer), end(buffer));
CHECK(kInvalid == result.first.messageType);
CHECK(begin(buffer) == result.second);
}
TEST_CASE("ParseTruncatedMessageHeader", "[Messages]")
{
const std::array truncated = {
{'_', 'a', 's', 'd', 'p', '_', 'v', 1, 'x', 'y'}};
const auto result = parseMessageHeader(begin(truncated), end(truncated));
CHECK(kInvalid == result.first.messageType);
const auto consumedBytes = (begin(truncated) != result.second);
CHECK_FALSE(consumedBytes);
}
TEST_CASE("MissingProtocolHeader", "[Messages]")
{
// Buffer should be large enough to proceed but shouldn't match the protocol header
const std::array zeros{};
auto result = parseMessageHeader(begin(zeros), end(zeros));
CHECK(kInvalid == result.first.messageType);
CHECK(begin(zeros) == result.second);
}
TEST_CASE("RoundtripAliveNoPayload", "[Messages]")
{
std::array buffer{};
const uint8_t ident = 1;
const auto endAlive = aliveMessage(ident, 5, makePayload(), begin(buffer));
const auto result = parseMessageHeader(begin(buffer), endAlive);
CHECK(endAlive == result.second);
CHECK(kAlive == result.first.messageType);
CHECK(5 == result.first.ttl);
CHECK(ident == result.first.ident);
}
} // namespace v1
} // namespace discovery
} // namespace ableton
link-Link-3.0.2/src/ableton/discovery/tst_PeerGateway.cpp 0000644 0001750 0001750 00000012716 13303226525 023643 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
#include
namespace ableton
{
namespace discovery
{
namespace
{
struct TestNodeState : std::tuple
{
TestNodeState(std::string ident, double tempo)
: std::tuple(std::move(ident), std::move(tempo))
{
}
std::string ident() const
{
return std::get<0>(*this);
}
};
struct TestMessenger
{
template
void receive(Handler handler)
{
receivePeerState = [handler](const PeerState& msg) { handler(msg); };
receiveByeBye = [handler](const ByeBye& msg) { handler(msg); };
}
std::function&)> receivePeerState;
std::function&)> receiveByeBye;
};
struct TestObserver
{
using GatewayObserverNodeState = TestNodeState;
using GatewayObserverNodeId = std::string;
friend void sawPeer(TestObserver& observer, const GatewayObserverNodeState& peer)
{
observer.mPeersSeen.push_back(peer);
}
friend void peerLeft(TestObserver& observer, const GatewayObserverNodeId& id)
{
observer.mPeersLeft.push_back(id);
}
friend void peerTimedOut(TestObserver& observer, const GatewayObserverNodeId& id)
{
observer.mPeersTimedOut.push_back(id);
}
std::vector mPeersSeen;
std::vector mPeersLeft;
std::vector mPeersTimedOut;
};
void expectPeersSeen(std::vector expected, const TestObserver& observer)
{
CHECK(expected == observer.mPeersSeen);
}
void expectPeersLeft(std::vector expected, const TestObserver& observer)
{
CHECK(expected == observer.mPeersLeft);
}
void expectPeersTimedOut(std::vector expected, const TestObserver& observer)
{
CHECK(expected == observer.mPeersTimedOut);
}
// Test peers
const auto peerA = TestNodeState{"peerA", 120};
const auto peerB = TestNodeState{"peerB", 150};
} // anonymous namespace
TEST_CASE("PeerGateway | NoActivity", "[PeerGateway]")
{
test::serial_io::Fixture io;
TestObserver observer;
auto listener = makePeerGateway(util::injectVal(TestMessenger{}),
util::injectRef(observer), util::injectVal(io.makeIoContext()));
io.advanceTime(std::chrono::seconds(10));
// Without any outside interaction but the passage of time, our
// listener should not have seen any peers.
CHECK(observer.mPeersSeen.empty());
CHECK(observer.mPeersLeft.empty());
CHECK(observer.mPeersTimedOut.empty());
}
TEST_CASE("PeerGateway | ReceivedPeerState", "[PeerGateway]")
{
test::serial_io::Fixture io;
TestObserver observer;
TestMessenger messenger;
auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer),
util::injectVal(io.makeIoContext()));
messenger.receivePeerState({peerA, 5});
io.flush();
expectPeersSeen({peerA}, observer);
}
TEST_CASE("PeerGateway | TwoPeersOneLeaves", "[PeerGateway]")
{
test::serial_io::Fixture io;
TestObserver observer;
TestMessenger messenger;
auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer),
util::injectVal(io.makeIoContext()));
messenger.receivePeerState({peerA, 5});
messenger.receivePeerState({peerB, 5});
messenger.receiveByeBye({peerA.ident()});
io.flush();
expectPeersSeen({peerA, peerB}, observer);
expectPeersLeft({peerA.ident()}, observer);
}
TEST_CASE("PeerGateway | TwoPeersOneTimesOut", "[PeerGateway]")
{
test::serial_io::Fixture io;
TestObserver observer;
TestMessenger messenger;
auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer),
util::injectVal(io.makeIoContext()));
messenger.receivePeerState({peerA, 5});
messenger.receivePeerState({peerB, 10});
io.advanceTime(std::chrono::seconds(3));
expectPeersTimedOut({}, observer);
io.advanceTime(std::chrono::seconds(4));
expectPeersTimedOut({peerA.ident()}, observer);
}
TEST_CASE("PeerGateway | PeerTimesOutAndIsSeenAgain", "[PeerGateway]")
{
test::serial_io::Fixture io;
TestObserver observer;
TestMessenger messenger;
auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer),
util::injectVal(io.makeIoContext()));
messenger.receivePeerState({peerA, 5});
io.advanceTime(std::chrono::seconds(7));
expectPeersTimedOut({peerA.ident()}, observer);
messenger.receivePeerState({peerA, 5});
io.advanceTime(std::chrono::seconds(3));
expectPeersSeen({peerA, peerA}, observer);
expectPeersTimedOut({peerA.ident()}, observer);
}
} // namespace discovery
} // namespace ableton
link-Link-3.0.2/src/ableton/discovery/tst_PeerGateways.cpp 0000644 0001750 0001750 00000010151 13303226525 024015 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
namespace ableton
{
namespace discovery
{
namespace
{
struct Gateway
{
asio::ip::address addr;
};
struct NodeState
{
};
struct Factory
{
template
Gateway operator()(NodeState, util::Injected, const asio::ip::address& addr)
{
return {addr};
}
};
const asio::ip::address addr1 = asio::ip::address::from_string("192.192.192.1");
const asio::ip::address addr2 = asio::ip::address::from_string("192.192.192.2");
template
void expectGatewaysAsync(
Gateways& gateways, test::serial_io::Fixture& io, std::vector addrs)
{
using namespace std;
using GatewayIt = typename Gateways::GatewayMap::iterator;
bool bTested = false;
gateways.withGatewaysAsync([addrs, &bTested](GatewayIt begin, const GatewayIt end) {
bTested = true;
REQUIRE(static_cast(distance(begin, end)) == addrs.size());
std::size_t i = 0;
for (; begin != end; ++begin)
{
CHECK(begin->first == addrs[i++]);
}
});
io.flush();
CHECK(bTested);
}
} // anonymous namespace
TEST_CASE("PeerGateways | EmptyIfNoInterfaces", "[PeerGateways]")
{
test::serial_io::Fixture io;
auto pGateways = makePeerGateways(
std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext()));
pGateways->enable(true);
expectGatewaysAsync(*pGateways, io, {});
}
TEST_CASE("PeerGateways | MatchesAfterInitialScan", "[PeerGateways]")
{
test::serial_io::Fixture io;
io.setNetworkInterfaces({addr1, addr2});
auto pGateways = makePeerGateways(
std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext()));
pGateways->enable(true);
expectGatewaysAsync(*pGateways, io, {addr1, addr2});
}
TEST_CASE("PeerGateways | GatewayAppears", "[PeerGateways]")
{
test::serial_io::Fixture io;
io.setNetworkInterfaces({addr1});
auto pGateways = makePeerGateways(
std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext()));
pGateways->enable(true);
expectGatewaysAsync(*pGateways, io, {addr1});
io.setNetworkInterfaces({addr1, addr2});
io.advanceTime(std::chrono::seconds(3));
expectGatewaysAsync(*pGateways, io, {addr1, addr2});
}
TEST_CASE("PeerGateways | GatewayDisappears", "[PeerGateways]")
{
test::serial_io::Fixture io;
io.setNetworkInterfaces({addr1, addr2});
auto pGateways = makePeerGateways(
std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext()));
pGateways->enable(true);
expectGatewaysAsync(*pGateways, io, {addr1, addr2});
io.setNetworkInterfaces({addr1});
io.advanceTime(std::chrono::seconds(3));
expectGatewaysAsync(*pGateways, io, {addr1});
}
TEST_CASE("PeerGateways | GatewayChangesAddress", "[PeerGateways]")
{
test::serial_io::Fixture io;
io.setNetworkInterfaces({addr1});
auto pGateways = makePeerGateways(
std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext()));
pGateways->enable(true);
expectGatewaysAsync(*pGateways, io, {addr1});
io.setNetworkInterfaces({addr2});
io.advanceTime(std::chrono::seconds(3));
expectGatewaysAsync(*pGateways, io, {addr2});
}
} // namespace discovery
} // namespace ableton
link-Link-3.0.2/src/ableton/discovery/tst_InterfaceScanner.cpp 0000644 0001750 0001750 00000005325 13303226525 024636 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
namespace ableton
{
namespace link
{
namespace
{
struct TestCallback
{
template
void operator()(AddrRange addrs)
{
addrRanges.emplace_back(begin(addrs), end(addrs));
}
std::vector> addrRanges;
};
// some test addresses
const asio::ip::address addr1 = asio::ip::address::from_string("123.123.123.1");
const asio::ip::address addr2 = asio::ip::address::from_string("123.123.123.2");
} // anonymous namespace
TEST_CASE("InterfaceScanner | NoInterfacesThenOne", "[InterfaceScanner]")
{
test::serial_io::Fixture io;
auto callback = TestCallback{};
{
auto scanner = discovery::makeInterfaceScanner(std::chrono::seconds(2),
util::injectRef(callback), util::injectVal(io.makeIoContext()));
scanner.enable(true);
CHECK(1 == callback.addrRanges.size());
io.setNetworkInterfaces({addr1});
io.advanceTime(std::chrono::seconds(3));
}
REQUIRE(2 == callback.addrRanges.size());
CHECK(0 == callback.addrRanges[0].size());
REQUIRE(1 == callback.addrRanges[1].size());
CHECK(addr1 == callback.addrRanges[1].front());
}
TEST_CASE("InterfaceScanner | InterfaceGoesAway", "[InterfaceScanner]")
{
test::serial_io::Fixture io;
io.setNetworkInterfaces({addr1});
auto callback = TestCallback{};
{
auto scanner = discovery::makeInterfaceScanner(std::chrono::seconds(2),
util::injectRef(callback), util::injectVal(io.makeIoContext()));
scanner.enable(true);
io.setNetworkInterfaces({});
io.advanceTime(std::chrono::seconds(3));
}
REQUIRE(2 == callback.addrRanges.size());
REQUIRE(1 == callback.addrRanges[0].size());
CHECK(addr1 == callback.addrRanges[0].front());
CHECK(0 == callback.addrRanges[1].size());
}
} // namespace link
} // namespace ableton
link-Link-3.0.2/src/ableton/discovery/tst_Payload.cpp 0000644 0001750 0001750 00000015514 13303226525 023016 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
#include
#include
namespace ableton
{
namespace discovery
{
TEST_CASE("Payload | EmptyPayload", "[Payload]")
{
CHECK(0 == sizeInByteStream(makePayload()));
CHECK(nullptr == toNetworkByteStream(makePayload(), nullptr));
}
TEST_CASE("Payload | FixedSizeEntryPayloadSize", "[Payload]")
{
CHECK(12 == sizeInByteStream(makePayload(test::Foo{})));
}
TEST_CASE("Payload | SingleEntryPayloadEncoding", "[Payload]")
{
const auto payload = makePayload(test::Foo{-1});
std::vector bytes(sizeInByteStream(payload));
const auto end = toNetworkByteStream(payload, begin(bytes));
// Should have filled the buffer with the payload
CHECK(bytes.size() == static_cast(end - begin(bytes)));
// Should have encoded the value 1 after the payload entry header
// as an unsigned in network byte order
const auto uresult = ntohl(reinterpret_cast(*(begin(bytes) + 8)));
CHECK(-1 == reinterpret_cast(uresult));
}
TEST_CASE("Payload | DoubleEntryPayloadSize", "[Payload]")
{
CHECK(48 == sizeInByteStream(makePayload(test::Foo{}, test::Bar{{0, 1, 2}})));
}
TEST_CASE("Payload | DoubleEntryPayloadEncoding", "[Payload]")
{
const auto payload = makePayload(test::Foo{1}, test::Bar{{0, 1, 2}});
std::vector bytes(sizeInByteStream(payload));
const auto end = toNetworkByteStream(payload, begin(bytes));
// Should have filled the buffer with the payload
CHECK(bytes.size() == static_cast(end - begin(bytes)));
// Should have encoded the value 1 after the first payload entry header
// and the number of elements of the vector as well as 0,1,2 after the
// second payload entry header in network byte order
CHECK(1 == ntohl(reinterpret_cast(*(begin(bytes) + 8))));
CHECK(3 == ntohl(reinterpret_cast(*(begin(bytes) + 20))));
CHECK(0 == ntohll(reinterpret_cast(*(begin(bytes) + 24))));
CHECK(1 == ntohll(reinterpret_cast(*(begin(bytes) + 32))));
CHECK(2 == ntohll(reinterpret_cast(*(begin(bytes) + 40))));
}
TEST_CASE("Payload | RoundtripSingleEntry", "[Payload]")
{
const auto expected = test::Foo{1};
const auto payload = makePayload(expected);
std::vector bytes(sizeInByteStream(payload));
const auto end = toNetworkByteStream(payload, begin(bytes));
test::Foo actual{};
parsePayload(
begin(bytes), end, [&actual](const test::Foo& foo) { actual = foo; });
CHECK(expected.fooVal == actual.fooVal);
}
TEST_CASE("Payload | RoundtripDoubleEntry", "[Payload]")
{
const auto expectedFoo = test::Foo{1};
const auto expectedBar = test::Bar{{0, 1, 2}};
const auto payload = makePayload(expectedBar, expectedFoo);
std::vector bytes(sizeInByteStream(payload));
const auto end = toNetworkByteStream(payload, begin(bytes));
test::Foo actualFoo{};
test::Bar actualBar{};
parsePayload(begin(bytes), end,
[&actualFoo](const test::Foo& foo) { actualFoo = foo; },
[&actualBar](const test::Bar& bar) { actualBar = bar; });
CHECK(expectedFoo.fooVal == actualFoo.fooVal);
CHECK(expectedBar.barVals == actualBar.barVals);
}
TEST_CASE("Payload | RoundtripSingleEntryWithMultipleVectors", "[Payload]")
{
const auto expectedFoobar = test::Foobar{{0, 1, 2}, {3, 4, 5}};
const auto payload = makePayload(expectedFoobar);
std::vector bytes(sizeInByteStream(payload));
const auto end = toNetworkByteStream(payload, begin(bytes));
test::Foobar actualFoobar{};
parsePayload(begin(bytes), end,
[&actualFoobar](const test::Foobar& foobar) { actualFoobar = foobar; });
CHECK(expectedFoobar.asTuple() == actualFoobar.asTuple());
}
TEST_CASE("Payload | ParseSubset", "[Payload]")
{
// Encode two payload entries
const auto expectedFoo = test::Foo{1};
const auto expectedBar = test::Bar{{0, 1, 2}};
const auto payload = makePayload(expectedFoo, expectedBar);
std::vector bytes(sizeInByteStream(payload));
const auto end = toNetworkByteStream(payload, begin(bytes));
// Only decode one of them
test::Bar actualBar{};
parsePayload(
begin(bytes), end, [&actualBar](const test::Bar& bar) { actualBar = bar; });
CHECK(expectedBar.barVals == actualBar.barVals);
}
TEST_CASE("Payload | ParseTruncatedEntry", "[Payload]")
{
const auto expectedFoo = test::Foo{1};
const auto expectedBar = test::Bar{{0, 1, 2}};
const auto payload = makePayload(expectedBar, expectedFoo);
std::vector bytes(sizeInByteStream(payload));
const auto end = toNetworkByteStream(payload, begin(bytes));
test::Foo actualFoo{};
test::Bar actualBar{};
REQUIRE_THROWS_AS(( // We truncate the buffer by one byte
parsePayload(begin(bytes), end - 1,
[&actualFoo](const test::Foo& foo) { actualFoo = foo; },
[&actualBar](const test::Bar& bar) { actualBar = bar; })),
std::runtime_error);
// We expect that bar should be properly parsed but foo not
CHECK(0 == actualFoo.fooVal);
CHECK(expectedBar.barVals == actualBar.barVals);
}
TEST_CASE("Payload | AddPayloads", "[Payload]")
{
// The sum of a foo payload and a bar payload should be equal in
// every way to a foobar payload
const auto foo = test::Foo{1};
const auto bar = test::Bar{{0, 1, 2}};
const auto fooBarPayload = makePayload(foo, bar);
const auto sumPayload = makePayload(foo) + makePayload(bar);
REQUIRE(sizeInByteStream(fooBarPayload) == sizeInByteStream(sumPayload));
std::vector fooBarBytes(sizeInByteStream(fooBarPayload));
std::vector sumBytes(sizeInByteStream(sumPayload));
const auto fooBarEnd = toNetworkByteStream(fooBarPayload, begin(fooBarBytes));
toNetworkByteStream(sumPayload, begin(sumBytes));
CHECK(std::equal(begin(fooBarBytes), fooBarEnd, begin(sumBytes)));
}
} // namespace discovery
} // namespace ableton
link-Link-3.0.2/src/ableton/discovery/tst_UdpMessenger.cpp 0000644 0001750 0001750 00000016747 13303226525 024037 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#include
#include
#include
#include
namespace ableton
{
namespace discovery
{
namespace
{
struct TestNodeState
{
using IdType = uint8_t;
IdType ident() const
{
return nodeId;
}
friend auto toPayload(const TestNodeState& state) -> decltype(makePayload(test::Foo{}))
{
return makePayload(test::Foo{state.fooVal});
}
template
static TestNodeState fromPayload(const uint8_t id, It begin, It end)
{
TestNodeState state = {id, 0};
parsePayload(
begin, end, [&state](const test::Foo& foo) { state.fooVal = foo.fooVal; });
return state;
}
IdType nodeId;
int32_t fooVal;
};
// Test data for a simulated peer
const TestNodeState peerState = {5, 15};
const asio::ip::udp::endpoint peerEndpoint = {
asio::ip::address::from_string("123.123.234.234"), 1900};
} // anonymous namespace
TEST_CASE("UdpMessenger | BroadcastsStateOnConstruction", "[UdpMessenger]")
{
::ableton::test::serial_io::Fixture io;
auto context = io.makeIoContext();
const auto state = TestNodeState{3, 10};
auto iface = test::Interface{};
auto messenger =
makeUdpMessenger(util::injectRef(iface), state, util::injectRef(context), 1, 1);
REQUIRE(1 == iface.sentMessages.size());
const auto messageBuffer = iface.sentMessages[0].first;
const auto sentTo = iface.sentMessages[0].second;
const auto result = v1::parseMessageHeader(
begin(messageBuffer), end(messageBuffer));
// Expect an Alive header
CHECK(v1::kAlive == result.first.messageType);
CHECK(state.nodeId == result.first.ident);
CHECK(1 == result.first.ttl);
// Sent to the multicast endpoint
CHECK(multicastEndpoint() == sentTo);
// And the payload should parse to equal to the original state
const auto actualState =
TestNodeState::fromPayload(state.nodeId, result.second, end(messageBuffer));
CHECK(state.fooVal == actualState.fooVal);
}
TEST_CASE("UdpMessenger | Heartbeat", "[UdpMessenger]")
{
::ableton::test::serial_io::Fixture io;
const auto state = TestNodeState{3, 10};
auto iface = test::Interface{};
auto messenger = makeUdpMessenger(
util::injectRef(iface), state, util::injectVal(io.makeIoContext()), 4, 2);
REQUIRE(1 == iface.sentMessages.size());
// At two seconds the messenger should have broadcasted its state again
io.advanceTime(std::chrono::seconds(3));
CHECK(2 == iface.sentMessages.size());
}
TEST_CASE("UdpMessenger | Response", "[UdpMessenger]")
{
::ableton::test::serial_io::Fixture io;
const auto state = TestNodeState{3, 10};
auto iface = test::Interface{};
auto messenger = makeUdpMessenger(
util::injectRef(iface), state, util::injectVal(io.makeIoContext()), 1, 1);
// Simulate state broadcast from peer, leaving out details like payload
v1::MessageBuffer buffer;
const auto messageEnd =
v1::aliveMessage(peerState.ident(), 0, makePayload(), begin(buffer));
iface.incomingMessage(peerEndpoint, begin(buffer), messageEnd);
// The messenger should have responded to the alive message with its
// current state
REQUIRE(2 == iface.sentMessages.size());
const auto messageBuffer = iface.sentMessages[1].first;
const auto sentTo = iface.sentMessages[1].second;
const auto result = v1::parseMessageHeader(
begin(messageBuffer), end(messageBuffer));
CHECK(v1::kResponse == result.first.messageType);
CHECK(state.nodeId == result.first.ident);
CHECK(1 == result.first.ttl);
CHECK(peerEndpoint == sentTo);
}
namespace
{
struct TestHandler
{
void operator()(PeerState state)
{
peerStates.push_back(std::move(state));
}
void operator()(ByeBye byeBye)
{
byeByes.push_back(std::move(byeBye));
}
std::vector> peerStates;
std::vector> byeByes;
};
} // namespace
TEST_CASE("UdpMessenger | Receive", "[UdpMessenger]")
{
::ableton::test::serial_io::Fixture io;
auto iface = test::Interface{};
auto tmpMessenger = makeUdpMessenger(
util::injectRef(iface), TestNodeState{}, util::injectVal(io.makeIoContext()), 1, 1);
auto messenger = std::move(tmpMessenger);
auto handler = TestHandler{};
messenger.receive(std::ref(handler));
v1::MessageBuffer buffer;
// Receive an alive message
auto end = v1::aliveMessage(peerState.nodeId, 3, toPayload(peerState), begin(buffer));
iface.incomingMessage(peerEndpoint, begin(buffer), end);
// And a bye bye message
end = v1::byeByeMessage(peerState.nodeId, begin(buffer));
messenger.receive(std::ref(handler));
iface.incomingMessage(peerEndpoint, begin(buffer), end);
REQUIRE(1 == handler.peerStates.size());
CHECK(3 == handler.peerStates[0].ttl);
CHECK(peerState.nodeId == handler.peerStates[0].peerState.nodeId);
CHECK(peerState.fooVal == handler.peerStates[0].peerState.fooVal);
REQUIRE(1 == handler.byeByes.size());
CHECK(peerState.nodeId == handler.byeByes[0].peerId);
}
TEST_CASE("UdpMessenger | SendByeByeOnDestruction", "[UdpMessenger]")
{
::ableton::test::serial_io::Fixture io;
auto iface = test::Interface{};
{
auto messenger = makeUdpMessenger(util::injectRef(iface), TestNodeState{5, 10},
util::injectVal(io.makeIoContext()), 1, 1);
}
REQUIRE(2 == iface.sentMessages.size());
const auto messageBuffer = iface.sentMessages[1].first;
const auto sentTo = iface.sentMessages[1].second;
const auto result = v1::parseMessageHeader(
begin(messageBuffer), end(messageBuffer));
CHECK(v1::kByeBye == result.first.messageType);
CHECK(multicastEndpoint() == sentTo);
}
namespace
{
template
struct MessengerWrapper
{
MessengerWrapper(Messenger messenger)
: mMessenger(std::move(messenger))
{
}
Messenger mMessenger;
};
template
MessengerWrapper wrapMessenger(Messenger messenger)
{
return {std::move(messenger)};
}
} // namespace
TEST_CASE("UdpMessenger | MovingMessengerDoesntSendByeBye", "[UdpMessenger]")
{
::ableton::test::serial_io::Fixture io;
// The destructor for the messenger is written so that if an
// instance is moved from, it won't send the bye bye message.
auto iface = test::Interface{};
{
auto messenger = makeUdpMessenger(util::injectRef(iface), TestNodeState{5, 10},
util::injectVal(io.makeIoContext()), 1, 1);
auto wrapper = wrapMessenger(std::move(messenger));
}
// We should have an initial Alive and then a single ByeBye
CHECK(2 == iface.sentMessages.size());
}
} // namespace discovery
} // namespace ableton
link-Link-3.0.2/src/CMakeLists.txt 0000644 0001750 0001750 00000006321 13303226525 017130 0 ustar zmoelnig zmoelnig cmake_minimum_required(VERSION 3.0)
project(LinkTest)
# ____
# / ___| ___ _ _ _ __ ___ ___ ___
# \___ \ / _ \| | | | '__/ __/ _ \/ __|
# ___) | (_) | |_| | | | (_| __/\__ \
# |____/ \___/ \__,_|_| \___\___||___/
#
set(link_discovery_test_SOURCES
ableton/discovery/tst_InterfaceScanner.cpp
ableton/discovery/tst_Payload.cpp
ableton/discovery/tst_PeerGateway.cpp
ableton/discovery/tst_PeerGateways.cpp
ableton/discovery/tst_UdpMessenger.cpp
ableton/discovery/v1/tst_Messages.cpp
)
set(link_core_test_SOURCES
ableton/link/tst_Beats.cpp
ableton/link/tst_CircularFifo.cpp
ableton/link/tst_ClientSessionTimelines.cpp
ableton/link/tst_Controller.cpp
ableton/link/tst_HostTimeFilter.cpp
ableton/link/tst_Kalman.cpp
ableton/link/tst_LinearRegression.cpp
ableton/link/tst_Measurement.cpp
ableton/link/tst_Peers.cpp
ableton/link/tst_Phase.cpp
ableton/link/tst_PingResponder.cpp
ableton/link/tst_StartStopState.cpp
ableton/link/tst_Tempo.cpp
ableton/link/tst_Timeline.cpp
)
set(link_test_SOURCES
ableton/test/catch/CatchMain.cpp
ableton/test/serial_io/SchedulerTree.cpp
)
# ____ ____
# / ___| ___ _ _ _ __ ___ ___ / ___|_ __ ___ _ _ _ __ ___
# \___ \ / _ \| | | | '__/ __/ _ \ | | _| '__/ _ \| | | | '_ \/ __|
# ___) | (_) | |_| | | | (_| __/ | |_| | | | (_) | |_| | |_) \__ \
# |____/ \___/ \__,_|_| \___\___| \____|_| \___/ \__,_| .__/|___/
# |_|
source_group("Link" FILES
${link_core_HEADERS}
)
source_group("Discovery" FILES
${link_discovery_HEADERS}
)
source_group("Platform" FILES
${link_platform_HEADERS}
)
source_group("Util" FILES
${link_util_HEADERS}
)
source_group("Test Utils" FILES
${link_test_HEADERS}
${link_test_SOURCES}
)
# _____ _
# |_ _|_ _ _ __ __ _ ___| |_ ___
# | |/ _` | '__/ _` |/ _ \ __|/ __|
# | | (_| | | | (_| | __/ |_ \__ \
# |_|\__,_|_| \__, |\___|\__||___/
# |___/
function(configure_link_test_executable target)
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries(${target} atomic pthread)
endif()
target_link_libraries(${target} Catch::Catch Ableton::Link)
endfunction()
# For the LinkCore test suite, we add header dependencies individually so that
# the source groups above are shown correctly to the project. However, most
# other projects integrating Link won't need this functionality, and they should
# should just depend on ${link_HEADERS}.
add_executable(LinkCoreTest
${link_core_HEADERS}
${link_discovery_HEADERS}
${link_platform_HEADERS}
${link_util_HEADERS}
${link_test_HEADERS}
${link_core_test_SOURCES}
${link_test_SOURCES}
)
configure_link_test_executable(LinkCoreTest)
# For the LinkDiscovery test suite, we only add dependencies on the headers
# necessary to compile these tests, since the Discovery feature should not have
# dependencies on the Link core code. Normal targets should always use
# ${link_HEADERS} for their dependencies.
add_executable(LinkDiscoveryTest
${link_discovery_HEADERS}
${link_platform_HEADERS}
${link_util_HEADERS}
${link_test_HEADERS}
${link_discovery_test_SOURCES}
${link_test_SOURCES}
)
configure_link_test_executable(LinkDiscoveryTest)
link-Link-3.0.2/examples/ 0000755 0001750 0001750 00000000000 13303226525 015415 5 ustar zmoelnig zmoelnig link-Link-3.0.2/examples/linkaudio/ 0000755 0001750 0001750 00000000000 13303226525 017374 5 ustar zmoelnig zmoelnig link-Link-3.0.2/examples/linkaudio/AudioPlatform_Jack.cpp 0000644 0001750 0001750 00000012155 13303226525 023602 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include "AudioPlatform_Jack.hpp"
#include
#include
#include
namespace ableton
{
namespace linkaudio
{
AudioPlatform::AudioPlatform(Link& link)
: mEngine(link)
, mSampleTime(0.)
, mpJackClient(NULL)
, mpJackPorts(NULL)
{
initialize();
start();
}
AudioPlatform::~AudioPlatform()
{
stop();
uninitialize();
}
int AudioPlatform::audioCallback(jack_nframes_t nframes, void* pvUserData)
{
AudioPlatform* pAudioPlatform = static_cast(pvUserData);
return pAudioPlatform->audioCallback(nframes);
}
int AudioPlatform::audioCallback(jack_nframes_t nframes)
{
using namespace std::chrono;
AudioEngine& engine = mEngine;
const auto hostTime = mHostTimeFilter.sampleTimeToHostTime(mSampleTime);
mSampleTime += nframes;
const auto bufferBeginAtOutput = hostTime + engine.mOutputLatency;
engine.audioCallback(bufferBeginAtOutput, nframes);
for (int k = 0; k < 2; ++k)
{
float* buffer = static_cast(jack_port_get_buffer(mpJackPorts[k], nframes));
for (unsigned long i = 0; i < nframes; ++i)
buffer[i] = static_cast(engine.mBuffer[i]);
}
return 0;
}
void AudioPlatform::initialize()
{
jack_status_t status = JackFailure;
mpJackClient = jack_client_open("LinkHut", JackNullOption, &status);
if (mpJackClient == NULL)
{
std::cerr << "Could not initialize Audio Engine. ";
std::cerr << "JACK: " << std::endl;
if (status & JackFailure)
std::cerr << "Overall operation failed." << std::endl;
if (status & JackInvalidOption)
std::cerr << "Invalid or unsupported option." << std::endl;
if (status & JackNameNotUnique)
std::cerr << "Client name not unique." << std::endl;
if (status & JackServerStarted)
std::cerr << "Server is started." << std::endl;
if (status & JackServerFailed)
std::cerr << "Unable to connect to server." << std::endl;
if (status & JackServerError)
std::cerr << "Server communication error." << std::endl;
if (status & JackNoSuchClient)
std::cerr << "Client does not exist." << std::endl;
if (status & JackLoadFailure)
std::cerr << "Unable to load internal client." << std::endl;
if (status & JackInitFailure)
std::cerr << "Unable to initialize client." << std::endl;
if (status & JackShmFailure)
std::cerr << "Unable to access shared memory." << std::endl;
if (status & JackVersionError)
std::cerr << "Client protocol version mismatch." << std::endl;
std::cerr << std::endl;
std::terminate();
};
mpJackPorts = new jack_port_t*[2];
for (int k = 0; k < 2; ++k)
{
const std::string port_name = "out_" + std::to_string(k + 1);
mpJackPorts[k] = jack_port_register(
mpJackClient, port_name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (mpJackPorts[k] == NULL)
{
std::cerr << "Could not get Audio Device. " << std::endl;
jack_client_close(mpJackClient);
std::terminate();
}
}
jack_set_process_callback(mpJackClient, AudioPlatform::audioCallback, this);
const double bufferSize = jack_get_buffer_size(mpJackClient);
const double sampleRate = jack_get_sample_rate(mpJackClient);
mEngine.setBufferSize(static_cast(bufferSize));
mEngine.setSampleRate(sampleRate);
mEngine.mOutputLatency =
std::chrono::microseconds(llround(1.0e6 * bufferSize / sampleRate));
}
void AudioPlatform::uninitialize()
{
for (int k = 0; k < 2; ++k)
{
jack_port_unregister(mpJackClient, mpJackPorts[k]);
mpJackPorts[k] = NULL;
}
delete[] mpJackPorts;
mpJackPorts = NULL;
jack_client_close(mpJackClient);
mpJackClient = NULL;
}
void AudioPlatform::start()
{
jack_activate(mpJackClient);
const char** playback_ports = jack_get_ports(
mpJackClient, 0, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsPhysical);
if (playback_ports)
{
const std::string client_name = jack_get_client_name(mpJackClient);
for (int k = 0; k < 2; ++k)
{
const std::string port_name = "out_" + std::to_string(k + 1);
const std::string client_port = client_name + ':' + port_name;
jack_connect(mpJackClient, client_port.c_str(), playback_ports[k]);
}
jack_free(playback_ports);
}
}
void AudioPlatform::stop()
{
jack_deactivate(mpJackClient);
}
} // namespace linkaudio
} // namespace ableton
link-Link-3.0.2/examples/linkaudio/AudioPlatform_CoreAudio.hpp 0000644 0001750 0001750 00000002625 13303226525 024612 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#pragma once
#include "AudioEngine.hpp"
#include
namespace ableton
{
namespace linkaudio
{
class AudioPlatform
{
public:
AudioPlatform(Link& link);
~AudioPlatform();
AudioEngine mEngine;
private:
static OSStatus audioCallback(void* inRefCon,
AudioUnitRenderActionFlags*,
const AudioTimeStamp* inTimeStamp,
UInt32,
UInt32 inNumberFrames,
AudioBufferList* ioData);
void initialize();
void uninitialize();
void start();
void stop();
AudioUnit mIoUnit;
};
} // namespace linkaudio
} // namespace ableton
link-Link-3.0.2/examples/linkaudio/AudioPlatform_Wasapi.cpp 0000644 0001750 0001750 00000022275 13303226525 024162 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include "AudioPlatform_Wasapi.hpp"
#include
#include
// WARNING: This file provides an audio driver for Windows using WASAPI. This driver is
// considered experimental and has problems with low-latency playback. Please consider
// using the ASIO driver instead.
namespace ableton
{
namespace linkaudio
{
// GUID identifiers used to when looking up COM enumerators and devices
static const IID kMMDeviceEnumeratorId = __uuidof(MMDeviceEnumerator);
static const IID kIMMDeviceEnumeratorId = __uuidof(IMMDeviceEnumerator);
static const IID kAudioClientId = __uuidof(IAudioClient);
static const IID kAudioRenderClientId = __uuidof(IAudioRenderClient);
// Controls how large the driver's ring buffer will be, expressed in terms of
// 100-nanosecond units. This value also influences the overall driver latency.
static const REFERENCE_TIME kBufferDuration = 1000000;
// How long to block the runloop while waiting for an event callback.
static const DWORD kWaitTimeoutInMs = 2000;
void fatalError(HRESULT result, LPCTSTR context)
{
if (result > 0)
{
_com_error error(result);
LPCTSTR errorMessage = error.ErrorMessage();
std::cerr << context << ": " << errorMessage << std::endl;
}
else
{
std::cerr << context << std::endl;
}
std::terminate();
}
DWORD renderAudioRunloop(LPVOID lpParam)
{
AudioPlatform* platform = static_cast(lpParam);
return platform->audioRunloop();
}
AudioPlatform::AudioPlatform(Link& link)
: mEngine(link)
, mDevice(nullptr)
, mAudioClient(nullptr)
, mRenderClient(nullptr)
, mStreamFormat(nullptr)
, mEventHandle(nullptr)
, mAudioThreadHandle(nullptr)
, mIsRunning(false)
, mSampleTime(0)
{
initialize();
mEngine.setBufferSize(bufferSize());
mEngine.setSampleRate(mStreamFormat->nSamplesPerSec);
start();
}
AudioPlatform::~AudioPlatform()
{
// WARNING: Here be dragons!
// The WASAPI driver is not thread-safe, and crashes may occur when shutting down due
// to these fields being concurrently accessed in the audio thread. Introducing a mutex
// in the audio thread is not an appropriate solution to fix this race condition; a more
// robust solution needs to be considered instead.
if (mDevice != nullptr)
{
mDevice->Release();
}
if (mAudioClient != nullptr)
{
mAudioClient->Release();
}
if (mRenderClient != nullptr)
{
mRenderClient->Release();
}
CoTaskMemFree(mStreamFormat);
}
UINT32 AudioPlatform::bufferSize()
{
UINT32 bufferSize;
HRESULT result = mAudioClient->GetBufferSize(&bufferSize);
if (FAILED(result))
{
fatalError(result, "Could not get buffer size");
return 0; // not reached
}
return bufferSize;
}
void AudioPlatform::initialize()
{
HRESULT result = CoInitialize(nullptr);
if (FAILED(result))
{
fatalError(result, "Could not initialize COM library");
}
IMMDeviceEnumerator* enumerator = nullptr;
result = CoCreateInstance(kMMDeviceEnumeratorId, nullptr, CLSCTX_ALL,
kIMMDeviceEnumeratorId, (void**)&enumerator);
if (FAILED(result))
{
fatalError(result, "Could not create device instance");
}
result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &(mDevice));
if (FAILED(result))
{
fatalError(result, "Could not get default audio endpoint");
}
else
{
enumerator->Release();
enumerator = nullptr;
}
result =
mDevice->Activate(kAudioClientId, CLSCTX_ALL, nullptr, (void**)&(mAudioClient));
if (FAILED(result))
{
fatalError(result, "Could not activate audio device");
}
result = mAudioClient->GetMixFormat(&(mStreamFormat));
if (FAILED(result))
{
fatalError(result, "Could not get mix format");
}
if (mStreamFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
WAVEFORMATEXTENSIBLE* streamFormatEx =
reinterpret_cast(mStreamFormat);
if (streamFormatEx->SubFormat != KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
{
fatalError(0, "Sorry, only IEEE floating point streams are supported");
}
}
else
{
fatalError(0, "Sorry, only extensible wave streams are supported");
}
result = mAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, kBufferDuration, 0, mStreamFormat, nullptr);
if (FAILED(result))
{
fatalError(result, "Could not initialize audio device");
}
mEventHandle = CreateEvent(nullptr, false, false, nullptr);
if (mEventHandle == nullptr)
{
fatalError(result, "Could not create event handle");
}
result = mAudioClient->GetService(kAudioRenderClientId, (void**)&(mRenderClient));
if (FAILED(result))
{
fatalError(result, "Could not get audio render service");
}
mIsRunning = true;
LPTHREAD_START_ROUTINE threadEntryPoint =
reinterpret_cast(renderAudioRunloop);
mAudioThreadHandle = CreateThread(nullptr, 0, threadEntryPoint, this, 0, nullptr);
if (mAudioThreadHandle == nullptr)
{
fatalError(GetLastError(), "Could not create audio thread");
}
}
void AudioPlatform::start()
{
UINT32 bufSize = bufferSize();
BYTE* buffer;
HRESULT result = mRenderClient->GetBuffer(bufSize, &buffer);
if (FAILED(result))
{
fatalError(result, "Could not get render client buffer (in start audio engine)");
}
result = mRenderClient->ReleaseBuffer(bufSize, 0);
if (FAILED(result))
{
fatalError(result, "Could not release buffer");
}
result = mAudioClient->SetEventHandle(mEventHandle);
if (FAILED(result))
{
fatalError(result, "Could not set event handle to audio client");
}
REFERENCE_TIME latency;
result = mAudioClient->GetStreamLatency(&latency);
if (FAILED(result))
{
fatalError(result, "Could not get stream latency");
}
result = mAudioClient->Start();
if (FAILED(result))
{
fatalError(result, "Could not start audio client");
}
}
DWORD AudioPlatform::audioRunloop()
{
while (mIsRunning)
{
DWORD wait = WaitForSingleObject(mEventHandle, kWaitTimeoutInMs);
if (wait != WAIT_OBJECT_0)
{
mIsRunning = false;
mAudioClient->Stop();
return wait;
}
// Get the amount of padding, which basically is the amount of data in the driver's
// ring buffer that is filled with unread data. Thus, subtracting this amount from
// the buffer size gives the effective buffer size, which is the amount of frames
// that can be safely written to the driver.
UINT32 paddingFrames;
HRESULT result = mAudioClient->GetCurrentPadding(&paddingFrames);
if (FAILED(result))
{
fatalError(result, "Could not get number of padding frames");
}
const UINT32 numSamples = bufferSize() - paddingFrames;
BYTE* buffer;
result = mRenderClient->GetBuffer(numSamples, &buffer);
if (FAILED(result))
{
fatalError(result, "Could not get render client buffer (in callback)");
}
const double sampleRate = static_cast(mStreamFormat->nSamplesPerSec);
using namespace std::chrono;
const auto bufferDuration =
duration_cast(duration{numSamples / sampleRate});
const auto hostTime = mHostTimeFilter.sampleTimeToHostTime(mSampleTime);
mSampleTime += numSamples;
const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency;
mEngine.audioCallback(bufferBeginAtOutput, numSamples);
float* floatBuffer = reinterpret_cast(buffer);
for (WORD i = 0; i < numSamples; ++i)
{
if (i >= mEngine.mBuffer.size())
{
break;
}
for (WORD j = 0; j < mStreamFormat->nChannels; ++j)
{
floatBuffer[j + (i * mStreamFormat->nChannels)] =
static_cast(mEngine.mBuffer[i]);
}
}
// Write the buffer to the audio driver and subsequently free the buffer memory
result = mRenderClient->ReleaseBuffer(numSamples, 0);
if (FAILED(result))
{
fatalError(result, "Error rendering data");
}
} // end of runloop
mIsRunning = false;
return 0;
}
// void fillBuffer(MetronomeSynth& metronome,
// const UINT32 startFrame,
// const UINT32 numSamples,
// const UINT32 numChannels,
// BYTE* buffer)
//{
// float* floatBuffer = reinterpret_cast(buffer);
// UINT32 frame = startFrame;
// while (frame < numSamples * numChannels)
// {
// const float sample = static_cast(metronome.getSample());
// for (UINT32 channel = 0; channel < numChannels; ++channel)
// {
// floatBuffer[frame++] = sample;
// }
// }
//}
} // namespace linkaudio
} // namespace ableton
link-Link-3.0.2/examples/linkaudio/AudioPlatform.hpp 0000644 0001750 0001750 00000002557 13303226525 022664 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#pragma once
#if defined(LINKHUT_AUDIO_PLATFORM_ASIO)
#include "AudioPlatform_Asio.hpp"
#endif
#if defined(LINKHUT_AUDIO_PLATFORM_COREAUDIO)
#include "AudioPlatform_CoreAudio.hpp"
#endif
#if defined(LINKHUT_AUDIO_PLATFORM_DUMMY)
#include "AudioPlatform_Dummy.hpp"
#endif
#if defined(LINKHUT_AUDIO_PLATFORM_JACK)
#include "AudioPlatform_Jack.hpp"
#endif
#if defined(LINKHUT_AUDIO_PLATFORM_PORTAUDIO)
#include "AudioPlatform_Portaudio.hpp"
#endif
#if defined(LINKHUT_AUDIO_PLATFORM_WASAPI)
#include "AudioPlatform_Wasapi.hpp"
#endif
link-Link-3.0.2/examples/linkaudio/AudioPlatform_Portaudio.hpp 0000644 0001750 0001750 00000003106 13303226525 024701 0 ustar zmoelnig zmoelnig /* Copyright 2016, Ableton AG, Berlin. All rights reserved.
*
* 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, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#pragma once
#include "AudioEngine.hpp"
#include