summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIOhannes m zmölnig <zmoelnig@umlautS.umlaeute.mur.at>2021-01-02 20:46:28 +0100
committerIOhannes m zmölnig <zmoelnig@umlautS.umlaeute.mur.at>2021-01-02 20:46:28 +0100
commit8aedadd869752a59c32d6c69a346aaeab52fe8fa (patch)
treea6bb64abdb36de09d24395e96a28f54d41a533d8
parent25458a95d45b2ce741b76cb4d9d8de1a91f2dce6 (diff)
New upstream version 3.0.3+dfsg
-rw-r--r--.appveyor.yml40
-rw-r--r--.travis.yml97
-rw-r--r--AbletonLinkConfig.cmake4
-rw-r--r--CMakeLists.txt1
-rw-r--r--CONTRIBUTING.md36
-rw-r--r--README.md28
-rw-r--r--TEST-PLAN.md88
-rwxr-xr-xci/configure.py8
-rw-r--r--cmake_include/ConfigureCompileFlags.cmake4
-rw-r--r--examples/CMakeLists.txt98
-rw-r--r--examples/esp32/CMakeLists.txt6
-rw-r--r--examples/esp32/README.md12
-rw-r--r--examples/esp32/main/CMakeLists.txt11
-rw-r--r--examples/esp32/main/main.cpp108
-rw-r--r--examples/esp32/sdkconfig.defaults1
-rw-r--r--examples/linkaudio/AudioEngine.cpp9
-rw-r--r--examples/linkaudio/AudioEngine.hpp3
-rw-r--r--examples/linkaudio/AudioPlatform_Asio.cpp11
-rw-r--r--examples/linkaudio/AudioPlatform_CoreAudio.cpp4
-rw-r--r--examples/linkaudio/AudioPlatform_Dummy.hpp73
-rw-r--r--examples/linkaudio/AudioPlatform_Jack.cpp51
-rw-r--r--examples/linkaudio/AudioPlatform_Jack.hpp2
-rw-r--r--examples/linkaudio/AudioPlatform_Portaudio.cpp6
-rw-r--r--examples/linkaudio/AudioPlatform_Wasapi.cpp4
-rw-r--r--examples/linkhut/main.cpp31
-rw-r--r--examples/qlinkhut/Controller.cpp110
-rw-r--r--examples/qlinkhut/Controller.hpp78
-rw-r--r--examples/qlinkhut/main.qml351
-rw-r--r--examples/qlinkhut/resources.qrc6
-rw-r--r--include/CMakeLists.txt17
-rw-r--r--include/ableton/Link.hpp74
-rw-r--r--include/ableton/Link.ipp107
-rw-r--r--include/ableton/discovery/InterfaceScanner.hpp3
-rw-r--r--include/ableton/discovery/IpV4Interface.hpp4
-rw-r--r--include/ableton/discovery/NetworkByteStreamSerializable.hpp22
-rw-r--r--include/ableton/discovery/Payload.hpp7
-rw-r--r--include/ableton/discovery/PeerGateway.hpp7
-rw-r--r--include/ableton/discovery/PeerGateways.hpp2
-rw-r--r--include/ableton/discovery/Socket.hpp139
-rw-r--r--include/ableton/discovery/UdpMessenger.hpp2
-rw-r--r--include/ableton/discovery/v1/Messages.hpp6
-rw-r--r--include/ableton/link/Beats.hpp44
-rw-r--r--include/ableton/link/CircularFifo.hpp4
-rw-r--r--include/ableton/link/Controller.hpp87
-rw-r--r--include/ableton/link/Gateway.hpp5
-rw-r--r--include/ableton/link/GhostXForm.hpp6
-rw-r--r--include/ableton/link/HostTimeFilter.hpp4
-rw-r--r--include/ableton/link/Kalman.hpp8
-rw-r--r--include/ableton/link/Measurement.hpp99
-rw-r--r--include/ableton/link/MeasurementEndpointV4.hpp10
-rw-r--r--include/ableton/link/MeasurementService.hpp59
-rw-r--r--include/ableton/link/NodeId.hpp16
-rw-r--r--include/ableton/link/NodeState.hpp10
-rw-r--r--include/ableton/link/PayloadEntries.hpp12
-rw-r--r--include/ableton/link/PeerState.hpp6
-rw-r--r--include/ableton/link/Peers.hpp10
-rw-r--r--include/ableton/link/PingResponder.hpp43
-rw-r--r--include/ableton/link/SessionId.hpp4
-rw-r--r--include/ableton/link/SessionState.hpp7
-rw-r--r--include/ableton/link/Sessions.hpp22
-rw-r--r--include/ableton/link/StartStopState.hpp33
-rw-r--r--include/ableton/link/Tempo.hpp49
-rw-r--r--include/ableton/link/Timeline.hpp6
-rw-r--r--include/ableton/link/v1/Messages.hpp2
-rw-r--r--include/ableton/platforms/Config.hpp22
-rw-r--r--include/ableton/platforms/asio/AsioService.hpp105
-rw-r--r--include/ableton/platforms/asio/AsioWrapper.hpp8
-rw-r--r--include/ableton/platforms/asio/Context.hpp13
-rw-r--r--include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp5
-rw-r--r--include/ableton/platforms/esp32/Clock.hpp36
-rw-r--r--include/ableton/platforms/esp32/Context.hpp208
-rw-r--r--include/ableton/platforms/esp32/Esp32.hpp27
-rw-r--r--include/ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp94
-rw-r--r--include/ableton/platforms/esp32/Random.hpp36
-rw-r--r--include/ableton/platforms/esp32/ScanIpIfAddrs.hpp51
-rw-r--r--include/ableton/platforms/stl/Random.hpp (renamed from examples/qlinkhut/main.cpp)46
-rw-r--r--include/ableton/platforms/windows/Clock.hpp6
-rw-r--r--include/ableton/platforms/windows/Windows.hpp (renamed from examples/qlinkhut/BeatTile.qml)26
-rw-r--r--include/ableton/test/serial_io/Context.hpp4
-rw-r--r--include/ableton/test/serial_io/SchedulerTree.hpp2
-rw-r--r--include/ableton/test/serial_io/Socket.hpp83
-rw-r--r--src/ableton/link/tst_Controller.cpp36
-rw-r--r--src/ableton/link/tst_Measurement.cpp68
-rw-r--r--src/ableton/link/tst_Peers.cpp19
-rw-r--r--src/ableton/link/tst_PingResponder.cpp58
85 files changed, 1578 insertions, 1592 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
index 16f14a3..8ae74ff 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -8,6 +8,7 @@ branches:
image:
- Visual Studio 2015
- Visual Studio 2017
+ - Visual Studio 2019
environment:
matrix:
@@ -15,55 +16,45 @@ environment:
- 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
+ # Visual Studio 2019, 64-bit Release, Asio driver
+ - AUDIO_DRIVER: Asio
+ CONFIGURATION: Release
+ GENERATOR: Visual Studio 16 2019
matrix:
exclude:
- image: Visual Studio 2015
GENERATOR: Visual Studio 15 2017 Win64
+ - image: Visual Studio 2015
+ GENERATOR: Visual Studio 16 2019
- image: Visual Studio 2017
GENERATOR: Visual Studio 12 2013
- image: Visual Studio 2017
@@ -72,13 +63,26 @@ matrix:
GENERATOR: Visual Studio 14 2015
- image: Visual Studio 2017
GENERATOR: Visual Studio 14 2015 Win64
+ - image: Visual Studio 2017
+ GENERATOR: Visual Studio 16 2019
+ - image: Visual Studio 2019
+ GENERATOR: Visual Studio 12 2013
+ - image: Visual Studio 2019
+ GENERATOR: Visual Studio 12 2013 Win64
+ - image: Visual Studio 2019
+ GENERATOR: Visual Studio 14 2015
+ - image: Visual Studio 2019
+ GENERATOR: Visual Studio 14 2015 Win64
+ - image: Visual Studio 2019
+ GENERATOR: Visual Studio 15 2017
+ - image: Visual Studio 2019
+ GENERATOR: Visual Studio 15 2017 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/configure.py -a %AUDIO_DRIVER% -g "%GENERATOR%"
- python ci/build.py --configuration %CONFIGURATION%
test_script:
diff --git a/.travis.yml b/.travis.yml
index 87550d4..bfb93cc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,6 @@
language: cpp
sudo: required
-dist: trusty
branches:
only:
- master
@@ -38,6 +37,7 @@ matrix:
# Linux with Clang 3.6, 64-bit Debug, Alsa
- os: linux
+ dist: trusty
compiler: clang
addons:
apt:
@@ -45,38 +45,29 @@ matrix:
packages: ['clang-3.6', 'g++-5', 'valgrind', 'portaudio19-dev']
env: COMPILER=clang++-3.6 WORDSIZE=64 CONFIGURATION=Debug AUDIO=Alsa
- # Linux with Clang 3.6, 64-bit Release, Jack
+ # Linux with Clang 3.8, 64-bit Release, Jack
- os: linux
+ dist: trusty
compiler: clang
addons:
apt:
- sources: ['llvm-toolchain-precise-3.8', 'ubuntu-toolchain-r-test']
+ sources: ['ubuntu-toolchain-r-test']
packages: ['clang-3.8', 'g++-5', 'valgrind', 'portaudio19-dev']
env: COMPILER=clang++-3.8 WORDSIZE=64 CONFIGURATION=Release AUDIO=Jack
# Linux with Clang 5.0, 64-bit Debug, Alsa
- os: linux
+ dist: bionic
compiler: clang
addons:
apt:
- sources: ['llvm-toolchain-trusty-5.0', 'ubuntu-toolchain-r-test']
+ sources: ['ubuntu-toolchain-r-test']
packages: ['clang-5.0', 'g++-5', 'valgrind', 'portaudio19-dev']
env: COMPILER=clang++-5.0 WORDSIZE=64 CONFIGURATION=Debug AUDIO=Alsa
- # Linux with GCC 5.x, 32-bit Release
-# - os: linux
-# compiler: gcc
-# addons:
-# apt:
-# sources: ['ubuntu-toolchain-r-test']
-# packages: ['g++-5', 'g++-5-multilib', 'linux-libc-dev:i386', 'valgrind:i386',
-# 'libxext-dev:i386', 'libglapi-mesa:i386',
-# 'libgl1-mesa-glx:i386', 'libgl1-mesa-dev:i386',
-# 'portaudio19-dev:i386', 'libglib2.0-0:i386']
-# env: COMPILER=g++-5 WORDSIZE=32 CONFIGURATION=Release
-
# Linux with GCC 5.x, 64-bit Release, Alsa
- os: linux
+ dist: bionic
compiler: gcc
addons:
apt:
@@ -86,6 +77,7 @@ matrix:
# Linux with GCC 6.x, 64-bit Release, Alsa
- os: linux
+ dist: bionic
compiler: gcc
addons:
apt:
@@ -93,8 +85,19 @@ matrix:
packages: ['g++-6', 'valgrind', 'portaudio19-dev']
env: COMPILER=g++-6 WORDSIZE=64 CONFIGURATION=Release AUDIO=Alsa
+ # ESP32
+ - os: linux
+ dist: bionic
+ compiler: clang
+ addons:
+ apt:
+ sources: ['llvm-toolchain-trusty-5.0', 'ubuntu-toolchain-r-test']
+ packages: ['wget', 'flex', 'bison', 'gperf', 'python-pip', 'ninja-build', 'ccache']
+ env: IDF_RELEASE=v4.0-rc IDF_PATH=${HOME}/esp-idf-${IDF_RELEASE} COMPILER=clang
+
# Code formatting verification
- os: linux
+ dist: bionic
compiler: clang
env: CONFIGURATION=Formatting LLVM_VERSION=6.0
services:
@@ -112,40 +115,38 @@ before_install:
# Override Travis' CXX Flag
- CXX=$COMPILER
- # Install homebrew packages for Mac OS X
- - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update && brew install ninja qt5; fi
-
- # Install QT for Linux
- - |
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
- sudo apt-add-repository -y ppa:beineri/opt-qt-5.10.1-trusty
- sudo apt-get -qy update
- sudo apt-get -qy install qtdeclarative5-dev
- fi
-
install:
- git submodule update --init --recursive
script:
- |
set -e
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then
- PATH=`brew --prefix qt5`/bin:${PATH}
- if [ "$WORDSIZE" -eq 64 ]; then
- python ci/configure.py --configuration $CONFIGURATION --generator Ninja --with-qt
- else
- python ci/configure.py --configuration $CONFIGURATION --generator Ninja --flags "\-DCMAKE_OSX_ARCHITECTURES=i386"
- fi
- python ci/build.py --configuration $CONFIGURATION --arguments "all -v"
+ if [ ! -z "$IDF_PATH" ]; then
+ mkdir -p ${IDF_PATH}
+ wget -P ${HOME} https://github.com/espressif/esp-idf/releases/download/${IDF_RELEASE}/esp-idf-${IDF_RELEASE}.zip
+ unzip -o ${HOME}/esp-idf-${IDF_RELEASE}.zip -d ${HOME}
+ ${IDF_PATH}/install.sh
+ . ${IDF_PATH}/export.sh
+ . ${IDF_PATH}/add_path.sh
+ PATH=${IDF_PATH}/tools:${PATH}
+ cd examples/esp32
+ idf.py build
else
- if [ "$WORDSIZE" -eq 64 ]; then
- PATH=$PWD/5.5/gcc_64:${PATH}
- python ci/configure.py --configuration $CONFIGURATION --with-qt -a $AUDIO
+ if [ "$TRAVIS_OS_NAME" = "osx" ]; then
+ if [ "$WORDSIZE" -eq 64 ]; then
+ python ci/configure.py --configuration $CONFIGURATION
+ else
+ python ci/configure.py --configuration $CONFIGURATION --flags "\-DCMAKE_OSX_ARCHITECTURES=i386"
+ fi
+ python ci/build.py --configuration $CONFIGURATION
else
- PATH=$PWD/5.5/gcc:${PATH}
- python ci/configure.py --configuration $CONFIGURATION -a $AUDIO --flags "\-DCMAKE_CXX_FLAGS=\"\-m32\""
+ if [ "$WORDSIZE" -eq 64 ]; then
+ python ci/configure.py --configuration $CONFIGURATION -a $AUDIO
+ else
+ python ci/configure.py --configuration $CONFIGURATION -a $AUDIO --flags "\-DCMAKE_CXX_FLAGS=\"\-m32\""
+ fi
+ python ci/build.py --configuration $CONFIGURATION
fi
- python ci/build.py --configuration $CONFIGURATION --arguments "VERBOSE=1 -j8"
fi
set +e
@@ -154,11 +155,13 @@ script:
# compiled by brew and this takes way too much time on the build server.
- |
set -e
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
- python ci/run-tests.py --target LinkCoreTest --valgrind
- python ci/run-tests.py --target LinkDiscoveryTest --valgrind
- else
- python ci/run-tests.py --target LinkCoreTest
- python ci/run-tests.py --target LinkDiscoveryTest
+ if [ -z "$IDF_PATH" ]; then
+ if [ "$TRAVIS_OS_NAME" = "linux" ]; then
+ python ci/run-tests.py --target LinkCoreTest --valgrind
+ python ci/run-tests.py --target LinkDiscoveryTest --valgrind
+ else
+ python ci/run-tests.py --target LinkCoreTest
+ python ci/run-tests.py --target LinkDiscoveryTest
+ fi
fi
set +e
diff --git a/AbletonLinkConfig.cmake b/AbletonLinkConfig.cmake
index ce77a6d..c34d8e3 100644
--- a/AbletonLinkConfig.cmake
+++ b/AbletonLinkConfig.cmake
@@ -31,10 +31,6 @@ elseif(WIN32)
INTERFACE_COMPILE_DEFINITIONS
LINK_PLATFORM_WINDOWS=1
)
- set_property(TARGET Ableton::Link APPEND PROPERTY
- INTERFACE_COMPILE_OPTIONS
- "/wd4503" # 'Identifier': decorated name length exceeded, name was truncated
- )
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set_property(TARGET Ableton::Link APPEND PROPERTY
INTERFACE_COMPILE_DEFINITIONS
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d47b1c8..100fe2c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,7 +11,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# |_|
# Note: Please use the LINK_* prefix for all project-specific options
-option(LINK_BUILD_QT_EXAMPLES "Build examples (Requires Qt)" OFF)
if(UNIX)
option(LINK_ENABLE_ASAN "Build with Address Sanitizier (ASan)" OFF)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 53764dc..c4a0fd5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,18 +22,18 @@ the following criteria:
- You have signed and returned Ableton's [CLA][cla]
- The [tests pass](#testing)
- - The PR passes all [CI service checks](#ci-services)
+ - The PR passes all CI service checks
- The code is [well-formatted](#code-formatting)
- The git commit messages comply to [the commonly accepted standards][git-commit-msgs]
Testing
-------
-Link ships with unit tests that are run by our [CI services](#ci-services) for all PRs.
-There are two test suites: `LinkCoreTest`, which tests the core Link functionality, and
-`LinkDiscoverTest`, which tests the network discovery feature of Link. A third virtual
-target, `LinkAllTest` is provided by the CMake project as a convenience to run all tests
-at once.
+Link ships with unit tests that are run on [Travis CI][travis] and [AppVeyor][appveyor] for
+all PRs. There are two test suites: `LinkCoreTest`, which tests the core Link
+functionality, and `LinkDiscoverTest`, which tests the network discovery feature of Link.
+A third virtual target, `LinkAllTest` is provided by the CMake project as a convenience
+to run all tests at once.
The unit tests are run on every platform which Link is officially supported on, and also
are run through [Valgrind][valgrind] on Linux to check for memory corruption and leaks. If
@@ -42,31 +42,11 @@ valgrind detects any memory errors when running the tests, it will fail the buil
If you are submitting a PR which fixes a bug or introduces new functionality, please add a
test which helps to verify the correctness of the code in the PR.
-
-CI Services
------------
-
-Every PR submitted to Link must build and pass tests on the following platforms
-
-| Service | Platform | Compiler | Word size | Checks |
-| -------- | -------- | --------- | ----------| ------ |
-| AppVeyor | Windows | MSVC 2013 | 32/64-bit | Build (Release), run tests |
-| AppVeyor | Windows | MSVC 2015 | 32/64-bit | Build (Debug/Release), run tests |
-| Travis | Mac OS X | Xcode 7.1 | 64-bit | Build (Debug/Release), run tests |
-| Travis | Linux | Clang-3.6 | 64-bit | Build (Debug/Release), run tests with Valgrind |
-| Travis | Linux | GCC-5.2 | 32/64-bit | Build (Release), run tests with Valgrind |
-| Travis | Linux | Clang-3.8 | N/A | Check code formatting with clang-format |
-
-Assuming that your PR gets a green checkmark from each service, you're good to go. In case
-of a build failure, please examine the build logs from the respective CI service. If you
-have any questions, feel free to contact one of the maintainers -- we're here to help you!
-
-
Code Formatting
---------------
Link uses [clang-format][clang-format] to enforce our preferred code style. At the moment,
-we use **clang-format version 3.9**. Note that other versions may format code differently.
+we use **clang-format version 6.0**. Note that other versions may format code differently.
Any PRs submitted to Link are also checked with clang-format by the Travis CI service. If
you get a build failure, then you can format your code by running the following command:
@@ -79,3 +59,5 @@ clang-format -style=file -i (filename)
[git-commit-msgs]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[clang-format]: http://llvm.org/builds
[valgrind]: http://valgrind.org
+[travis]: http://travis-ci.com
+[appveyor]: http://appveyor.com
diff --git a/README.md b/README.md
index 396043d..9bc59ae 100644
--- a/README.md
+++ b/README.md
@@ -32,28 +32,8 @@ $ cmake ..
$ cmake --build .
```
-In order to build the GUI example application **QLinkHut**, the [Qt][qt] installation
-path must be set in the system PATH and `LINK_BUILD_QT_EXAMPLES` must be set:
-
-```
-$ mkdir build
-$ cd build
-$ cmake -DLINK_BUILD_QT_EXAMPLES=ON ..
-$ cmake --build .
-```
-
The output binaries for the example applications and the unit-tests will be placed in a
-`bin` subdirectory of the CMake binary directory. Also note that the word size of the Qt
-installation must match how Link has been configured. Look for the value of
-`LINK_WORD_SIZE` in the CMake output to verify that the word size matches Qt's.
-
-When running QLinkHut on Windows, the Qt binary path must be in the system `PATH` before
-launching the executable. So to launch QLinkHut from Visual Studio, one must go to the
-QLinkHut Properties -> Debugging -> Environment, and set it to:
-
-```
-PATH=$(Path);C:\path\to\Qt\5.5\msvc64_bin\bin
-```
+`bin` subdirectory of the CMake binary directory.
# Integrating Link in your Application
@@ -100,7 +80,6 @@ insight as to the compiler flags needed to build Link.
| Platform | Minimum Required | Optional (only required for examples) |
|----------|----------------------|---------------------------------------|
-| **All** | CMake 3.0 | Qt 5.5 |
| Windows | MSVC 2013 | Steinberg ASIO SDK 2.3 |
| Mac | Xcode 7.0 | |
| Linux | Clang 3.6 or GCC 5.2 | libportaudio19-dev |
@@ -115,8 +94,8 @@ information on the LinkKit SDK for iOS.
An overview of Link concepts can be found at http://ableton.github.io/link. Those that
are new to Link should start there. The [Link.hpp](include/ableton/Link.hpp) header
-contains the full Link public interface. See the LinkHut and QLinkHut projects in this
-repo for an example usage of the `Link` type.
+contains the full Link public interface. See the LinkHut projects in this repo for an
+example usage of the `Link` type.
## Time and Clocks
@@ -175,4 +154,3 @@ example apps.
[catch]: https://github.com/philsquared/Catch
[cmake]: https://www.cmake.org
[license]: LICENSE.md
-[qt]: https://www.qt.io
diff --git a/TEST-PLAN.md b/TEST-PLAN.md
index 39f82e4..3b00d61 100644
--- a/TEST-PLAN.md
+++ b/TEST-PLAN.md
@@ -6,59 +6,80 @@ behave consistently with respect to these test cases. ## Tempo Changes
### TEMPO-1: Tempo changes should be transmitted between connected apps.
-- Open LinkHut, press **Play**, when using QLinkHut click the **Link** button to enable
-Link. - Open App and **enable** Link. - Without starting to play, change tempo in App
-**&rArr;** LinkHut clicks should speed up or slow down to match the tempo specified in
-the App. - Start playing in the App **&rArr;** App and LinkHut should be in sync - Change
-tempo in App and in LinkHut **&rArr;** App and LinkHut should remain in sync
+- Open LinkHut, press **Play** and **enable** Link.
+- Open App and **enable** Link.
+- Without starting to play, change tempo in App **&rArr;** LinkHut clicks should speed up or slow down to match the tempo specified in
+the App.
+- Start playing in the App **&rArr;** App and LinkHut should be in sync
+- Change tempo in App and in LinkHut **&rArr;** App and LinkHut should remain in sync
### TEMPO-2: Opening an app with Link enabled should not change the tempo of an existing Link session.
-- Open App and **enable** Link. - Set App tempo to 100bpm. - Terminate App. - Open
-LinkHut, press **Play** and **enable** Link. - Set LinkHut tempo to 130bpm. - Open App
-and **enable** Link. **&rArr;** Link should be connected (“1 Link”) and the App and
+- Open App and **enable** Link.
+- Set App tempo to 100bpm.
+- Terminate App.
+- Open LinkHut, press **Play** and **enable** Link.
+- Set LinkHut tempo to 130bpm.
+- Open App and **enable** Link. **&rArr;** Link should be connected (“1 Link”) and the App and
LinkHut’s tempo should both be 130bpm.
### TEMPO-3: When connected, loading a new document should not change the Link session tempo.
-- Open LinkHut, press **Play**, when using QLinkHut click the **Link** button to enable
-Link. - Set LinkHut tempo to 130bpm. - Open App and **enable** Link **&rArr;** LinkHut’s
-tempo should not change. - Load new Song/Set/Session with a tempo other than 130bpm
-**&rArr;** App and LinkHut tempo should both be 130bpm.
+- Open LinkHut, press **Play** and **enable** Link.
+- Set LinkHut tempo to 130bpm.
+- Open App and **enable** Link **&rArr;** LinkHut’s tempo should not change.
+- Load new Song/Set/Session with a tempo other than 130bpm **&rArr;** App and LinkHut tempo should both be 130bpm.
### TEMPO-4: Tempo range handling.
-- Open LinkHut, press **Play**, when using QLinkHut click the **Link** button to enable
-Link. - Open App, start Audio, and **enable** Link. - Change tempo in LinkHut to
-**20bpm** **&rArr;** App and LinkHut should stay in sync. - Change Tempo in LinkHut to
-**999bpm** **&rArr;** App and LinkHut should stay in sync. - If App does not support the
-full range of tempos supported by Link, it should stay in sync by switching to a multiple
-of the Link session tempo.
+- Open LinkHut, press **Play**, enable Link.
+- Open App, start Audio, and **enable** Link.
+- Change tempo in LinkHut to **20bpm** **&rArr;** App and LinkHut should stay in sync.
+- Change Tempo in LinkHut to **999bpm** **&rArr;** App and LinkHut should stay in sync.
+- If App does not support the full range of tempos supported by Link, it should stay in sync by switching to a multiple of the Link session tempo.
### TEMPO-5: Enabling Link does not change app's tempo if there is no Link session to join.
-- Open App, start playing. - Change App tempo to something other than the default.
-- **Enable** Link **&rArr;** App's tempo should not change. - Change App tempo to a new
-value (not the default). - **Disable** Link **&rArr;** App's tempo should not change.
+- Open App, start playing.
+- Change App tempo to something other than the default.
+- **Enable** Link **&rArr;** App's tempo should not change.
+- Change App tempo to a new value (not the default).
+- **Disable** Link **&rArr;** App's tempo should not change.
## Beat Time
These cases verify the continuity of beat time across Link operations.
### BEATTIME-1: Enabling Link does not change app's beat time if there is no Link session to join.
-- Open App, start playing. - **Enable** Link **&rArr;** No beat time jump or
-audible discontinuity should occur. - **Disable** Link **&rArr;** No beat time jump or
-audible discontinuity should occur.
+- Open App, start playing.
+- **Enable** Link **&rArr;** No beat time jump or audible discontinuity should occur.
+- **Disable** Link **&rArr;** No beat time jump or audible discontinuity should occur.
### BEATTIME-2: App's beat time does not change if another participant joins its session.
-- Open App and **enable** Link. - Start playing. - Open LinkHut, whenusing QLinkHut
-**enable** Link **&rArr;** No beat time jump or audible discontinuity should occur in the
-App.
+- Open App and **enable** Link.
+- Start playing.
+- Open LinkHut and **enable** Link **&rArr;** No beat time jump or audible discontinuity should occur in the App.
**Note**: When joining an existing Link session, an app should adjust to the existing
session's tempo and phase, which will usually result in a beat time jump. Apps that are
already in a session should never have any kind of beat time or audio discontinuity when
a new participant joins the session.
+## Start Stop States
+
+### STARTSTOPSTATE-1: Listening to start/stop commands from other peers.
+- Open App, set Link and Start Stop Sync to **Enabled**.
+- Open LinkHut, **enable** Link and Start Stop Sync and press **Play** **&rArr;** App should start playing according to its quantization.
+- Stop playback in LinkHut **&rArr;** App should stop playing.
+
+### STARTSTOPSTATE-2: Sending start/stop commands to other peers.
+- Open LinkHut, **enable** Link and Start Stop Sync and press **Play**.
+- Open App, set Link and Start Stop Sync to **Enabled** **&rArr;** App should not be
+playing while LinkHut continues playing.
+- Start playback in App **&rArr;** App should join playing according to its quantization.
+- Stop playback in App **&rArr;** App and LinkHut should stop playing.
+- Start playback in App **&rArr;** App and LinkHut should start playing according to
+their quantizations.
+
## Audio Engine
These cases verify the correct implementation of latency compensation within an app's
@@ -66,10 +87,11 @@ audio engine.
### AUDIOENGINE-1: Correct alignment of app audio with shared session
- - Connect the audio out of your computer to the audio in. Alternatively use
+- Connect the audio out of your computer to the audio in. Alternatively use
[SoundFlower](https://github.com/mattingalls/Soundflower) to be able to record the output
-of your app and LinkHut. - Open LinkHut, press **Play**, , when using QLinkHut click the
-**Link** button to enable Link. - Open App and **enable** Link. - Start playing audio
-(preferably a short, click-like sample) with notes on the same beats as LinkHut. - Record
-audio within application of choice. - Validate whether onset of the sample aligns with
-the pulse generated by LinkHut (tolerance: less than 3 ms).
+of your app and LinkHut.
+- Open LinkHut, **enable** Link and press **Play**.
+- Open App and **enable** Link.
+- Start playing audio (preferably a short, click-like sample) with notes on the same beats as LinkHut.
+- Record audio within application of choice.
+- Validate whether onset of the sample aligns with the pulse generated by LinkHut (tolerance: less than 3 ms).
diff --git a/ci/configure.py b/ci/configure.py
index b3a4a2f..451e2f3 100755
--- a/ci/configure.py
+++ b/ci/configure.py
@@ -27,11 +27,6 @@ def parse_args():
help='Build configuration to use (not supported by IDE generators)')
arg_parser.add_argument(
- '-q', '--with-qt',
- help='Build Qt example apps',
- action='store_true')
-
- arg_parser.add_argument(
'-g', '--generator',
help='CMake generator to use (default: Determined by CMake)')
@@ -54,9 +49,6 @@ def build_cmake_args(args):
cmake_args.append('-G')
cmake_args.append(args.generator)
- if args.with_qt:
- cmake_args.append('-DLINK_BUILD_QT_EXAMPLES=ON')
-
if args.configuration is not None:
cmake_args.append('-DCMAKE_BUILD_TYPE=' + args.configuration)
diff --git a/cmake_include/ConfigureCompileFlags.cmake b/cmake_include/ConfigureCompileFlags.cmake
index 96ceb03..4cf5140 100644
--- a/cmake_include/ConfigureCompileFlags.cmake
+++ b/cmake_include/ConfigureCompileFlags.cmake
@@ -107,8 +107,12 @@ elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
"/wd4640" # 'Instance': construction of local static object is not thread-safe
"/wd4710" # 'Function': function not inlined
"/wd4711" # Function 'function' selected for inline expansion
+ "/wd4723" # potential divide by 0
"/wd4738" # Storing 32-bit float result in memory, possible loss of performance
"/wd4820" # 'Bytes': bytes padding added after construct 'member_name'
+ "/wd4996" # Your code uses a function, class member, variable, or typedef that's marked deprecated
+ "/wd5045" # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified
+ "/wd5204" # 'class' : class has virtual functions, but destructor is not virtual
)
if(MSVC_VERSION VERSION_GREATER 1800)
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 1cafbae..b85107f 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -103,24 +103,6 @@ function(configure_linkhut_executable target)
target_link_libraries(${target} Ableton::Link)
endfunction()
-function(configure_qlinkhut_executable target)
- configure_linkhut_executable(${target})
- target_link_libraries(${target} Qt5::Quick)
-
- if(${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
- target_compile_options(${target} PRIVATE
- "-Wno-global-constructors"
- "-Wno-missing-prototypes"
- "-Wno-undefined-reinterpret-cast"
- "-Wno-zero-as-null-pointer-constant"
- )
- endif()
-
- if(MSVC_VERSION VERSION_GREATER 1900)
- target_compile_options(${target} PRIVATE "/wd4946")
- endif()
-endfunction()
-
function(configure_linkhut_audio_sources target)
if(APPLE)
target_link_libraries(${target} "-framework AudioUnit")
@@ -210,66 +192,32 @@ configure_linkhut_audio_sources(LinkHut)
configure_linkhut_executable(LinkHut)
source_group("LinkHut" FILES ${linkhut_HEADERS} ${linkhut_SOURCES})
-# ___ _ _ _ _ _ _
-# / _ \| | (_)_ __ | | _| | | |_ _| |_
-# | | | | | | | '_ \| |/ / |_| | | | | __|
-# | |_| | |___| | | | | <| _ | |_| | |_
-# \__\_\_____|_|_| |_|_|\_\_| |_|\__,_|\__|
+# _ _ _ _ _ _ ____ _ _ _
+# | | (_)_ __ | | _| | | |_ _| |_/ ___|(_) | ___ _ __ | |_
+# | | | | '_ \| |/ / |_| | | | | __\___ \| | |/ _ \ '_ \| __|
+# | |___| | | | | <| _ | |_| | |_ ___) | | | __/ | | | |_
+# |_____|_|_| |_|_|\_\_| |_|\__,_|\__|____/|_|_|\___|_| |_|\__|
#
-if(LINK_BUILD_QT_EXAMPLES)
-
- set(CMAKE_AUTOMOC ON)
- set(CMAKE_INCLUDE_CURRENT_DIR ON)
-
- find_package(Qt5Quick REQUIRED)
-
- set(qlinkhut_HEADERS
- linkaudio/AudioEngine.hpp
- linkaudio/AudioPlatform.hpp
- qlinkhut/Controller.hpp
- ${link_HEADERS}
- )
-
- set(qlinkhut_SOURCES
- linkaudio/AudioEngine.cpp
- qlinkhut/Controller.cpp
- qlinkhut/main.cpp
- ${link_SOURCES}
- )
- source_group("QLinkHut" FILES ${qlinkhut_HEADERS} ${qlinkhut_SOURCES})
- qt5_add_resources(UI_RESOURCES qlinkhut/resources.qrc)
-
- add_executable(QLinkHut
- ${qlinkhut_HEADERS}
- ${qlinkhut_SOURCES}
- ${linkhut_audio_SOURCES}
- ${UI_RESOURCES}
- )
-
- configure_linkhut_audio_sources(QLinkHut)
- configure_qlinkhut_executable(QLinkHut)
+set(linkhutsilent_HEADERS
+ linkaudio/AudioEngine.hpp
+ linkaudio/AudioPlatform_Dummy.hpp
+ ${link_HEADERS}
+)
- # A silent version of QLinkHut that uses the Link application context instead of
- # the audio buffer context. It is the same as QLinkHut except that it doesn't
- # generate sound.
+set(linkhutsilent_SOURCES
+ linkaudio/AudioEngine.cpp
+ linkhut/main.cpp
+)
- add_executable(QLinkHutSilent
- ${qlinkhut_HEADERS}
- ${qlinkhut_SOURCES}
- linkaudio/AudioPlatform_Dummy.hpp
- ${UI_RESOURCES}
- )
+add_executable(LinkHutSilent
+ ${linkhutsilent_HEADERS}
+ ${linkhutsilent_SOURCES}
+)
- target_compile_definitions(QLinkHutSilent PRIVATE
- -DLINKHUT_AUDIO_PLATFORM_DUMMY=1
- )
- if(UNIX)
- set_target_properties(QLinkHutSilent
- PROPERTIES
- COMPILE_FLAGS "-Wno-unused"
- )
- endif()
+target_compile_definitions(LinkHutSilent PRIVATE
+ -DLINKHUT_AUDIO_PLATFORM_DUMMY=1
+)
- configure_qlinkhut_executable(QLinkHutSilent)
-endif()
+configure_linkhut_executable(LinkHutSilent)
+source_group("LinkHutSilent" FILES ${linkhutsilent_HEADERS} ${linkhutsilent_SOURCES})
diff --git a/examples/esp32/CMakeLists.txt b/examples/esp32/CMakeLists.txt
new file mode 100644
index 0000000..b6d956d
--- /dev/null
+++ b/examples/esp32/CMakeLists.txt
@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 3.5)
+
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(link_esp32_example) \ No newline at end of file
diff --git a/examples/esp32/README.md b/examples/esp32/README.md
new file mode 100644
index 0000000..9647af4
--- /dev/null
+++ b/examples/esp32/README.md
@@ -0,0 +1,12 @@
+# *E X P E R I M E N T A L*
+
+*Tested with esp-idf [v4.0-beta2](https://github.com/espressif/esp-idf/releases/tag/v4.0-rc)*
+
+## Building and Running the Example
+
+* Setup esp-idf as described in [the documentation](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html)
+* Run `idf.py menuconfig` and setup WiFi credentials under
+`Example Connection Configuration`
+```
+idf.py build
+idf.py -p ${ESP32_SERIAL_PORT} flash
diff --git a/examples/esp32/main/CMakeLists.txt b/examples/esp32/main/CMakeLists.txt
new file mode 100644
index 0000000..64756ee
--- /dev/null
+++ b/examples/esp32/main/CMakeLists.txt
@@ -0,0 +1,11 @@
+idf_component_register(SRCS main.cpp)
+
+if(NOT DEFINED LINK_ESP_TASK_CORE_ID)
+ set(LINK_ESP_TASK_CORE_ID tskNO_AFFINITY)
+endif()
+target_compile_definitions(${COMPONENT_LIB} PRIVATE LINK_ESP_TASK_CORE_ID=${LINK_ESP_TASK_CORE_ID})
+
+target_compile_options(${COMPONENT_LIB} PRIVATE -fexceptions)
+
+include(../../../AbletonLinkConfig.cmake)
+target_link_libraries(${COMPONENT_TARGET} Ableton::Link)
diff --git a/examples/esp32/main/main.cpp b/examples/esp32/main/main.cpp
new file mode 100644
index 0000000..6c8892f
--- /dev/null
+++ b/examples/esp32/main/main.cpp
@@ -0,0 +1,108 @@
+#include <ableton/Link.hpp>
+#include <driver/gpio.h>
+#include <driver/timer.h>
+#include <esp_event.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <freertos/task.h>
+#include <nvs_flash.h>
+#include <protocol_examples_common.h>
+
+#define LED GPIO_NUM_2
+#define PRINT_LINK_STATE false
+
+unsigned int if_nametoindex(const char* ifName)
+{
+ return 0;
+}
+
+char* if_indextoname(unsigned int ifIndex, char* ifName)
+{
+ return nullptr;
+}
+
+void IRAM_ATTR timer_group0_isr(void* userParam)
+{
+ static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+
+ TIMERG0.int_clr_timers.t0 = 1;
+ TIMERG0.hw_timer[0].config.alarm_en = 1;
+
+ xSemaphoreGiveFromISR(userParam, &xHigherPriorityTaskWoken);
+ if (xHigherPriorityTaskWoken)
+ {
+ portYIELD_FROM_ISR();
+ }
+}
+
+void timerGroup0Init(int timerPeriodUS, void* userParam)
+{
+ timer_config_t config = {.alarm_en = TIMER_ALARM_EN,
+ .counter_en = TIMER_PAUSE,
+ .intr_type = TIMER_INTR_LEVEL,
+ .counter_dir = TIMER_COUNT_UP,
+ .auto_reload = TIMER_AUTORELOAD_EN,
+ .divider = 80};
+
+ timer_init(TIMER_GROUP_0, TIMER_0, &config);
+ timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
+ timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, timerPeriodUS);
+ timer_enable_intr(TIMER_GROUP_0, TIMER_0);
+ timer_isr_register(TIMER_GROUP_0, TIMER_0, &timer_group0_isr, userParam, 0, nullptr);
+
+ timer_start(TIMER_GROUP_0, TIMER_0);
+}
+
+void printTask(void* userParam)
+{
+ auto link = static_cast<ableton::Link*>(userParam);
+ const auto quantum = 4.0;
+
+ while (true)
+ {
+ const auto sessionState = link->captureAppSessionState();
+ const auto numPeers = link->numPeers();
+ const auto time = link->clock().micros();
+ const auto beats = sessionState.beatAtTime(time, quantum);
+ std::cout << std::defaultfloat << "| peers: " << numPeers << " | "
+ << "tempo: " << sessionState.tempo() << " | " << std::fixed
+ << "beats: " << beats << " |" << std::endl;
+ vTaskDelay(800 / portTICK_PERIOD_MS);
+ }
+}
+
+void tickTask(void* userParam)
+{
+ ableton::Link link(120.0f);
+ link.enable(true);
+
+ if (PRINT_LINK_STATE)
+ {
+ xTaskCreate(printTask, "print", 8192, &link, 1, nullptr);
+ }
+
+ gpio_set_direction(LED, GPIO_MODE_OUTPUT);
+
+ while (true)
+ {
+ xSemaphoreTake(userParam, portMAX_DELAY);
+
+ const auto state = link.captureAudioSessionState();
+ const auto phase = state.phaseAtTime(link.clock().micros(), 1.);
+ gpio_set_level(LED, fmodf(phase, 1.) < 0.1);
+ portYIELD();
+ }
+}
+
+extern "C" void app_main()
+{
+ ESP_ERROR_CHECK(nvs_flash_init());
+ tcpip_adapter_init();
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ ESP_ERROR_CHECK(example_connect());
+
+ SemaphoreHandle_t tickSemphr = xSemaphoreCreateBinary();
+ timerGroup0Init(100, tickSemphr);
+
+ xTaskCreate(tickTask, "tick", 8192, tickSemphr, configMAX_PRIORITIES - 1, nullptr);
+}
diff --git a/examples/esp32/sdkconfig.defaults b/examples/esp32/sdkconfig.defaults
new file mode 100644
index 0000000..75ebeae
--- /dev/null
+++ b/examples/esp32/sdkconfig.defaults
@@ -0,0 +1 @@
+CONFIG_COMPILER_CXX_EXCEPTIONS=y
diff --git a/examples/linkaudio/AudioEngine.cpp b/examples/linkaudio/AudioEngine.cpp
index f2802d0..323d634 100644
--- a/examples/linkaudio/AudioEngine.cpp
+++ b/examples/linkaudio/AudioEngine.cpp
@@ -24,6 +24,7 @@
#define _USE_MATH_DEFINES
#endif
#include <cmath>
+#include <iostream>
namespace ableton
{
@@ -33,12 +34,16 @@ namespace linkaudio
AudioEngine::AudioEngine(Link& link)
: mLink(link)
, mSampleRate(44100.)
- , mOutputLatency(0)
+ , mOutputLatency(std::chrono::microseconds{0})
, mSharedEngineData({0., false, false, 4., false})
, mLockfreeEngineData(mSharedEngineData)
, mTimeAtLastClick{}
, mIsPlaying(false)
{
+ if (!mOutputLatency.is_lock_free())
+ {
+ std::cout << "WARNING: AudioEngine::mOutputLatency is not lock free!" << std::endl;
+ }
}
void AudioEngine::startPlaying()
@@ -143,7 +148,7 @@ void AudioEngine::renderMetronomeIntoBuffer(const Link::SessionState sessionStat
{
double amplitude = 0.;
// Compute the host time for this sample and the last.
- const auto hostTime = beginHostTime + microseconds(llround(i * microsPerSample));
+ const auto hostTime = beginHostTime + microseconds(llround(static_cast<double>(i) * microsPerSample));
const auto lastSampleHostTime = hostTime - microseconds(llround(microsPerSample));
// Only make sound for positive beat magnitudes. Negative beat
diff --git a/examples/linkaudio/AudioEngine.hpp b/examples/linkaudio/AudioEngine.hpp
index 5225ffd..f9c94d5 100644
--- a/examples/linkaudio/AudioEngine.hpp
+++ b/examples/linkaudio/AudioEngine.hpp
@@ -22,6 +22,7 @@
// Make sure to define this before <cmath> is included for Windows
#define _USE_MATH_DEFINES
#include <ableton/Link.hpp>
+#include <atomic>
#include <mutex>
namespace ableton
@@ -64,7 +65,7 @@ private:
Link& mLink;
double mSampleRate;
- std::chrono::microseconds mOutputLatency;
+ std::atomic<std::chrono::microseconds> mOutputLatency;
std::vector<double> mBuffer;
EngineData mSharedEngineData;
EngineData mLockfreeEngineData;
diff --git a/examples/linkaudio/AudioPlatform_Asio.cpp b/examples/linkaudio/AudioPlatform_Asio.cpp
index fb4250d..40aa20a 100644
--- a/examples/linkaudio/AudioPlatform_Asio.cpp
+++ b/examples/linkaudio/AudioPlatform_Asio.cpp
@@ -112,7 +112,7 @@ void AudioPlatform::audioCallback(ASIOTime* timeInfo, long index)
asioSamplesToDouble(timeInfo->timeInfo.samplePosition));
}
- const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency;
+ const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency.load();
ASIOBufferInfo* bufferInfos = mDriverInfo.bufferInfos;
const long numSamples = mDriverInfo.preferredSize;
@@ -220,12 +220,13 @@ void AudioPlatform::createAsioBuffers()
<< ", output latency: " << outputLatency << "usec" << std::endl;
const double bufferSize = driverInfo.preferredSize / driverInfo.sampleRate;
- mEngine.mOutputLatency =
+ auto outputLatencyMicros =
std::chrono::microseconds(llround(outputLatency / driverInfo.sampleRate));
- mEngine.mOutputLatency += std::chrono::microseconds(llround(1.0e6 * bufferSize));
+ outputLatencyMicros += std::chrono::microseconds(llround(1.0e6 * bufferSize));
+
+ mEngine.mOutputLatency.store(outputLatencyMicros);
- const auto totalLatency = mEngine.mOutputLatency.count();
- std::clog << "Total latency: " << totalLatency << "usec" << std::endl;
+ std::clog << "Total latency: " << outputLatencyMicros.count() << "usec" << std::endl;
}
void AudioPlatform::initializeDriverInfo()
diff --git a/examples/linkaudio/AudioPlatform_CoreAudio.cpp b/examples/linkaudio/AudioPlatform_CoreAudio.cpp
index 5574bc1..8ec07ed 100644
--- a/examples/linkaudio/AudioPlatform_CoreAudio.cpp
+++ b/examples/linkaudio/AudioPlatform_CoreAudio.cpp
@@ -50,7 +50,7 @@ OSStatus AudioPlatform::audioCallback(void* inRefCon,
AudioEngine* engine = static_cast<AudioEngine*>(inRefCon);
const auto bufferBeginAtOutput =
- engine->mLink.clock().ticksToMicros(inTimeStamp->mHostTime) + engine->mOutputLatency;
+ engine->mLink.clock().ticksToMicros(inTimeStamp->mHostTime) + engine->mOutputLatency.load();
engine->audioCallback(bufferBeginAtOutput, inNumberFrames);
@@ -161,7 +161,7 @@ void AudioPlatform::initialize()
using namespace std::chrono;
const double latency = static_cast<double>(deviceLatency) / mEngine.mSampleRate;
- mEngine.mOutputLatency = duration_cast<microseconds>(duration<double>{latency});
+ mEngine.mOutputLatency.store(duration_cast<microseconds>(duration<double>{latency}));
AURenderCallbackStruct ioRemoteInput;
ioRemoteInput.inputProc = audioCallback;
diff --git a/examples/linkaudio/AudioPlatform_Dummy.hpp b/examples/linkaudio/AudioPlatform_Dummy.hpp
index bd9905d..b5ff745 100644
--- a/examples/linkaudio/AudioPlatform_Dummy.hpp
+++ b/examples/linkaudio/AudioPlatform_Dummy.hpp
@@ -19,7 +19,7 @@
#pragma once
-#include "AudioEngine.hpp"
+#include <ableton/Link.hpp>
namespace ableton
{
@@ -28,6 +28,77 @@ namespace linkaudio
class AudioPlatform
{
+ class AudioEngine
+ {
+ public:
+ AudioEngine(Link& link)
+ : mLink(link)
+ , mQuantum(4.)
+ {
+ }
+
+ void startPlaying()
+ {
+ auto sessionState = mLink.captureAppSessionState();
+ sessionState.setIsPlayingAndRequestBeatAtTime(true, now(), 0., mQuantum);
+ mLink.commitAppSessionState(sessionState);
+ }
+
+ void stopPlaying()
+ {
+ auto sessionState = mLink.captureAppSessionState();
+ sessionState.setIsPlaying(false, now());
+ mLink.commitAppSessionState(sessionState);
+ }
+
+ bool isPlaying() const
+ {
+ return mLink.captureAppSessionState().isPlaying();
+ }
+
+ double beatTime() const
+ {
+ auto sessionState = mLink.captureAppSessionState();
+ return sessionState.beatAtTime(now(), mQuantum);
+ }
+
+ void setTempo(double tempo)
+ {
+ auto sessionState = mLink.captureAppSessionState();
+ sessionState.setTempo(tempo, now());
+ mLink.commitAppSessionState(sessionState);
+ }
+
+ double quantum() const
+ {
+ return mQuantum;
+ }
+
+ void setQuantum(double quantum)
+ {
+ mQuantum = quantum;
+ }
+
+ bool isStartStopSyncEnabled() const
+ {
+ return mLink.isStartStopSyncEnabled();
+ }
+
+ void setStartStopSyncEnabled(bool enabled)
+ {
+ mLink.enableStartStopSync(enabled);
+ }
+
+ private:
+ std::chrono::microseconds now() const
+ {
+ return mLink.clock().micros();
+ }
+
+ Link& mLink;
+ double mQuantum;
+ };
+
public:
AudioPlatform(Link& link)
: mEngine(link)
diff --git a/examples/linkaudio/AudioPlatform_Jack.cpp b/examples/linkaudio/AudioPlatform_Jack.cpp
index 87b7b36..76a778d 100644
--- a/examples/linkaudio/AudioPlatform_Jack.cpp
+++ b/examples/linkaudio/AudioPlatform_Jack.cpp
@@ -30,8 +30,8 @@ namespace linkaudio
AudioPlatform::AudioPlatform(Link& link)
: mEngine(link)
, mSampleTime(0.)
- , mpJackClient(NULL)
- , mpJackPorts(NULL)
+ , mpJackClient(nullptr)
+ , mpJackPorts(nullptr)
{
initialize();
start();
@@ -49,6 +49,20 @@ int AudioPlatform::audioCallback(jack_nframes_t nframes, void* pvUserData)
return pAudioPlatform->audioCallback(nframes);
}
+void AudioPlatform::latencyCallback(jack_latency_callback_mode_t, void* pvUserData)
+{
+ AudioPlatform* pAudioPlatform = static_cast<AudioPlatform*>(pvUserData);
+ pAudioPlatform->updateLatency();
+}
+
+void AudioPlatform::updateLatency()
+{
+ jack_latency_range_t latencyRange;
+ jack_port_get_latency_range(mpJackPorts[0], JackPlaybackLatency, &latencyRange);
+ mEngine.mOutputLatency.store(
+ std::chrono::microseconds(llround(1.0e6 * latencyRange.max / mEngine.mSampleRate)));
+}
+
int AudioPlatform::audioCallback(jack_nframes_t nframes)
{
using namespace std::chrono;
@@ -58,7 +72,7 @@ int AudioPlatform::audioCallback(jack_nframes_t nframes)
mSampleTime += nframes;
- const auto bufferBeginAtOutput = hostTime + engine.mOutputLatency;
+ const auto bufferBeginAtOutput = hostTime + engine.mOutputLatency.load();
engine.audioCallback(bufferBeginAtOutput, nframes);
@@ -76,7 +90,7 @@ void AudioPlatform::initialize()
{
jack_status_t status = JackFailure;
mpJackClient = jack_client_open("LinkHut", JackNullOption, &status);
- if (mpJackClient == NULL)
+ if (mpJackClient == nullptr)
{
std::cerr << "Could not initialize Audio Engine. ";
std::cerr << "JACK: " << std::endl;
@@ -104,15 +118,23 @@ void AudioPlatform::initialize()
std::cerr << "Client protocol version mismatch." << std::endl;
std::cerr << std::endl;
std::terminate();
- };
+ }
+
+ const double bufferSize = jack_get_buffer_size(mpJackClient);
+ const double sampleRate = jack_get_sample_rate(mpJackClient);
+ mEngine.setBufferSize(static_cast<std::size_t>(bufferSize));
+ mEngine.setSampleRate(sampleRate);
+
+ jack_set_latency_callback(mpJackClient, AudioPlatform::latencyCallback, this);
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)
+ if (mpJackPorts[k] == nullptr)
{
std::cerr << "Could not get Audio Device. " << std::endl;
jack_client_close(mpJackClient);
@@ -121,15 +143,6 @@ void AudioPlatform::initialize()
}
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<std::size_t>(bufferSize));
- mEngine.setSampleRate(sampleRate);
-
- mEngine.mOutputLatency =
- std::chrono::microseconds(llround(1.0e6 * bufferSize / sampleRate));
}
void AudioPlatform::uninitialize()
@@ -137,13 +150,13 @@ void AudioPlatform::uninitialize()
for (int k = 0; k < 2; ++k)
{
jack_port_unregister(mpJackClient, mpJackPorts[k]);
- mpJackPorts[k] = NULL;
+ mpJackPorts[k] = nullptr;
}
delete[] mpJackPorts;
- mpJackPorts = NULL;
+ mpJackPorts = nullptr;
jack_client_close(mpJackClient);
- mpJackClient = NULL;
+ mpJackClient = nullptr;
}
void AudioPlatform::start()
@@ -151,7 +164,7 @@ void AudioPlatform::start()
jack_activate(mpJackClient);
const char** playback_ports = jack_get_ports(
- mpJackClient, 0, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsPhysical);
+ mpJackClient, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsPhysical);
if (playback_ports)
{
diff --git a/examples/linkaudio/AudioPlatform_Jack.hpp b/examples/linkaudio/AudioPlatform_Jack.hpp
index 5233e4a..0b7999c 100644
--- a/examples/linkaudio/AudioPlatform_Jack.hpp
+++ b/examples/linkaudio/AudioPlatform_Jack.hpp
@@ -40,6 +40,8 @@ public:
private:
static int audioCallback(jack_nframes_t nframes, void* pvUserData);
int audioCallback(jack_nframes_t nframes);
+ static void latencyCallback(jack_latency_callback_mode_t mode, void* pvUserData);
+ void updateLatency();
void initialize();
void uninitialize();
diff --git a/examples/linkaudio/AudioPlatform_Portaudio.cpp b/examples/linkaudio/AudioPlatform_Portaudio.cpp
index d6b4e3f..17e7e05 100644
--- a/examples/linkaudio/AudioPlatform_Portaudio.cpp
+++ b/examples/linkaudio/AudioPlatform_Portaudio.cpp
@@ -59,7 +59,7 @@ int AudioPlatform::audioCallback(const void* /*inputBuffer*/,
platform.mSampleTime += inNumFrames;
- const auto bufferBeginAtOutput = hostTime + engine.mOutputLatency;
+ const auto bufferBeginAtOutput = hostTime + engine.mOutputLatency.load();
engine.audioCallback(bufferBeginAtOutput, inNumFrames);
@@ -94,8 +94,8 @@ void AudioPlatform::initialize()
outputParameters.suggestedLatency =
Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = nullptr;
- mEngine.mOutputLatency =
- std::chrono::microseconds(llround(outputParameters.suggestedLatency * 1.0e6));
+ mEngine.mOutputLatency.store(
+ std::chrono::microseconds(llround(outputParameters.suggestedLatency * 1.0e6)));
result = Pa_OpenStream(&pStream, nullptr, &outputParameters, mEngine.mSampleRate,
mEngine.mBuffer.size(), paClipOff, &audioCallback, this);
diff --git a/examples/linkaudio/AudioPlatform_Wasapi.cpp b/examples/linkaudio/AudioPlatform_Wasapi.cpp
index 4bdd912..efb73f8 100644
--- a/examples/linkaudio/AudioPlatform_Wasapi.cpp
+++ b/examples/linkaudio/AudioPlatform_Wasapi.cpp
@@ -67,6 +67,7 @@ DWORD renderAudioRunloop(LPVOID lpParam)
AudioPlatform::AudioPlatform(Link& link)
: mEngine(link)
+ , mSampleTime(0)
, mDevice(nullptr)
, mAudioClient(nullptr)
, mRenderClient(nullptr)
@@ -74,7 +75,6 @@ AudioPlatform::AudioPlatform(Link& link)
, mEventHandle(nullptr)
, mAudioThreadHandle(nullptr)
, mIsRunning(false)
- , mSampleTime(0)
{
initialize();
mEngine.setBufferSize(bufferSize());
@@ -278,7 +278,7 @@ DWORD AudioPlatform::audioRunloop()
mSampleTime += numSamples;
- const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency;
+ const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency.load();
mEngine.audioCallback(bufferBeginAtOutput, numSamples);
diff --git a/examples/linkhut/main.cpp b/examples/linkhut/main.cpp
index 6f17f2d..7bd0fcf 100644
--- a/examples/linkhut/main.cpp
+++ b/examples/linkhut/main.cpp
@@ -22,6 +22,7 @@
#include <algorithm>
#include <atomic>
#include <chrono>
+#include <iomanip>
#include <iostream>
#include <thread>
#if defined(LINK_PLATFORM_UNIX)
@@ -42,7 +43,6 @@ struct State
, link(120.)
, audioPlatform(link)
{
- link.enable(true);
}
};
@@ -76,6 +76,7 @@ void printHelp()
{
std::cout << std::endl << " < L I N K H U T >" << std::endl << std::endl;
std::cout << "usage:" << std::endl;
+ std::cout << " enable / disable Link: a" << std::endl;
std::cout << " start / stop: space" << std::endl;
std::cout << " decrease / increase tempo: w / e" << std::endl;
std::cout << " decrease / increase quantum: r / t" << std::endl;
@@ -83,20 +84,30 @@ void printHelp()
std::cout << " quit: q" << std::endl << std::endl;
}
+void printStateHeader()
+{
+ std::cout
+ << "enabled | num peers | quantum | start stop sync | tempo | beats | metro"
+ << std::endl;
+}
+
void printState(const std::chrono::microseconds time,
const ableton::Link::SessionState sessionState,
+ const bool linkEnabled,
const std::size_t numPeers,
const double quantum,
const bool startStopSyncOn)
{
+ using namespace std;
+ const auto enabled = linkEnabled ? "yes" : "no";
const auto beats = sessionState.beatAtTime(time, quantum);
const auto phase = sessionState.phaseAtTime(time, quantum);
- const auto startStop = startStopSyncOn ? "on" : "off";
- std::cout << std::defaultfloat << "peers: " << numPeers << " | "
- << "quantum: " << quantum << " | "
- << "start stop sync: " << startStop << " | "
- << "tempo: " << sessionState.tempo() << " | " << std::fixed
- << "beats: " << beats << " | ";
+ const auto startStop = startStopSyncOn ? "yes" : "no";
+ const auto isPlaying = sessionState.isPlaying() ? "[playing]" : "[stopped]";
+ cout << defaultfloat << left << setw(7) << enabled << " | " << setw(9) << numPeers
+ << " | " << setw(7) << quantum << " | " << setw(3) << startStop << " " << setw(11)
+ << isPlaying << " | " << fixed << setw(7) << sessionState.tempo() << " | " << fixed
+ << setprecision(2) << setw(7) << beats << " | ";
for (int i = 0; i < ceil(quantum); ++i)
{
if (i < phase)
@@ -137,6 +148,9 @@ void input(State& state)
state.running = false;
clearLine();
return;
+ case 'a':
+ state.link.enable(!state.link.isEnabled());
+ break;
case 'w':
engine.setTempo(tempo - 1);
break;
@@ -173,6 +187,7 @@ int main(int, char**)
{
State state;
printHelp();
+ printStateHeader();
std::thread thread(input, std::ref(state));
disableBufferedInput();
@@ -180,7 +195,7 @@ int main(int, char**)
{
const auto time = state.link.clock().micros();
auto sessionState = state.link.captureAppSessionState();
- printState(time, sessionState, state.link.numPeers(),
+ printState(time, sessionState, state.link.isEnabled(), state.link.numPeers(),
state.audioPlatform.mEngine.quantum(),
state.audioPlatform.mEngine.isStartStopSyncEnabled());
std::this_thread::sleep_for(std::chrono::milliseconds(10));
diff --git a/examples/qlinkhut/Controller.cpp b/examples/qlinkhut/Controller.cpp
deleted file mode 100644
index ce9dc30..0000000
--- a/examples/qlinkhut/Controller.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/* 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 <http://www.gnu.org/licenses/>.
- *
- * If you would like to incorporate Link into a proprietary software application,
- * please contact <link-devs@ableton.com>.
- */
-
-#include "Controller.hpp"
-
-namespace ableton
-{
-namespace qlinkhut
-{
-
-Controller::Controller()
- : mTempo(120)
- , mLink(mTempo)
- , mAudioPlatform(mLink)
-{
- mLink.setNumPeersCallback([this](std::size_t) { Q_EMIT onNumberOfPeersChanged(); });
- mLink.setTempoCallback([this](const double bpm) {
- mTempo = bpm;
- Q_EMIT onTempoChanged();
- });
-}
-
-void Controller::setIsPlaying(bool isPlaying)
-{
- if (isPlaying)
- {
- mAudioPlatform.mEngine.startPlaying();
- }
- else
- {
- mAudioPlatform.mEngine.stopPlaying();
- }
-}
-
-bool Controller::isPlaying()
-{
- return mAudioPlatform.mEngine.isPlaying();
-}
-
-void Controller::setTempo(double bpm)
-{
- mAudioPlatform.mEngine.setTempo(bpm);
-}
-
-double Controller::tempo()
-{
- return mTempo;
-}
-
-double Controller::quantum()
-{
- return mAudioPlatform.mEngine.quantum();
-}
-
-void Controller::setQuantum(const double quantum)
-{
- mAudioPlatform.mEngine.setQuantum(quantum);
- Q_EMIT onQuantumChanged();
-}
-
-unsigned long Controller::numberOfPeers()
-{
- return static_cast<unsigned long>(mLink.numPeers());
-}
-
-void Controller::setLinkEnabled(const bool isEnabled)
-{
- mLink.enable(isEnabled);
- Q_EMIT onIsLinkEnabledChanged();
-}
-
-bool Controller::isLinkEnabled()
-{
- return mLink.isEnabled();
-}
-
-void Controller::setStartStopSyncEnabled(const bool isEnabled)
-{
- mAudioPlatform.mEngine.setStartStopSyncEnabled(isEnabled);
- Q_EMIT onIsStartStopSyncEnabledChanged();
-}
-
-bool Controller::isStartStopSyncEnabled()
-{
- return mAudioPlatform.mEngine.isStartStopSyncEnabled();
-}
-
-double Controller::beatTime()
-{
- return mAudioPlatform.mEngine.beatTime();
-}
-
-} // namespace qlinkhut
-} // namespace ableton
diff --git a/examples/qlinkhut/Controller.hpp b/examples/qlinkhut/Controller.hpp
deleted file mode 100644
index d66ebca..0000000
--- a/examples/qlinkhut/Controller.hpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/* 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 <http://www.gnu.org/licenses/>.
- *
- * If you would like to incorporate Link into a proprietary software application,
- * please contact <link-devs@ableton.com>.
- */
-
-#pragma once
-
-#include "AudioPlatform.hpp"
-#include <QtCore/QObject>
-
-namespace ableton
-{
-namespace qlinkhut
-{
-
-class Controller : public QObject
-{
- Q_OBJECT
- Q_DISABLE_COPY(Controller)
-
-public:
- Controller();
-
- void setIsPlaying(bool);
- bool isPlaying();
- Q_SIGNAL void onIsPlayingChanged();
- Q_PROPERTY(bool isPlaying READ isPlaying WRITE setIsPlaying)
-
- void setTempo(double);
- double tempo();
- Q_SIGNAL void onTempoChanged();
- Q_PROPERTY(double tempo READ tempo WRITE setTempo NOTIFY onTempoChanged)
-
- void setQuantum(double quantum);
- double quantum();
- Q_SIGNAL void onQuantumChanged();
- Q_PROPERTY(double quantum READ quantum WRITE setQuantum NOTIFY onQuantumChanged)
-
- unsigned long numberOfPeers();
- Q_SIGNAL void onNumberOfPeersChanged();
- Q_PROPERTY(unsigned long numberOfPeers READ numberOfPeers NOTIFY onNumberOfPeersChanged)
-
- void setLinkEnabled(bool isEnabled);
- bool isLinkEnabled();
- Q_SIGNAL void onIsLinkEnabledChanged();
- Q_PROPERTY(bool isLinkEnabled WRITE setLinkEnabled READ isLinkEnabled NOTIFY
- onIsLinkEnabledChanged)
-
- void setStartStopSyncEnabled(bool isEnabled);
- bool isStartStopSyncEnabled();
- Q_SIGNAL void onIsStartStopSyncEnabledChanged();
- Q_PROPERTY(bool isStartStopSyncEnabled WRITE setStartStopSyncEnabled READ
- isStartStopSyncEnabled NOTIFY onIsStartStopSyncEnabledChanged)
-
- Q_INVOKABLE double beatTime();
-
-private:
- double mTempo;
- Link mLink;
- linkaudio::AudioPlatform mAudioPlatform;
-};
-
-} // namespace qlinkhut
-} // namespace ableton
diff --git a/examples/qlinkhut/main.qml b/examples/qlinkhut/main.qml
deleted file mode 100644
index e31e837..0000000
--- a/examples/qlinkhut/main.qml
+++ /dev/null
@@ -1,351 +0,0 @@
-/* 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 <http://www.gnu.org/licenses/>.
- *
- * If you would like to incorporate Link into a proprietary software application,
- * please contact <link-devs@ableton.com>.
- */
-
-import QtQuick 2.3
-import QtQuick.Controls 1.2
-import QtQuick.Layouts 1.1
-
-Item {
- width: 768
- height: 576
-
- Rectangle {
- id: linkView
- x: 31
- y: 25
- width: 170
- height: 50
- border.color: "#404040"
- border.width: 1
- color: controller.isLinkEnabled ? "#E6E6E6" : "#FFFFFF"
-
- Label {
- id: linkLabel
- property var linkEnabledText: "0 Links"
- text: controller.isLinkEnabled ? linkEnabledText : "Link"
- color: "#404040"
- font.pixelSize: 36
- font.family: "Calibri"
- anchors.fill: parent
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
-
- Connections {
- target: controller
-
- onNumberOfPeersChanged: {
- linkLabel.linkEnabledText = controller.numberOfPeers + " Link"
- linkLabel.linkEnabledText = controller.numberOfPeers == 1
- ? linkLabel.linkEnabledText
- : linkLabel.linkEnabledText + "s"
- }
- }
- }
-
- MouseArea {
- anchors.fill: parent
-
- onPressed: {
- controller.isLinkEnabled = !controller.isLinkEnabled
- }
- }
- }
-
- Rectangle {
- id: startStopSyncView
- x: parent.width - width - 31
- y: 25
- width: 300
- height: 50
-
- border.color: "#404040"
- border.width: 1
- color: controller.isStartStopSyncEnabled ? "#E6E6E6" : "#FFFFFF"
-
- Label {
- id: startStopSyncLabel
- text: controller.isStartStopSyncEnabled ?
- "StartStopSync On" : "StartStopSync Off"
- color: "#404040"
- font.pixelSize: 36
- font.family: "Calibri"
- anchors.fill: parent
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- MouseArea {
- anchors.fill: parent
-
- onPressed: {
- controller.isStartStopSyncEnabled = !controller.isStartStopSyncEnabled
- }
- }
- }
-
- Rectangle {
- id: loopView
- x: 30
- y: 90
- width: 708
- height: 328
-
- function onQuantumChanged() {
- for(var i = loopView.children.length; i > 0 ; i--) {
- loopView.children[i-1].destroy()
- }
-
- var numSegments = Math.ceil(controller.quantum)
- for (var i = 0; i < numSegments; i++) {
- var width = loopView.width / controller.quantum
- var x = i * width
- var component = Qt.createComponent("BeatTile.qml");
- var tile = component.createObject(loopView, {
- "index": i,
- "x": x,
- "width": x + width > loopView.width ? loopView.width - x : width,
- })
- tile.activeColor = i == 0 ? "#FF6A00" : "#FFD500"
- }
- }
-
- Connections {
- target: controller
-
- onQuantumChanged: {
- loopView.onQuantumChanged()
- }
- }
-
- Timer {
- interval: 8
- running: true
- repeat: true
- property var last: 1
- onTriggered: {
- var index = controller.isPlaying
- ? Math.floor(controller.beatTime() % controller.quantum)
- : controller.quantum
- var countIn = index < 0;
- var beat = countIn ? controller.quantum + index : index
- for(var i = 0; i < loopView.children.length ; i++) {
- loopView.children[i].countIn = countIn
- loopView.children[i].currentBeat = beat
- }
- }
- }
-
- Component.onCompleted: {
- onQuantumChanged()
- }
- }
-
- Label {
- x: 31
- y: 460
- width: 170
- height: 30
- text: "Quantum";
- color: "#404040"
- font.pixelSize: 24
- font.family: "Calibri"
- horizontalAlignment: Text.AlignHCenter
- }
-
- Rectangle {
- id: quantumView
- x: 31
- y: 500
- width: 170
- height: 50
- border.width : 1
- border.color: "#404040"
-
- Label {
- id: quantumLabel
- text: controller.quantum.toFixed()
- color: "#404040"
- font.pixelSize: 36
- font.family: "Calibri"
- anchors.fill: parent
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- MouseArea {
- anchors.fill: parent
-
- property var clickQuantum: 0
- property var clickY: 0
-
- onPressed: {
- clickQuantum = controller.quantum
- clickY = mouseY
- }
-
- onPositionChanged: {
- var quantum = clickQuantum + 0.1 * (clickY - mouseY)
- controller.quantum = Math.max(1, quantum.toFixed())
- }
-
- onDoubleClicked: {
- controller.quantum = 4
- }
- }
- }
-
- Label {
- x: (parent.width - width) / 2 - 90
- y: 460
- width: 170
- height: 30
- text: "Tempo";
- color: "#404040"
- font.pixelSize: 24
- font.family: "Calibri"
- horizontalAlignment: Text.AlignHCenter
- }
-
-
- Rectangle {
- id: tempoView
- x: (parent.width - width) / 2 - 90
- y: 500
- width: 170
- height: 50
- border.width : 1
- border.color: "#404040"
-
- Label {
- id: tempoLabel
- text: controller.tempo.toFixed(2)
- color: "#404040"
- font.pixelSize: 36
- font.family: "Calibri"
- anchors.fill: parent
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- MouseArea {
- anchors.fill: parent
-
- property var clickTempo: 0
- property var clickY: 0
-
- onPressed: {
- clickTempo = controller.tempo
- clickY = mouseY
- }
-
- onPositionChanged: {
- var tempo = clickTempo + 0.5 * (clickY - mouseY)
- controller.tempo = tempo
- }
-
- onDoubleClicked: {
- controller.tempo = 120
- }
- }
- }
-
- Label {
- id: beatTimeView
- x: (parent.width - width) / 2 + 90
- y: 460
- width: 170
- height: 30
- text: "Beats"
- color: "#404040"
- font.pixelSize: 24
- font.family: "Calibri"
- horizontalAlignment: Text.AlignHCenter
- }
-
- Rectangle {
- id: beatTimeIntView
- x: (parent.width - width) / 2 + 90
- y: 500
- width: 170
- height: 50
- border.width: 1
- border.color: "#404040"
-
- Label {
- id: beatTimeText
- color: "#404040"
- font.pixelSize: 36
- font.family: "Calibri"
- anchors.fill: parent
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
- }
-
- Timer {
- id: timer
- interval: 10
- repeat: true
- running: true
-
- onTriggered: {
- var beatTime = controller.beatTime()
- beatTimeText.text = beatTime.toFixed(1)
- transportText.text = controller.isPlaying ? "Pause" : "Play";
- transportView.color = controller.isPlaying ? "#E6E6E6" : "#FFFFFF";
- }
- }
-
- Rectangle {
- id: transportView
- x: parent.width - width - 31
- y: 500
- width: 170
- height: 50
- border.width: 1
- border.color: "#404040"
-
- Label {
- id: transportText
- color: "#404040"
- font.pixelSize: 36
- font.family: "Calibri"
- anchors.fill: parent
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- MouseArea {
- anchors.fill: parent
-
- onPressed: {
- controller.isPlaying = !controller.isPlaying
- }
- }
- }
-
- Item {
- focus: true
- Keys.onPressed: {
- if ( event.key == 32) {
- controller.isPlaying = !controller.isPlaying
- }
- }
- }
-}
diff --git a/examples/qlinkhut/resources.qrc b/examples/qlinkhut/resources.qrc
deleted file mode 100644
index 74f2d2f..0000000
--- a/examples/qlinkhut/resources.qrc
+++ /dev/null
@@ -1,6 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>BeatTile.qml</file>
- <file>main.qml</file>
- </qresource>
-</RCC>
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 987cec0..0d9c71e 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -57,7 +57,6 @@ set(link_discovery_HEADERS
${link_discovery_DIR}/PeerGateway.hpp
${link_discovery_DIR}/PeerGateways.hpp
${link_discovery_DIR}/Service.hpp
- ${link_discovery_DIR}/Socket.hpp
${link_discovery_DIR}/UdpMessenger.hpp
${link_discovery_DIR}/v1/Messages.hpp
PARENT_SCOPE
@@ -73,7 +72,6 @@ set(link_discovery_HEADERS
set(link_platform_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ableton/platforms)
set(link_platform_HEADERS
${link_platform_DIR}/Config.hpp
- ${link_platform_DIR}/asio/AsioService.hpp
${link_platform_DIR}/asio/AsioTimer.hpp
${link_platform_DIR}/asio/AsioWrapper.hpp
${link_platform_DIR}/asio/Context.hpp
@@ -82,7 +80,16 @@ set(link_platform_HEADERS
${link_platform_DIR}/asio/Util.hpp
)
-if(UNIX)
+if(ESP_PLATFORM)
+ set(link_platform_HEADERS
+ ${link_platform_HEADERS}
+ ${link_platform_DIR}/esp32/Clock.hpp
+ ${link_platform_DIR}/esp32/Context.hpp
+ ${link_platform_DIR}/esp32/Esp32.hpp
+ ${link_platform_DIR}/esp32/Random.hpp
+ ${link_platform_DIR}/esp32/ScanIpIfAddrs.hpp
+ )
+elseif(UNIX)
set(link_platform_HEADERS
${link_platform_HEADERS}
${link_platform_DIR}/posix/ScanIpIfAddrs.hpp
@@ -93,6 +100,7 @@ if(UNIX)
${link_platform_HEADERS}
${link_platform_DIR}/darwin/Clock.hpp
${link_platform_DIR}/darwin/Darwin.hpp
+ ${link_platform_DIR}/stl/Random.hpp
)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set(link_platform_HEADERS
@@ -100,13 +108,16 @@ if(UNIX)
${link_platform_DIR}/linux/Clock.hpp
${link_platform_DIR}/linux/Linux.hpp
${link_platform_DIR}/stl/Clock.hpp
+ ${link_platform_DIR}/stl/Random.hpp
)
endif()
elseif(WIN32)
set(link_platform_HEADERS
${link_platform_HEADERS}
+ ${link_platform_DIR}/stl/Random.hpp
${link_platform_DIR}/windows/Clock.hpp
${link_platform_DIR}/windows/ScanIpIfAddrs.hpp
+ ${link_platform_DIR}/windows/Windows.hpp
)
endif()
set(link_platform_HEADERS
diff --git a/include/ableton/Link.hpp b/include/ableton/Link.hpp
index a5b76c5..9ba8b3a 100644
--- a/include/ableton/Link.hpp
+++ b/include/ableton/Link.hpp
@@ -29,8 +29,12 @@
namespace ableton
{
-/*! @class Link
- * @brief Class that represents a participant in a Link session.
+/*! @class Link and BasicLink
+ * @brief Classes representing a participant in a Link session.
+ * The BasicLink type allows to customize the clock. The Link type
+ * uses the recommended platform-dependent representation of the
+ * system clock as defined in platforms/Config.hpp.
+ * It's preferred to use Link instead of BasicLink.
*
* @discussion Each Link instance has its own session state which
* represents a beat timeline and a transport start/stop state. The
@@ -64,21 +68,26 @@ namespace ableton
* state from both the audio thread and an application thread
* concurrently is not advised and will potentially lead to unexpected
* behavior.
+ *
+ * Only use the BasicLink class if the default platform clock does not
+ * fulfill other requirements of the client application. Please note this
+ * will require providing a custom Clock implementation. See the clock()
+ * documentation for details.
*/
-class Link
+template <typename Clock>
+class BasicLink
{
public:
- using Clock = link::platform::Clock;
class SessionState;
/*! @brief Construct with an initial tempo. */
- Link(double bpm);
+ BasicLink(double bpm);
/*! @brief Link instances cannot be copied or moved */
- Link(const Link&) = delete;
- Link& operator=(const Link&) = delete;
- Link(Link&&) = delete;
- Link& operator=(Link&&) = delete;
+ BasicLink(const BasicLink<Clock>&) = delete;
+ BasicLink& operator=(const BasicLink<Clock>&) = delete;
+ BasicLink(BasicLink<Clock>&&) = delete;
+ BasicLink& operator=(BasicLink<Clock>&&) = delete;
/*! @brief Is Link currently enabled?
* Thread-safe: yes
@@ -152,13 +161,10 @@ public:
* Thread-safe: yes
* Realtime-safe: yes
*
- * @discussion The Clock type is a platform-dependent
- * representation of the system clock. It exposes a ticks() method
- * that returns the current ticks of the system clock as well as
- * micros(), which is a normalized representation of the current system
- * time in std::chrono::microseconds. It also provides conversion
- * functions ticksToMicros() and microsToTicks() to faciliate
- * converting between these units.
+ * @discussion The Clock type is a platform-dependent representation
+ * of the system clock. It exposes a micros() method, which is a
+ * normalized representation of the current system time in
+ * std::chrono::microseconds.
*/
Clock clock() const;
@@ -238,7 +244,12 @@ public:
public:
SessionState(const link::ApiState state, const bool bRespectQuantum);
- /*! @brief: The tempo of the timeline, in bpm */
+ /*! @brief: The tempo of the timeline, in Beats Per Minute.
+ *
+ * @discussion This is a stable value that is appropriate for display
+ * to the user. Beat time progress will not necessarily match this tempo
+ * exactly because of clock drift compensation.
+ */
double tempo() const;
/*! @brief: Set the timeline tempo to the given bpm value, taking
@@ -354,19 +365,38 @@ public:
bool isPlaying, std::chrono::microseconds time, double beat, double quantum);
private:
- friend Link;
+ friend BasicLink<Clock>;
link::ApiState mOriginalState;
link::ApiState mState;
bool mbRespectQuantum;
};
private:
+ using Controller = ableton::link::Controller<
+ link::PeerCountCallback,
+ link::TempoCallback,
+ link::StartStopStateCallback,
+ Clock,
+ link::platform::Random,
+ link::platform::IoContext>;
+
std::mutex mCallbackMutex;
- link::PeerCountCallback mPeerCountCallback;
- link::TempoCallback mTempoCallback;
- link::StartStopStateCallback mStartStopCallback;
+ link::PeerCountCallback mPeerCountCallback = [](std::size_t) {};
+ link::TempoCallback mTempoCallback = [](link::Tempo) {};
+ link::StartStopStateCallback mStartStopCallback = [](bool) {};
Clock mClock;
- link::platform::Controller mController;
+ Controller mController;
+};
+
+class Link : public BasicLink<link::platform::Clock>
+{
+public:
+ using Clock = link::platform::Clock;
+
+ Link(double bpm)
+ : BasicLink(bpm)
+ {
+ }
};
} // namespace ableton
diff --git a/include/ableton/Link.ipp b/include/ableton/Link.ipp
index 381bcd7..908f8e8 100644
--- a/include/ableton/Link.ipp
+++ b/include/ableton/Link.ipp
@@ -26,13 +26,12 @@ namespace ableton
namespace detail
{
-inline Link::SessionState toSessionState(
+template <typename Clock>
+inline typename BasicLink<Clock>::SessionState toSessionState(
const link::ClientState& state, const bool isConnected)
{
- const auto time = state.timeline.fromBeats(state.startStopState.beats);
- const auto startStopState =
- link::ApiStartStopState{state.startStopState.isPlaying, time};
- return {{state.timeline, startStopState}, isConnected};
+ return {{state.timeline, {state.startStopState.isPlaying, state.startStopState.time}},
+ isConnected};
}
inline link::IncomingClientState toIncomingClientState(const link::ApiState& state,
@@ -44,20 +43,18 @@ inline link::IncomingClientState toIncomingClientState(const link::ApiState& sta
: link::OptionalTimeline{};
const auto startStopState =
originalState.startStopState != state.startStopState
- ? link::OptionalStartStopState{{state.startStopState.isPlaying,
- state.timeline.toBeats(state.startStopState.time), timestamp}}
- : link::OptionalStartStopState{};
+ ? link::OptionalClientStartStopState{{state.startStopState.isPlaying,
+ state.startStopState.time, timestamp}}
+ : link::OptionalClientStartStopState{};
return {timeline, startStopState, timestamp};
}
} // namespace detail
-inline Link::Link(const double bpm)
- : mPeerCountCallback([](std::size_t) {})
- , mTempoCallback([](link::Tempo) {})
- , mStartStopCallback([](bool) {})
- , mClock{}
- , mController(link::Tempo(bpm),
+template <typename Clock>
+inline BasicLink<Clock>::BasicLink(const double bpm)
+ : mController(
+ link::Tempo(bpm),
[this](const std::size_t peers) {
std::lock_guard<std::mutex> lock(mCallbackMutex);
mPeerCountCallback(peers);
@@ -75,74 +72,87 @@ inline Link::Link(const double bpm)
{
}
-inline bool Link::isEnabled() const
+template <typename Clock>
+inline bool BasicLink<Clock>::isEnabled() const
{
return mController.isEnabled();
}
-inline void Link::enable(const bool bEnable)
+template <typename Clock>
+inline void BasicLink<Clock>::enable(const bool bEnable)
{
mController.enable(bEnable);
}
-inline bool Link::isStartStopSyncEnabled() const
+template <typename Clock>
+inline bool BasicLink<Clock>::isStartStopSyncEnabled() const
{
return mController.isStartStopSyncEnabled();
}
-inline void Link::enableStartStopSync(bool bEnable)
+template <typename Clock>
+inline void BasicLink<Clock>::enableStartStopSync(bool bEnable)
{
mController.enableStartStopSync(bEnable);
}
-inline std::size_t Link::numPeers() const
+template <typename Clock>
+inline std::size_t BasicLink<Clock>::numPeers() const
{
return mController.numPeers();
}
+template <typename Clock>
template <typename Callback>
-void Link::setNumPeersCallback(Callback callback)
+void BasicLink<Clock>::setNumPeersCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); };
}
+template <typename Clock>
template <typename Callback>
-void Link::setTempoCallback(Callback callback)
+void BasicLink<Clock>::setTempoCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); };
}
+template <typename Clock>
template <typename Callback>
-void Link::setStartStopCallback(Callback callback)
+void BasicLink<Clock>::setStartStopCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
mStartStopCallback = callback;
}
-inline Link::Clock Link::clock() const
+template <typename Clock>
+inline Clock BasicLink<Clock>::clock() const
{
return mClock;
}
-inline Link::SessionState Link::captureAudioSessionState() const
+template <typename Clock>
+inline typename BasicLink<Clock>::SessionState BasicLink<Clock>::captureAudioSessionState() const
{
- return detail::toSessionState(mController.clientStateRtSafe(), numPeers() > 0);
+ return detail::toSessionState<Clock>(mController.clientStateRtSafe(), numPeers() > 0);
}
-inline void Link::commitAudioSessionState(const Link::SessionState state)
+template <typename Clock>
+inline void BasicLink<Clock>::commitAudioSessionState(const typename BasicLink<Clock>::SessionState state)
{
mController.setClientStateRtSafe(
detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros()));
}
-inline Link::SessionState Link::captureAppSessionState() const
+template <typename Clock>
+inline typename BasicLink<Clock>::SessionState BasicLink<Clock>::captureAppSessionState() const
{
- return detail::toSessionState(mController.clientState(), numPeers() > 0);
+ return detail::toSessionState<Clock>(mController.clientState(), numPeers() > 0);
}
-inline void Link::commitAppSessionState(const Link::SessionState state)
+template <typename Clock>
+inline void BasicLink<Clock>::commitAppSessionState(const typename BasicLink<Clock>::SessionState state)
{
mController.setClientState(
detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros()));
@@ -150,7 +160,8 @@ inline void Link::commitAppSessionState(const Link::SessionState state)
// Link::SessionState
-inline Link::SessionState::SessionState(
+template <typename Clock>
+inline BasicLink<Clock>::SessionState::SessionState(
const link::ApiState state, const bool bRespectQuantum)
: mOriginalState(state)
, mState(state)
@@ -158,12 +169,14 @@ inline Link::SessionState::SessionState(
{
}
-inline double Link::SessionState::tempo() const
+template <typename Clock>
+inline double BasicLink<Clock>::SessionState::tempo() const
{
return mState.timeline.tempo.bpm();
}
-inline void Link::SessionState::setTempo(
+template <typename Clock>
+inline void BasicLink<Clock>::SessionState::setTempo(
const double bpm, const std::chrono::microseconds atTime)
{
const auto desiredTl = link::clampTempo(
@@ -172,28 +185,32 @@ inline void Link::SessionState::setTempo(
mState.timeline.timeOrigin = desiredTl.fromBeats(mState.timeline.beatOrigin);
}
-inline double Link::SessionState::beatAtTime(
+template <typename Clock>
+inline double BasicLink<Clock>::SessionState::beatAtTime(
const std::chrono::microseconds time, const double quantum) const
{
return link::toPhaseEncodedBeats(mState.timeline, time, link::Beats{quantum})
.floating();
}
-inline double Link::SessionState::phaseAtTime(
+template <typename Clock>
+inline double BasicLink<Clock>::SessionState::phaseAtTime(
const std::chrono::microseconds time, const double quantum) const
{
return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum})
.floating();
}
-inline std::chrono::microseconds Link::SessionState::timeAtBeat(
+template <typename Clock>
+inline std::chrono::microseconds BasicLink<Clock>::SessionState::timeAtBeat(
const double beat, const double quantum) const
{
return link::fromPhaseEncodedBeats(
mState.timeline, link::Beats{beat}, link::Beats{quantum});
}
-inline void Link::SessionState::requestBeatAtTime(
+template <typename Clock>
+inline void BasicLink<Clock>::SessionState::requestBeatAtTime(
const double beat, std::chrono::microseconds time, const double quantum)
{
if (mbRespectQuantum)
@@ -206,7 +223,8 @@ inline void Link::SessionState::requestBeatAtTime(
forceBeatAtTime(beat, time, quantum);
}
-inline void Link::SessionState::forceBeatAtTime(
+template <typename Clock>
+inline void BasicLink<Clock>::SessionState::forceBeatAtTime(
const double beat, const std::chrono::microseconds time, const double quantum)
{
// There are two components to the beat adjustment: a phase shift
@@ -220,23 +238,27 @@ inline void Link::SessionState::forceBeatAtTime(
mState.timeline.beatOrigin + (link::Beats{beat} - closestInPhase);
}
-inline void Link::SessionState::setIsPlaying(
+template <typename Clock>
+inline void BasicLink<Clock>::SessionState::setIsPlaying(
const bool isPlaying, const std::chrono::microseconds time)
{
mState.startStopState = {isPlaying, time};
}
-inline bool Link::SessionState::isPlaying() const
+template <typename Clock>
+inline bool BasicLink<Clock>::SessionState::isPlaying() const
{
return mState.startStopState.isPlaying;
}
-inline std::chrono::microseconds Link::SessionState::timeForIsPlaying() const
+template <typename Clock>
+inline std::chrono::microseconds BasicLink<Clock>::SessionState::timeForIsPlaying() const
{
return mState.startStopState.time;
}
-inline void Link::SessionState::requestBeatAtStartPlayingTime(
+template <typename Clock>
+inline void BasicLink<Clock>::SessionState::requestBeatAtStartPlayingTime(
const double beat, const double quantum)
{
if (isPlaying())
@@ -245,7 +267,8 @@ inline void Link::SessionState::requestBeatAtStartPlayingTime(
}
}
-inline void Link::SessionState::setIsPlayingAndRequestBeatAtTime(
+template <typename Clock>
+inline void BasicLink<Clock>::SessionState::setIsPlayingAndRequestBeatAtTime(
bool isPlaying, std::chrono::microseconds time, double beat, double quantum)
{
mState.startStopState = {isPlaying, time};
diff --git a/include/ableton/discovery/InterfaceScanner.hpp b/include/ableton/discovery/InterfaceScanner.hpp
index b694528..cb3adad 100644
--- a/include/ableton/discovery/InterfaceScanner.hpp
+++ b/include/ableton/discovery/InterfaceScanner.hpp
@@ -95,8 +95,7 @@ InterfaceScanner<Callback, IoContext> makeInterfaceScanner(
util::Injected<Callback> callback,
util::Injected<IoContext> io)
{
- using namespace std;
- return {period, move(callback), move(io)};
+ return {period, std::move(callback), std::move(io)};
}
} // namespace discovery
diff --git a/include/ableton/discovery/IpV4Interface.hpp b/include/ableton/discovery/IpV4Interface.hpp
index ad02fe3..9967f51 100644
--- a/include/ableton/discovery/IpV4Interface.hpp
+++ b/include/ableton/discovery/IpV4Interface.hpp
@@ -19,7 +19,7 @@
#pragma once
-#include <ableton/platforms/asio/AsioService.hpp>
+#include <ableton/platforms/asio/AsioWrapper.hpp>
#include <ableton/util/Injected.hpp>
namespace ableton
@@ -29,7 +29,7 @@ namespace discovery
inline asio::ip::udp::endpoint multicastEndpoint()
{
- return {asio::ip::address::from_string("224.76.78.75"), 20808};
+ return {asio::ip::address_v4::from_string("224.76.78.75"), 20808};
}
// Type tags for dispatching between unicast and multicast packets
diff --git a/include/ableton/discovery/NetworkByteStreamSerializable.hpp b/include/ableton/discovery/NetworkByteStreamSerializable.hpp
index 0a65415..6de27cc 100644
--- a/include/ableton/discovery/NetworkByteStreamSerializable.hpp
+++ b/include/ableton/discovery/NetworkByteStreamSerializable.hpp
@@ -24,6 +24,10 @@
#include <ableton/platforms/darwin/Darwin.hpp>
#elif defined(LINK_PLATFORM_LINUX)
#include <ableton/platforms/linux/Linux.hpp>
+#elif defined(LINK_PLATFORM_WINDOWS)
+#include <ableton/platforms/windows/Windows.hpp>
+#elif defined(ESP_PLATFORM)
+#include <ableton/platforms/esp32/Esp32.hpp>
#endif
#include <chrono>
@@ -281,7 +285,7 @@ struct Deserialize<std::chrono::microseconds>
static std::pair<std::chrono::microseconds, It> fromNetworkByteStream(It begin, It end)
{
using namespace std;
- auto result = Deserialize<int64_t>::fromNetworkByteStream(move(begin), move(end));
+ auto result = Deserialize<int64_t>::fromNetworkByteStream(std::move(begin), std::move(end));
return make_pair(chrono::microseconds{result.first}, result.second);
}
};
@@ -357,8 +361,8 @@ struct Deserialize<std::array<T, Size>>
using namespace std;
array<T, Size> result{};
auto resultIt =
- detail::deserializeContainer<T>(move(begin), move(end), move(result.begin()), Size);
- return make_pair(move(result), move(resultIt));
+ detail::deserializeContainer<T>(std::move(begin), std::move(end), std::move(result.begin()), Size);
+ return make_pair(std::move(result), std::move(resultIt));
}
};
@@ -385,11 +389,11 @@ struct Deserialize<std::vector<T, Alloc>>
{
using namespace std;
auto result_size =
- Deserialize<uint32_t>::fromNetworkByteStream(move(bytesBegin), bytesEnd);
+ Deserialize<uint32_t>::fromNetworkByteStream(std::move(bytesBegin), bytesEnd);
vector<T, Alloc> result;
auto resultIt = detail::deserializeContainer<T>(
- move(result_size.second), move(bytesEnd), back_inserter(result), result_size.first);
- return make_pair(move(result), move(resultIt));
+ std::move(result_size.second), std::move(bytesEnd), back_inserter(result), result_size.first);
+ return make_pair(std::move(result), std::move(resultIt));
}
};
@@ -416,7 +420,7 @@ struct Deserialize<std::tuple<X, Y>>
using namespace std;
auto xres = Deserialize<X>::fromNetworkByteStream(begin, end);
auto yres = Deserialize<Y>::fromNetworkByteStream(xres.second, end);
- return make_pair(make_tuple(move(xres.first), move(yres.first)), move(yres.second));
+ return make_pair(make_tuple(std::move(xres.first), std::move(yres.first)), std::move(yres.second));
}
};
@@ -446,8 +450,8 @@ struct Deserialize<std::tuple<X, Y, Z>>
auto xres = Deserialize<X>::fromNetworkByteStream(begin, end);
auto yres = Deserialize<Y>::fromNetworkByteStream(xres.second, end);
auto zres = Deserialize<Z>::fromNetworkByteStream(yres.second, end);
- return make_pair(make_tuple(move(xres.first), move(yres.first), move(zres.first)),
- move(zres.second));
+ return make_pair(make_tuple(std::move(xres.first), std::move(yres.first), std::move(zres.first)),
+ std::move(zres.second));
}
};
diff --git a/include/ableton/discovery/Payload.hpp b/include/ableton/discovery/Payload.hpp
index d15b9f3..2c93241 100644
--- a/include/ableton/discovery/Payload.hpp
+++ b/include/ableton/discovery/Payload.hpp
@@ -21,6 +21,7 @@
#include <ableton/discovery/NetworkByteStreamSerializable.hpp>
#include <functional>
+#include <sstream>
#include <unordered_map>
namespace ableton
@@ -56,7 +57,7 @@ struct PayloadEntryHeader
Size size;
tie(key, begin) = Deserialize<Key>::fromNetworkByteStream(begin, end);
tie(size, begin) = Deserialize<Size>::fromNetworkByteStream(begin, end);
- return make_pair(PayloadEntryHeader{move(key), move(size)}, move(begin));
+ return make_pair(PayloadEntryHeader{std::move(key), std::move(size)}, std::move(begin));
}
};
@@ -126,7 +127,7 @@ void parseByteStream(HandlerMap<It>& map, It bsBegin, const It bsEnd)
auto handlerIt = map.find(header.key);
if (handlerIt != end(map))
{
- handlerIt->second(move(valueBegin), move(valueEnd));
+ handlerIt->second(std::move(valueBegin), std::move(valueEnd));
}
}
}
@@ -286,7 +287,7 @@ template <typename... Entries, typename It, typename... Handlers>
void parsePayload(It begin, It end, Handlers... handlers)
{
using namespace std;
- ParsePayload<Entries...>::parse(move(begin), move(end), move(handlers)...);
+ ParsePayload<Entries...>::parse(std::move(begin), std::move(end), std::move(handlers)...);
}
} // namespace discovery
diff --git a/include/ableton/discovery/PeerGateway.hpp b/include/ableton/discovery/PeerGateway.hpp
index 7091e82..2d35f03 100644
--- a/include/ableton/discovery/PeerGateway.hpp
+++ b/include/ableton/discovery/PeerGateway.hpp
@@ -21,7 +21,6 @@
#include <ableton/discovery/UdpMessenger.hpp>
#include <ableton/discovery/v1/Messages.hpp>
-#include <ableton/platforms/asio/AsioService.hpp>
#include <ableton/util/SafeAsyncHandler.hpp>
#include <memory>
@@ -125,7 +124,7 @@ private:
auto newTo = make_pair(mPruneTimer.now() + std::chrono::seconds(ttl), peerId);
mPeerTimeouts.insert(
upper_bound(begin(mPeerTimeouts), end(mPeerTimeouts), newTo, TimeoutCompare{}),
- move(newTo));
+ std::move(newTo));
sawPeer(*mObserver, nodeState);
scheduleNextPruning();
@@ -244,8 +243,8 @@ IpV4Gateway<PeerObserver, NodeState, IoContext> makeIpV4Gateway(
auto iface = makeIpV4Interface<v1::kMaxMessageSize>(injectRef(*io), addr);
auto messenger =
- makeUdpMessenger(injectVal(move(iface)), move(state), injectRef(*io), ttl, ttlRatio);
- return {injectVal(move(messenger)), move(observer), move(io)};
+ makeUdpMessenger(injectVal(std::move(iface)), std::move(state), injectRef(*io), ttl, ttlRatio);
+ return {injectVal(std::move(messenger)), std::move(observer), std::move(io)};
}
} // namespace discovery
diff --git a/include/ableton/discovery/PeerGateways.hpp b/include/ableton/discovery/PeerGateways.hpp
index 16c2dac..ed5cfe5 100644
--- a/include/ableton/discovery/PeerGateways.hpp
+++ b/include/ableton/discovery/PeerGateways.hpp
@@ -222,7 +222,7 @@ std::unique_ptr<PeerGateways<NodeState, GatewayFactory, IoContext>> makePeerGate
using namespace std;
using Gateways = PeerGateways<NodeState, GatewayFactory, IoContext>;
return unique_ptr<Gateways>{
- new Gateways{rescanPeriod, move(state), move(factory), move(io)}};
+ new Gateways{rescanPeriod, std::move(state), std::move(factory), std::move(io)}};
}
} // namespace discovery
diff --git a/include/ableton/discovery/Socket.hpp b/include/ableton/discovery/Socket.hpp
deleted file mode 100644
index cbc5993..0000000
--- a/include/ableton/discovery/Socket.hpp
+++ /dev/null
@@ -1,139 +0,0 @@
-/* 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 <http://www.gnu.org/licenses/>.
- *
- * If you would like to incorporate Link into a proprietary software application,
- * please contact <link-devs@ableton.com>.
- */
-
-#pragma once
-
-#include <ableton/platforms/asio/AsioService.hpp>
-#include <ableton/platforms/asio/AsioWrapper.hpp>
-#include <ableton/util/SafeAsyncHandler.hpp>
-#include <array>
-#include <cassert>
-
-namespace ableton
-{
-namespace discovery
-{
-
-template <std::size_t MaxPacketSize>
-struct Socket
-{
- Socket(platforms::asio::AsioService& io)
- : mpImpl(std::make_shared<Impl>(io))
- {
- }
-
- Socket(const Socket&) = delete;
- Socket& operator=(const Socket&) = delete;
-
- Socket(Socket&& rhs)
- : mpImpl(std::move(rhs.mpImpl))
- {
- }
-
- std::size_t send(
- const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to)
- {
- assert(numBytes < MaxPacketSize);
- return mpImpl->mSocket.send_to(asio::buffer(pData, numBytes), to);
- }
-
- template <typename Handler>
- void receive(Handler handler)
- {
- mpImpl->mHandler = std::move(handler);
- mpImpl->mSocket.async_receive_from(
- asio::buffer(mpImpl->mReceiveBuffer, MaxPacketSize), mpImpl->mSenderEndpoint,
- util::makeAsyncSafe(mpImpl));
- }
-
- asio::ip::udp::endpoint endpoint() const
- {
- return mpImpl->mSocket.local_endpoint();
- }
-
- struct Impl
- {
- Impl(platforms::asio::AsioService& io)
- : mSocket(io.mService, asio::ip::udp::v4())
- {
- }
-
- ~Impl()
- {
- // Ignore error codes in shutdown and close as the socket may
- // have already been forcibly closed
- asio::error_code ec;
- mSocket.shutdown(asio::ip::udp::socket::shutdown_both, ec);
- mSocket.close(ec);
- }
-
- void operator()(const asio::error_code& error, const std::size_t numBytes)
- {
- if (!error && numBytes > 0 && numBytes <= MaxPacketSize)
- {
- const auto bufBegin = begin(mReceiveBuffer);
- mHandler(mSenderEndpoint, bufBegin, bufBegin + static_cast<ptrdiff_t>(numBytes));
- }
- }
-
- asio::ip::udp::socket mSocket;
- asio::ip::udp::endpoint mSenderEndpoint;
- using Buffer = std::array<uint8_t, MaxPacketSize>;
- Buffer mReceiveBuffer;
- using ByteIt = typename Buffer::const_iterator;
- std::function<void(const asio::ip::udp::endpoint&, ByteIt, ByteIt)> mHandler;
- };
-
- std::shared_ptr<Impl> mpImpl;
-};
-
-// Configure an asio socket for receiving multicast messages
-template <std::size_t MaxPacketSize>
-void configureMulticastSocket(Socket<MaxPacketSize>& socket,
- const asio::ip::address_v4& addr,
- const asio::ip::udp::endpoint& multicastEndpoint)
-{
- socket.mpImpl->mSocket.set_option(asio::ip::udp::socket::reuse_address(true));
- // ???
- socket.mpImpl->mSocket.set_option(asio::socket_base::broadcast(!addr.is_loopback()));
- // ???
- socket.mpImpl->mSocket.set_option(
- asio::ip::multicast::enable_loopback(addr.is_loopback()));
- socket.mpImpl->mSocket.set_option(asio::ip::multicast::outbound_interface(addr));
- // Is from_string("0.0.0.0") best approach?
- socket.mpImpl->mSocket.bind(
- {asio::ip::address::from_string("0.0.0.0"), multicastEndpoint.port()});
- socket.mpImpl->mSocket.set_option(
- asio::ip::multicast::join_group(multicastEndpoint.address().to_v4(), addr));
-}
-
-// Configure an asio socket for receiving unicast messages
-template <std::size_t MaxPacketSize>
-void configureUnicastSocket(
- Socket<MaxPacketSize>& socket, const asio::ip::address_v4& addr)
-{
- // ??? really necessary?
- socket.mpImpl->mSocket.set_option(
- asio::ip::multicast::enable_loopback(addr.is_loopback()));
- socket.mpImpl->mSocket.set_option(asio::ip::multicast::outbound_interface(addr));
- socket.mpImpl->mSocket.bind(asio::ip::udp::endpoint{addr, 0});
-}
-
-} // namespace discovery
-} // namespace ableton
diff --git a/include/ableton/discovery/UdpMessenger.hpp b/include/ableton/discovery/UdpMessenger.hpp
index d98f28a..c15c694 100644
--- a/include/ableton/discovery/UdpMessenger.hpp
+++ b/include/ableton/discovery/UdpMessenger.hpp
@@ -59,7 +59,7 @@ void sendUdpMessage(Interface& iface,
v1::MessageBuffer buffer;
const auto messageBegin = begin(buffer);
const auto messageEnd =
- v1::detail::encodeMessage(move(from), ttl, messageType, payload, messageBegin);
+ v1::detail::encodeMessage(std::move(from), ttl, messageType, payload, messageBegin);
const auto numBytes = static_cast<size_t>(distance(messageBegin, messageEnd));
try
{
diff --git a/include/ableton/discovery/v1/Messages.hpp b/include/ableton/discovery/v1/Messages.hpp
index 81d0955..73ba8c3 100644
--- a/include/ableton/discovery/v1/Messages.hpp
+++ b/include/ableton/discovery/v1/Messages.hpp
@@ -82,7 +82,7 @@ struct MessageHeader
tie(header.ident, begin) =
Deserialize<decltype(header.ident)>::fromNetworkByteStream(begin, end);
- return make_pair(move(header), move(begin));
+ return make_pair(std::move(header), std::move(begin));
}
};
@@ -111,7 +111,7 @@ It encodeMessage(NodeId from,
{
return toNetworkByteStream(
payload, toNetworkByteStream(
- header, copy(begin(kProtocolHeader), end(kProtocolHeader), move(out))));
+ header, copy(begin(kProtocolHeader), end(kProtocolHeader), std::move(out))));
}
else
{
@@ -160,7 +160,7 @@ std::pair<MessageHeader<NodeId>, It> parseMessageHeader(It bytesBegin, const It
tie(header, bytesBegin) = MessageHeader<NodeId>::fromNetworkByteStream(
bytesBegin + protocolHeaderSize, bytesEnd);
}
- return make_pair(move(header), move(bytesBegin));
+ return make_pair(std::move(header), std::move(bytesBegin));
}
} // namespace v1
diff --git a/include/ableton/link/Beats.hpp b/include/ableton/link/Beats.hpp
index 28e5482..bf3d45e 100644
--- a/include/ableton/link/Beats.hpp
+++ b/include/ableton/link/Beats.hpp
@@ -22,60 +22,79 @@
#include <ableton/discovery/NetworkByteStreamSerializable.hpp>
#include <cmath>
#include <cstdint>
-#include <tuple>
namespace ableton
{
namespace link
{
-struct Beats : std::tuple<std::int64_t>
+struct Beats
{
Beats() = default;
explicit Beats(const double beats)
- : std::tuple<std::int64_t>(llround(beats * 1e6))
+ : mValue(std::llround(beats * 1e6))
{
}
explicit Beats(const std::int64_t microBeats)
- : std::tuple<std::int64_t>(microBeats)
+ : mValue(microBeats)
{
}
double floating() const
{
- return microBeats() / 1e6;
+ return static_cast<double>(mValue) / 1e6;
}
std::int64_t microBeats() const
{
- return std::get<0>(*this);
+ return mValue;
}
Beats operator-() const
{
- return Beats{-microBeats()};
+ return Beats{-mValue};
}
friend Beats abs(const Beats b)
{
- return Beats{std::abs(b.microBeats())};
+ return Beats{std::abs(b.mValue)};
}
friend Beats operator+(const Beats lhs, const Beats rhs)
{
- return Beats{lhs.microBeats() + rhs.microBeats()};
+ return Beats{lhs.mValue + rhs.mValue};
}
friend Beats operator-(const Beats lhs, const Beats rhs)
{
- return Beats{lhs.microBeats() - rhs.microBeats()};
+ return Beats{lhs.mValue - rhs.mValue};
}
friend Beats operator%(const Beats lhs, const Beats rhs)
{
- return rhs == Beats{0.} ? Beats{0.} : Beats{lhs.microBeats() % rhs.microBeats()};
+ return Beats{rhs.mValue == 0 ? 0 : (lhs.mValue % rhs.mValue)};
+ }
+
+ friend bool operator<(const Beats lhs, const Beats rhs)
+ {
+ return lhs.mValue < rhs.mValue;
+ }
+
+ friend bool operator>(const Beats lhs, const Beats rhs)
+ {
+ return lhs.mValue > rhs.mValue;
+ }
+
+ friend bool operator==(const Beats lhs, const Beats rhs)
+ {
+ return lhs.mValue == rhs.mValue;
+ }
+
+ friend bool operator!=(const Beats lhs, const Beats rhs)
+ {
+ return lhs.mValue != rhs.mValue;
}
// Model the NetworkByteStreamSerializable concept
@@ -97,6 +116,9 @@ struct Beats : std::tuple<std::int64_t>
std::move(begin), std::move(end));
return std::make_pair(Beats{result.first}, std::move(result.second));
}
+
+private:
+ std::int64_t mValue = 0;
};
} // namespace link
diff --git a/include/ableton/link/CircularFifo.hpp b/include/ableton/link/CircularFifo.hpp
index 5e3578f..8ee8a26 100644
--- a/include/ableton/link/CircularFifo.hpp
+++ b/include/ableton/link/CircularFifo.hpp
@@ -19,10 +19,12 @@
#pragma once
-#include <ableton/link/Optional.hpp>
#include <array>
#include <atomic>
#include <cassert>
+#include <cstddef>
+
+#include <ableton/link/Optional.hpp>
namespace ableton
{
diff --git a/include/ableton/link/Controller.hpp b/include/ableton/link/Controller.hpp
index 99abc9d..ef802f9 100644
--- a/include/ableton/link/Controller.hpp
+++ b/include/ableton/link/Controller.hpp
@@ -59,8 +59,7 @@ inline ClientState initClientState(const SessionState& sessionState)
const auto hostTime = sessionState.ghostXForm.ghostToHost(std::chrono::microseconds{0});
return {
Timeline{sessionState.timeline.tempo, sessionState.timeline.beatOrigin, hostTime},
- StartStopState{sessionState.startStopState.isPlaying,
- sessionState.startStopState.beats, hostTime}};
+ ClientStartStopState{sessionState.startStopState.isPlaying, hostTime, hostTime}};
}
inline RtClientState initRtClientState(const ClientState& clientState)
@@ -75,36 +74,35 @@ inline RtClientState initRtClientState(const ClientState& clientState)
const auto kLocalModGracePeriod = std::chrono::milliseconds(1000);
const auto kRtHandlerFallbackPeriod = kLocalModGracePeriod / 2;
-inline StartStopState selectPreferredStartStopState(
- const StartStopState currentStartStopState, const StartStopState startStopState)
+inline ClientStartStopState selectPreferredStartStopState(
+ const ClientStartStopState currentStartStopState,
+ const ClientStartStopState startStopState)
{
return startStopState.timestamp > currentStartStopState.timestamp
? startStopState
: currentStartStopState;
}
-inline StartStopState mapStartStopStateFromSessionToClient(
+inline ClientStartStopState mapStartStopStateFromSessionToClient(
const StartStopState& sessionStartStopState,
- const Timeline& clientTimeline,
const Timeline& sessionTimeline,
const GhostXForm& xForm)
{
- const auto clientBeats = clientTimeline.toBeats(
- xForm.ghostToHost(sessionTimeline.fromBeats(sessionStartStopState.beats)));
- const auto clientTime = xForm.ghostToHost(sessionStartStopState.timestamp);
- return StartStopState{sessionStartStopState.isPlaying, clientBeats, clientTime};
+ const auto time =
+ xForm.ghostToHost(sessionTimeline.fromBeats(sessionStartStopState.beats));
+ const auto timestamp = xForm.ghostToHost(sessionStartStopState.timestamp);
+ return ClientStartStopState{sessionStartStopState.isPlaying, time, timestamp};
}
inline StartStopState mapStartStopStateFromClientToSession(
- const StartStopState& clientStartStopState,
- const Timeline& clientTimeline,
+ const ClientStartStopState& clientStartStopState,
const Timeline& sessionTimeline,
const GhostXForm& xForm)
{
- const auto sessionBeats = sessionTimeline.toBeats(
- xForm.hostToGhost(clientTimeline.fromBeats(clientStartStopState.beats)));
- const auto sessionTime = xForm.hostToGhost(clientStartStopState.timestamp);
- return StartStopState{clientStartStopState.isPlaying, sessionBeats, sessionTime};
+ const auto sessionBeats =
+ sessionTimeline.toBeats(xForm.hostToGhost(clientStartStopState.time));
+ const auto timestamp = xForm.hostToGhost(clientStartStopState.timestamp);
+ return StartStopState{clientStartStopState.isPlaying, sessionBeats, timestamp};
}
} // namespace detail
@@ -120,6 +118,7 @@ template <typename PeerCountCallback,
typename TempoCallback,
typename StartStopStateCallback,
typename Clock,
+ typename Random,
typename IoContext>
class Controller
{
@@ -133,7 +132,7 @@ public:
: mTempoCallback(std::move(tempoCallback))
, mStartStopStateCallback(std::move(startStopStateCallback))
, mClock(std::move(clock))
- , mNodeId(NodeId::random())
+ , mNodeId(NodeId::random<Random>())
, mSessionId(mNodeId)
, mSessionState(detail::initSessionState(tempo, mClock))
, mClientState(detail::initClientState(mSessionState))
@@ -170,6 +169,11 @@ public:
Controller& operator=(const Controller&) = delete;
Controller& operator=(Controller&&) = delete;
+ ~Controller()
+ {
+ mIo->stop();
+ }
+
void enable(const bool bEnable)
{
const bool bWasEnabled = mEnabled.exchange(bEnable);
@@ -178,6 +182,9 @@ public:
mIo->async([this, bEnable] {
if (bEnable)
{
+ // Process the pending client states to make sure we don't push one after we
+ // have joined a running session
+ mRtClientStateSetter.processPendingClientStates();
// Always reset when first enabling to avoid hijacking
// tempo in existing sessions
resetState();
@@ -352,8 +359,11 @@ private:
mSessionState.ghostXForm));
}
- void updateSessionTiming(const Timeline newTimeline, const GhostXForm newXForm)
+ void updateSessionTiming(Timeline newTimeline, const GhostXForm newXForm)
{
+ // Clamp the session tempo because it may slightly overshoot (999 bpm is
+ // transferred as 60606 us/beat and received as 999.000999... bpm).
+ newTimeline = clampTempo(newTimeline);
const auto oldTimeline = mSessionState.timeline;
const auto oldXForm = mSessionState.ghostXForm;
@@ -365,11 +375,20 @@ private:
mSessionState.ghostXForm = newXForm;
}
- // Update the client timeline based on the new session timing data
+ // Update the client timeline and start stop state based on the new session timing
{
- std::lock_guard<std::mutex> lock(mClientStateGuard);
+ std::lock_guard<std::mutex> timelineLock(mClientStateGuard);
mClientState.timeline = updateClientTimelineFromSession(mClientState.timeline,
mSessionState.timeline, mClock.micros(), mSessionState.ghostXForm);
+ // Don't pass the start stop state to the client when start stop sync is disabled
+ // or when we have a default constructed start stop state
+ if (mStartStopSyncEnabled && mSessionState.startStopState != StartStopState{})
+ {
+ std::lock_guard<std::mutex> startStopStateLock(mSessionStateGuard);
+ mClientState.startStopState =
+ detail::mapStartStopStateFromSessionToClient(mSessionState.startStopState,
+ mSessionState.timeline, mSessionState.ghostXForm);
+ }
}
if (oldTimeline.tempo != newTimeline.tempo)
@@ -413,9 +432,8 @@ private:
{
{
std::lock_guard<std::mutex> lock(mClientStateGuard);
- mClientState.startStopState =
- detail::mapStartStopStateFromSessionToClient(startStopState,
- mClientState.timeline, mSessionState.timeline, mSessionState.ghostXForm);
+ mClientState.startStopState = detail::mapStartStopStateFromSessionToClient(
+ startStopState, mSessionState.timeline, mSessionState.ghostXForm);
}
invokeStartStopStateCallbackIfChanged();
}
@@ -449,7 +467,7 @@ private:
std::lock_guard<std::mutex> lock(mClientStateGuard);
mSessionState.startStopState =
detail::mapStartStopStateFromClientToSession(*clientState.startStopState,
- mClientState.timeline, mSessionState.timeline, mSessionState.ghostXForm);
+ mSessionState.timeline, mSessionState.ghostXForm);
mClientState.startStopState = *clientState.startStopState;
}
@@ -491,9 +509,10 @@ private:
const bool sessionIdChanged = mSessionId != session.sessionId;
mSessionId = session.sessionId;
- // Prevent passing the start stop state of the previous session to the new one.
+ // Prevent passing the state of the previous session to the new one.
if (sessionIdChanged)
{
+ mRtClientStateSetter.processPendingClientStates();
resetSessionStartStopState();
}
@@ -510,7 +529,7 @@ private:
void resetState()
{
- mNodeId = NodeId::random();
+ mNodeId = NodeId::random<Random>();
mSessionId = mNodeId;
const auto xform = detail::initXForm(mClock);
@@ -551,7 +570,8 @@ private:
RtClientStateSetter(Controller& controller)
: mController(controller)
, mCallbackDispatcher(
- [this] { processPendingClientStates(); }, detail::kRtHandlerFallbackPeriod)
+ [this] { mController.mIo->async([this]() { processPendingClientStates(); }); },
+ detail::kRtHandlerFallbackPeriod)
{
}
@@ -565,6 +585,12 @@ private:
return success;
}
+ void processPendingClientStates()
+ {
+ const auto clientState = buildMergedPendingClientState();
+ mController.handleRtClientState(clientState);
+ }
+
private:
IncomingClientState buildMergedPendingClientState()
{
@@ -584,13 +610,6 @@ private:
return clientState;
}
- void processPendingClientStates()
- {
- const auto clientState = buildMergedPendingClientState();
- mController.mIo->async(
- [this, clientState]() { mController.handleRtClientState(clientState); });
- }
-
Controller& mController;
// Assuming a wake up time of one ms for the threads owned by the CallbackDispatcher
// and the ioService, buffering 16 client states allows to set eight client states
diff --git a/include/ableton/link/Gateway.hpp b/include/ableton/link/Gateway.hpp
index e904f25..086abfa 100644
--- a/include/ableton/link/Gateway.hpp
+++ b/include/ableton/link/Gateway.hpp
@@ -38,13 +38,12 @@ public:
NodeState nodeState,
GhostXForm ghostXForm,
Clock clock)
- // TODO: Measurement should have an IoContext injected
: mIo(std::move(io))
, mMeasurement(addr,
nodeState.sessionId,
std::move(ghostXForm),
std::move(clock),
- util::injectVal(channel(mIo->log(), "gateway@" + addr.to_string())))
+ util::injectVal(mIo->clone()))
, mPeerGateway(discovery::makeIpV4Gateway(util::injectRef(*mIo),
std::move(addr),
std::move(observer),
@@ -84,7 +83,7 @@ public:
private:
util::Injected<IoContext> mIo;
- MeasurementService<Clock, typename util::Injected<IoContext>::type::Log> mMeasurement;
+ MeasurementService<Clock, typename std::remove_reference<IoContext>::type> mMeasurement;
discovery::
IpV4Gateway<PeerObserver, PeerState, typename util::Injected<IoContext>::type&>
mPeerGateway;
diff --git a/include/ableton/link/GhostXForm.hpp b/include/ableton/link/GhostXForm.hpp
index 3a71a00..2971d7b 100644
--- a/include/ableton/link/GhostXForm.hpp
+++ b/include/ableton/link/GhostXForm.hpp
@@ -33,12 +33,14 @@ struct GhostXForm
{
microseconds hostToGhost(const microseconds hostTime) const
{
- return microseconds{llround(slope * hostTime.count())} + intercept;
+ return microseconds{llround(slope * static_cast<double>(hostTime.count()))}
+ + intercept;
}
microseconds ghostToHost(const microseconds ghostTime) const
{
- return microseconds{llround((ghostTime - intercept).count() / slope)};
+ return microseconds{
+ llround(static_cast<double>((ghostTime - intercept).count()) / slope)};
}
friend bool operator==(const GhostXForm lhs, const GhostXForm rhs)
diff --git a/include/ableton/link/HostTimeFilter.hpp b/include/ableton/link/HostTimeFilter.hpp
index d1d3085..2fdbc94 100644
--- a/include/ableton/link/HostTimeFilter.hpp
+++ b/include/ableton/link/HostTimeFilter.hpp
@@ -28,7 +28,7 @@ namespace ableton
namespace link
{
-template <class T>
+template <class Clock>
class HostTimeFilter
{
static const std::size_t kNumPoints = 512;
@@ -75,7 +75,7 @@ public:
private:
std::size_t mIndex;
Points mPoints;
- T mHostTimeSampler;
+ Clock mHostTimeSampler;
};
} // namespace link
diff --git a/include/ableton/link/Kalman.hpp b/include/ableton/link/Kalman.hpp
index 4abff9a..06c6387 100644
--- a/include/ableton/link/Kalman.hpp
+++ b/include/ableton/link/Kalman.hpp
@@ -55,14 +55,14 @@ struct Kalman
meanOfDiffs += (mMeasuredValues[k] - mFilterValues[k]);
}
- meanOfDiffs /= (mVarianceLength);
+ meanOfDiffs /= static_cast<double>(mVarianceLength);
for (size_t i = 0; i < (mVarianceLength); i++)
{
vVar += (pow(mMeasuredValues[i] - mFilterValues[i] - meanOfDiffs, 2.0));
}
- vVar /= (mVarianceLength - 1);
+ vVar /= static_cast<double>(mVarianceLength - 1);
return vVar;
}
@@ -78,7 +78,7 @@ struct Kalman
- mFilterValues[(mCounter - k - 2) % mVarianceLength]);
}
- meanOfDiffs /= (mVarianceLength);
+ meanOfDiffs /= static_cast<double>(mVarianceLength);
for (size_t i = 0; i < (mVarianceLength); i++)
{
@@ -87,7 +87,7 @@ struct Kalman
2.0));
}
- wVar /= (mVarianceLength - 1);
+ wVar /= static_cast<double>(mVarianceLength - 1);
return wVar;
}
diff --git a/include/ableton/link/Measurement.hpp b/include/ableton/link/Measurement.hpp
index 83bc0d9..c8d0f2b 100644
--- a/include/ableton/link/Measurement.hpp
+++ b/include/ableton/link/Measurement.hpp
@@ -20,13 +20,12 @@
#pragma once
#include <ableton/discovery/Payload.hpp>
-#include <ableton/discovery/Socket.hpp>
#include <ableton/link/PayloadEntries.hpp>
#include <ableton/link/PeerState.hpp>
#include <ableton/link/SessionId.hpp>
#include <ableton/link/v1/Messages.hpp>
-#include <ableton/platforms/asio/AsioService.hpp>
#include <ableton/util/Injected.hpp>
+#include <ableton/util/SafeAsyncHandler.hpp>
#include <chrono>
#include <memory>
@@ -35,83 +34,57 @@ namespace ableton
namespace link
{
-template <typename IoService, typename Clock, typename Socket, typename Log>
+template <typename Clock, typename IoContext>
struct Measurement
{
using Point = std::pair<double, double>;
using Callback = std::function<void(std::vector<Point>)>;
using Micros = std::chrono::microseconds;
- using Timer = typename IoService::Timer;
static const std::size_t kNumberDataPoints = 100;
static const std::size_t kNumberMeasurements = 5;
- Measurement() = default;
-
Measurement(const PeerState& state,
Callback callback,
asio::ip::address_v4 address,
Clock clock,
- util::Injected<Log> log)
- : mpIo(new IoService{})
- , mpImpl(std::make_shared<Impl>(*mpIo,
- std::move(state),
+ IoContext io)
+ : mIo(std::move(io))
+ , mpImpl(std::make_shared<Impl>(std::move(state),
std::move(callback),
std::move(address),
std::move(clock),
- std::move(log)))
+ mIo))
{
mpImpl->listen();
}
- Measurement(Measurement&& rhs)
- : mpIo(std::move(rhs.mpIo))
- , mpImpl(std::move(rhs.mpImpl))
- {
- }
-
- ~Measurement()
- {
- postImplDestruction();
- }
-
- Measurement& operator=(Measurement&& rhs)
- {
- postImplDestruction();
- mpIo = std::move(rhs.mpIo);
- mpImpl = std::move(rhs.mpImpl);
- return *this;
- }
-
- void postImplDestruction()
- {
- // Post destruction of the impl object into the io thread if valid
- if (mpIo)
- {
- mpIo->post(ImplDeleter{*this});
- }
- }
+ Measurement(const Measurement&) = delete;
+ Measurement& operator=(Measurement&) = delete;
+ Measurement(const Measurement&&) = delete;
+ Measurement& operator=(Measurement&&) = delete;
struct Impl : std::enable_shared_from_this<Impl>
{
- Impl(IoService& io,
- const PeerState& state,
+ using Socket = typename IoContext::template Socket<v1::kMaxMessageSize>;
+ using Timer = typename IoContext::Timer;
+ using Log = typename IoContext::Log;
+
+ Impl(const PeerState& state,
Callback callback,
asio::ip::address_v4 address,
Clock clock,
- util::Injected<Log> log)
- : mpSocket(std::make_shared<Socket>(io))
+ IoContext& io)
+ : mSocket(io.template openUnicastSocket<v1::kMaxMessageSize>(address))
, mSessionId(state.nodeState.sessionId)
, mEndpoint(state.endpoint)
, mCallback(std::move(callback))
, mClock(std::move(clock))
- , mTimer(util::injectVal(io.makeTimer()))
+ , mTimer(io.makeTimer())
, mMeasurementsStarted(0)
- , mLog(std::move(log))
+ , mLog(channel(io.log(), "Measurement on gateway@" + address.to_string()))
, mSuccess(false)
{
- configureUnicastSocket(*mpSocket, address);
-
const auto ht = HostTime{mClock.micros()};
sendPing(mEndpoint, discovery::makePayload(ht));
resetTimer();
@@ -119,9 +92,9 @@ struct Measurement
void resetTimer()
{
- mTimer->cancel();
- mTimer->expires_from_now(std::chrono::milliseconds(50));
- mTimer->async_wait([this](const typename Timer::ErrorCode e) {
+ mTimer.cancel();
+ mTimer.expires_from_now(std::chrono::milliseconds(50));
+ mTimer.async_wait([this](const typename Timer::ErrorCode e) {
if (!e)
{
if (mMeasurementsStarted < kNumberMeasurements)
@@ -141,7 +114,7 @@ struct Measurement
void listen()
{
- mpSocket->receive(util::makeAsyncSafe(this->shared_from_this()));
+ mSocket.receive(util::makeAsyncSafe(this->shared_from_this()));
}
// Operator to handle incoming messages on the interface
@@ -156,7 +129,7 @@ struct Measurement
if (header.messageType == v1::kPong)
{
- debug(*mLog) << "Received Pong message from " << from;
+ debug(mLog) << "Received Pong message from " << from;
// parse for all entries
SessionId sessionId{};
@@ -175,7 +148,7 @@ struct Measurement
}
catch (const std::runtime_error& err)
{
- warning(*mLog) << "Failed parsing payload, caught exception: " << err.what();
+ warning(mLog) << "Failed parsing payload, caught exception: " << err.what();
listen();
return;
}
@@ -215,7 +188,7 @@ struct Measurement
}
else
{
- debug(*mLog) << "Received invalid message from " << from;
+ debug(mLog) << "Received invalid message from " << from;
listen();
}
}
@@ -230,40 +203,40 @@ struct Measurement
try
{
- mpSocket->send(buffer.data(), numBytes, to);
+ mSocket.send(buffer.data(), numBytes, to);
}
catch (const std::runtime_error& err)
{
- info(*mLog) << "Failed to send Ping to " << to.address().to_string() << ": "
- << err.what();
+ info(mLog) << "Failed to send Ping to " << to.address().to_string() << ": "
+ << err.what();
}
}
void finish()
{
- mTimer->cancel();
+ mTimer.cancel();
mCallback(std::move(mData));
mData = {};
mSuccess = true;
- debug(*mLog) << "Measuring " << mEndpoint << " done.";
+ debug(mLog) << "Measuring " << mEndpoint << " done.";
}
void fail()
{
mCallback(std::vector<Point>{});
mData = {};
- debug(*mLog) << "Measuring " << mEndpoint << " failed.";
+ debug(mLog) << "Measuring " << mEndpoint << " failed.";
}
- std::shared_ptr<Socket> mpSocket;
+ Socket mSocket;
SessionId mSessionId;
asio::ip::udp::endpoint mEndpoint;
std::vector<std::pair<double, double>> mData;
Callback mCallback;
Clock mClock;
- util::Injected<typename IoService::Timer> mTimer;
+ Timer mTimer;
std::size_t mMeasurementsStarted;
- util::Injected<Log> mLog;
+ Log mLog;
bool mSuccess;
};
@@ -288,7 +261,7 @@ struct Measurement
std::shared_ptr<Impl> mpImpl;
};
- std::unique_ptr<IoService> mpIo;
+ IoContext mIo;
std::shared_ptr<Impl> mpImpl;
};
diff --git a/include/ableton/link/MeasurementEndpointV4.hpp b/include/ableton/link/MeasurementEndpointV4.hpp
index 2fdd3ec..a7551b3 100644
--- a/include/ableton/link/MeasurementEndpointV4.hpp
+++ b/include/ableton/link/MeasurementEndpointV4.hpp
@@ -53,12 +53,12 @@ struct MeasurementEndpointV4
{
using namespace std;
auto addrRes =
- discovery::Deserialize<std::uint32_t>::fromNetworkByteStream(move(begin), end);
+ discovery::Deserialize<std::uint32_t>::fromNetworkByteStream(std::move(begin), end);
auto portRes = discovery::Deserialize<std::uint16_t>::fromNetworkByteStream(
- move(addrRes.second), end);
- return make_pair(MeasurementEndpointV4{{asio::ip::address_v4{move(addrRes.first)},
- move(portRes.first)}},
- move(portRes.second));
+ std::move(addrRes.second), end);
+ return make_pair(MeasurementEndpointV4{{asio::ip::address_v4{std::move(addrRes.first)},
+ std::move(portRes.first)}},
+ std::move(portRes.second));
}
asio::ip::udp::endpoint ep;
diff --git a/include/ableton/link/MeasurementService.hpp b/include/ableton/link/MeasurementService.hpp
index 58c7075..f590715 100644
--- a/include/ableton/link/MeasurementService.hpp
+++ b/include/ableton/link/MeasurementService.hpp
@@ -19,7 +19,6 @@
#pragma once
-#include <ableton/discovery/Socket.hpp>
#include <ableton/link/GhostXForm.hpp>
#include <ableton/link/Kalman.hpp>
#include <ableton/link/LinearRegression.hpp>
@@ -28,48 +27,34 @@
#include <ableton/link/PingResponder.hpp>
#include <ableton/link/SessionId.hpp>
#include <ableton/link/v1/Messages.hpp>
-#include <ableton/platforms/asio/AsioService.hpp>
-#include <ableton/util/Log.hpp>
#include <map>
#include <memory>
-#include <thread>
namespace ableton
{
namespace link
{
-template <typename Clock, typename Log>
+template <typename Clock, typename IoContext>
class MeasurementService
{
public:
+ using IoType = util::Injected<IoContext>;
using Point = std::pair<double, double>;
-
- using MeasurementInstance = Measurement<platforms::asio::AsioService,
- Clock,
- discovery::Socket<v1::kMaxMessageSize>,
- Log>;
-
- using MeasurementServicePingResponder = PingResponder<platforms::asio::AsioService&,
- Clock,
- discovery::Socket<v1::kMaxMessageSize>,
- Log>;
-
- static const std::size_t kNumberThreads = 1;
+ using MeasurementInstance = Measurement<Clock, IoContext>;
MeasurementService(asio::ip::address_v4 address,
SessionId sessionId,
GhostXForm ghostXForm,
Clock clock,
- util::Injected<Log> log)
+ IoType io)
: mClock(std::move(clock))
- , mLog(std::move(log))
+ , mIo(std::move(io))
, mPingResponder(std::move(address),
std::move(sessionId),
std::move(ghostXForm),
- util::injectRef(mIo),
mClock,
- mLog)
+ util::injectRef(*mIo))
{
}
@@ -78,10 +63,10 @@ public:
~MeasurementService()
{
- // Clear the measurement map in the io service so that whatever
+ // Clear the measurement map in the IoContext so that whatever
// cleanup code executes in response to the destruction of the
- // measurement objects still have access to the io service
- mIo.post([this] { mMeasurementMap.clear(); });
+ // measurement objects still have access to the IoContext.
+ mIo->async([this] { mMeasurementMap.clear(); });
}
void updateNodeState(const SessionId& sessionId, const GhostXForm& xform)
@@ -100,19 +85,22 @@ public:
{
using namespace std;
- mIo.post([this, state, handler] {
+ mIo->async([this, state, handler] {
const auto nodeId = state.nodeState.nodeId;
auto addr = mPingResponder.endpoint().address().to_v4();
auto callback = CompletionCallback<Handler>{*this, nodeId, handler};
try
{
+
mMeasurementMap[nodeId] =
- MeasurementInstance{state, move(callback), move(addr), mClock, mLog};
+ std::unique_ptr<MeasurementInstance>(new MeasurementInstance{
+ state, std::move(callback), std::move(addr), mClock, mIo->clone()});
}
catch (const runtime_error& err)
{
- info(*mLog) << "Failed to measure. Reason: " << err.what();
+ info(mIo->log()) << "gateway@" + addr.to_string()
+ << " Failed to measure. Reason: " << err.what();
handler(GhostXForm{});
}
});
@@ -142,14 +130,14 @@ private:
using namespace std;
using std::chrono::microseconds;
- // Post this to the measurement service's io service so that we
+ // Post this to the measurement service's IoContext so that we
// don't delete the measurement object in its stack. Capture all
// needed data separately from this, since this object may be
// gone by the time the block gets executed.
auto nodeId = mNodeId;
auto handler = mHandler;
- auto& measurementMap = mService.mMeasurementMap;
- mService.mIo.post([nodeId, handler, &measurementMap, data] {
+ auto& measurementMap = mMeasurementService.mMeasurementMap;
+ mMeasurementService.mIo->async([nodeId, handler, &measurementMap, data] {
const auto it = measurementMap.find(nodeId);
if (it != measurementMap.end())
{
@@ -166,20 +154,19 @@ private:
});
}
- MeasurementService& mService;
+ MeasurementService& mMeasurementService;
NodeId mNodeId;
Handler mHandler;
};
- // Make sure the measurement map outlives the io service so that the rest of
+ // Make sure the measurement map outlives the IoContext so that the rest of
// the members are guaranteed to be valid when any final handlers
// are begin run.
- using MeasurementMap = std::map<NodeId, MeasurementInstance>;
+ using MeasurementMap = std::map<NodeId, std::unique_ptr<MeasurementInstance>>;
MeasurementMap mMeasurementMap;
Clock mClock;
- util::Injected<Log> mLog;
- platforms::asio::AsioService mIo;
- MeasurementServicePingResponder mPingResponder;
+ IoType mIo;
+ PingResponder<Clock, IoContext> mPingResponder;
};
} // namespace link
diff --git a/include/ableton/link/NodeId.hpp b/include/ableton/link/NodeId.hpp
index c99899a..5076320 100644
--- a/include/ableton/link/NodeId.hpp
+++ b/include/ableton/link/NodeId.hpp
@@ -23,7 +23,6 @@
#include <algorithm>
#include <array>
#include <cstdint>
-#include <random>
#include <string>
namespace ableton
@@ -42,18 +41,15 @@ struct NodeId : NodeIdArray
{
}
+ template <typename Random>
static NodeId random()
{
using namespace std;
+ NodeId nodeId;
- random_device rd;
- mt19937 gen(rd());
- // uint8_t not standardized for this type - use unsigned
- uniform_int_distribution<unsigned> dist(33, 126); // printable ascii chars
+ Random random;
+ generate(nodeId.begin(), nodeId.end(), [&] { return random(); });
- NodeId nodeId;
- generate(
- nodeId.begin(), nodeId.end(), [&] { return static_cast<uint8_t>(dist(gen)); });
return nodeId;
}
@@ -73,8 +69,8 @@ struct NodeId : NodeIdArray
{
using namespace std;
auto result =
- discovery::Deserialize<NodeIdArray>::fromNetworkByteStream(move(begin), move(end));
- return make_pair(NodeId(move(result.first)), move(result.second));
+ discovery::Deserialize<NodeIdArray>::fromNetworkByteStream(std::move(begin), std::move(end));
+ return make_pair(NodeId(std::move(result.first)), std::move(result.second));
}
};
diff --git a/include/ableton/link/NodeState.hpp b/include/ableton/link/NodeState.hpp
index c7b9961..c7d8178 100644
--- a/include/ableton/link/NodeState.hpp
+++ b/include/ableton/link/NodeState.hpp
@@ -56,13 +56,13 @@ struct NodeState
static NodeState fromPayload(NodeId nodeId, It begin, It end)
{
using namespace std;
- auto nodeState = NodeState{move(nodeId), {}, {}, {}};
- discovery::parsePayload<Timeline, SessionMembership, StartStopState>(move(begin),
- move(end), [&nodeState](Timeline tl) { nodeState.timeline = move(tl); },
+ auto nodeState = NodeState{std::move(nodeId), {}, {}, {}};
+ discovery::parsePayload<Timeline, SessionMembership, StartStopState>(std::move(begin),
+ std::move(end), [&nodeState](Timeline tl) { nodeState.timeline = std::move(tl); },
[&nodeState](SessionMembership membership) {
- nodeState.sessionId = move(membership.sessionId);
+ nodeState.sessionId = std::move(membership.sessionId);
},
- [&nodeState](StartStopState ststst) { nodeState.startStopState = move(ststst); });
+ [&nodeState](StartStopState ststst) { nodeState.startStopState = std::move(ststst); });
return nodeState;
}
diff --git a/include/ableton/link/PayloadEntries.hpp b/include/ableton/link/PayloadEntries.hpp
index bc5a458..cefd35f 100644
--- a/include/ableton/link/PayloadEntries.hpp
+++ b/include/ableton/link/PayloadEntries.hpp
@@ -57,8 +57,8 @@ struct HostTime
{
using namespace std;
auto result = discovery::Deserialize<chrono::microseconds>::fromNetworkByteStream(
- move(begin), move(end));
- return make_pair(HostTime{move(result.first)}, move(result.second));
+ std::move(begin), std::move(end));
+ return make_pair(HostTime{std::move(result.first)}, std::move(result.second));
}
std::chrono::microseconds time;
@@ -93,8 +93,8 @@ struct GHostTime
{
using namespace std;
auto result = discovery::Deserialize<chrono::microseconds>::fromNetworkByteStream(
- move(begin), move(end));
- return make_pair(GHostTime{move(result.first)}, move(result.second));
+ std::move(begin), std::move(end));
+ return make_pair(GHostTime{std::move(result.first)}, std::move(result.second));
}
std::chrono::microseconds time;
@@ -129,8 +129,8 @@ struct PrevGHostTime
{
using namespace std;
auto result = discovery::Deserialize<chrono::microseconds>::fromNetworkByteStream(
- move(begin), move(end));
- return make_pair(PrevGHostTime{move(result.first)}, move(result.second));
+ std::move(begin), std::move(end));
+ return make_pair(PrevGHostTime{std::move(result.first)}, std::move(result.second));
}
std::chrono::microseconds time;
diff --git a/include/ableton/link/PeerState.hpp b/include/ableton/link/PeerState.hpp
index 8b58df4..3bbfbc0 100644
--- a/include/ableton/link/PeerState.hpp
+++ b/include/ableton/link/PeerState.hpp
@@ -73,10 +73,10 @@ struct PeerState
static PeerState fromPayload(NodeId id, It begin, It end)
{
using namespace std;
- auto peerState = PeerState{NodeState::fromPayload(move(id), begin, end), {}};
+ auto peerState = PeerState{NodeState::fromPayload(std::move(id), begin, end), {}};
- discovery::parsePayload<MeasurementEndpointV4>(move(begin), move(end),
- [&peerState](MeasurementEndpointV4 me4) { peerState.endpoint = move(me4.ep); });
+ discovery::parsePayload<MeasurementEndpointV4>(std::move(begin), std::move(end),
+ [&peerState](MeasurementEndpointV4 me4) { peerState.endpoint = std::move(me4.ep); });
return peerState;
}
diff --git a/include/ableton/link/Peers.hpp b/include/ableton/link/Peers.hpp
index 24172b3..ce674c9 100644
--- a/include/ableton/link/Peers.hpp
+++ b/include/ableton/link/Peers.hpp
@@ -225,14 +225,14 @@ private:
isNewSessionStartStopState =
!sessionStartStopStateExists(peerSession, peerStartStopState);
- auto peer = make_pair(move(peerState), move(gatewayAddr));
+ auto peer = make_pair(std::move(peerState), std::move(gatewayAddr));
const auto idRange = equal_range(begin(mPeers), end(mPeers), peer, PeerIdComp{});
if (idRange.first == idRange.second)
{
// This peer is not currently known on any gateway
didSessionMembershipChange = true;
- mPeers.insert(move(idRange.first), move(peer));
+ mPeers.insert(std::move(idRange.first), std::move(peer));
}
else
{
@@ -249,12 +249,12 @@ private:
if (addrRange.first == addrRange.second)
{
// First time on this gateway, add it
- mPeers.insert(move(addrRange.first), move(peer));
+ mPeers.insert(std::move(addrRange.first), std::move(peer));
}
else
{
// We have an entry for this peer on this gateway, update it
- *addrRange.first = move(peer);
+ *addrRange.first = std::move(peer);
}
}
} // end lock
@@ -292,7 +292,7 @@ private:
if (it != end(mPeers))
{
- mPeers.erase(move(it));
+ mPeers.erase(std::move(it));
didSessionMembershipChange = true;
}
} // end lock
diff --git a/include/ableton/link/PingResponder.hpp b/include/ableton/link/PingResponder.hpp
index 0650cce..e8a5e8b 100644
--- a/include/ableton/link/PingResponder.hpp
+++ b/include/ableton/link/PingResponder.hpp
@@ -23,35 +23,34 @@
#include <ableton/link/PayloadEntries.hpp>
#include <ableton/link/SessionId.hpp>
#include <ableton/link/v1/Messages.hpp>
-#include <ableton/platforms/asio/AsioWrapper.hpp>
#include <ableton/util/Injected.hpp>
#include <ableton/util/SafeAsyncHandler.hpp>
#include <chrono>
#include <memory>
-#include <thread>
namespace ableton
{
namespace link
{
-template <typename Io, typename Clock, typename Socket, typename Log>
+template <typename Clock, typename IoContext>
class PingResponder
{
+ using IoType = util::Injected<IoContext&>;
+ using Socket = typename IoType::type::template Socket<v1::kMaxMessageSize>;
+
public:
PingResponder(asio::ip::address_v4 address,
SessionId sessionId,
GhostXForm ghostXForm,
- util::Injected<Io> io,
Clock clock,
- util::Injected<Log> log)
- : mIo(std::move(io))
- , mpImpl(std::make_shared<Impl>(*mIo,
- std::move(address),
+ IoType io)
+ : mIo(io)
+ , mpImpl(std::make_shared<Impl>(std::move(address),
std::move(sessionId),
std::move(ghostXForm),
std::move(clock),
- std::move(log)))
+ std::move(io)))
{
mpImpl->listen();
}
@@ -61,16 +60,16 @@ public:
~PingResponder()
{
- // post the release of the impl object into the io service so that
+ // post the release of the impl object into the IoContext so that
// it happens in the same thread as its handlers
auto pImpl = mpImpl;
- mIo->post([pImpl]() mutable { pImpl.reset(); });
+ mIo->async([pImpl]() mutable { pImpl.reset(); });
}
void updateNodeState(const SessionId& sessionId, const GhostXForm& xform)
{
auto pImpl = mpImpl;
- mIo->post([pImpl, sessionId, xform] {
+ mIo->async([pImpl, sessionId, xform] {
pImpl->mSessionId = std::move(sessionId);
pImpl->mGhostXForm = std::move(xform);
});
@@ -94,19 +93,17 @@ public:
private:
struct Impl : std::enable_shared_from_this<Impl>
{
- Impl(typename util::Injected<Io>::type& io,
- asio::ip::address_v4 address,
+ Impl(asio::ip::address_v4 address,
SessionId sessionId,
GhostXForm ghostXForm,
Clock clock,
- util::Injected<Log> log)
+ IoType io)
: mSessionId(std::move(sessionId))
, mGhostXForm(std::move(ghostXForm))
, mClock(std::move(clock))
- , mLog(std::move(log))
- , mSocket(io)
+ , mLog(channel(io->log(), "gateway@" + address.to_string()))
+ , mSocket(io->template openUnicastSocket<v1::kMaxMessageSize>(address))
{
- configureUnicastSocket(mSocket, address);
}
void listen()
@@ -131,7 +128,7 @@ private:
sizeInByteStream(makePayload(HostTime{}, PrevGHostTime{}));
if (header.messageType == v1::kPing && payloadSize <= maxPayloadSize)
{
- debug(*mLog) << "Received ping message from " << from;
+ debug(mLog) << " Received ping message from " << from;
try
{
@@ -139,12 +136,12 @@ private:
}
catch (const std::runtime_error& err)
{
- info(*mLog) << "Failed to send pong to " << from << ". Reason: " << err.what();
+ info(mLog) << " Failed to send pong to " << from << ". Reason: " << err.what();
}
}
else
{
- info(*mLog) << "Received invalid Message from " << from << ".";
+ info(mLog) << " Received invalid Message from " << from << ".";
}
listen();
}
@@ -173,11 +170,11 @@ private:
SessionId mSessionId;
GhostXForm mGhostXForm;
Clock mClock;
- util::Injected<Log> mLog;
+ typename IoType::type::Log mLog;
Socket mSocket;
};
- util::Injected<Io> mIo;
+ IoType mIo;
std::shared_ptr<Impl> mpImpl;
};
diff --git a/include/ableton/link/SessionId.hpp b/include/ableton/link/SessionId.hpp
index ac89672..7881e20 100644
--- a/include/ableton/link/SessionId.hpp
+++ b/include/ableton/link/SessionId.hpp
@@ -52,8 +52,8 @@ struct SessionMembership
static std::pair<SessionMembership, It> fromNetworkByteStream(It begin, It end)
{
using namespace std;
- auto idRes = SessionId::fromNetworkByteStream(move(begin), move(end));
- return make_pair(SessionMembership{move(idRes.first)}, move(idRes.second));
+ auto idRes = SessionId::fromNetworkByteStream(std::move(begin), std::move(end));
+ return make_pair(SessionMembership{std::move(idRes.first)}, std::move(idRes.second));
}
SessionId sessionId;
diff --git a/include/ableton/link/SessionState.hpp b/include/ableton/link/SessionState.hpp
index f64325f..67e2dd6 100644
--- a/include/ableton/link/SessionState.hpp
+++ b/include/ableton/link/SessionState.hpp
@@ -30,6 +30,7 @@ namespace link
using OptionalTimeline = Optional<Timeline>;
using OptionalStartStopState = Optional<StartStopState>;
+using OptionalClientStartStopState = Optional<ClientStartStopState>;
struct SessionState
{
@@ -52,13 +53,13 @@ struct ClientState
}
Timeline timeline;
- StartStopState startStopState;
+ ClientStartStopState startStopState;
};
struct RtClientState
{
Timeline timeline;
- StartStopState startStopState;
+ ClientStartStopState startStopState;
std::chrono::microseconds timelineTimestamp;
std::chrono::microseconds startStopStateTimestamp;
};
@@ -66,7 +67,7 @@ struct RtClientState
struct IncomingClientState
{
OptionalTimeline timeline;
- OptionalStartStopState startStopState;
+ OptionalClientStartStopState startStopState;
std::chrono::microseconds timelineTimestamp;
};
diff --git a/include/ableton/link/Sessions.hpp b/include/ableton/link/Sessions.hpp
index fc82bdd..ee1a95e 100644
--- a/include/ableton/link/Sessions.hpp
+++ b/include/ableton/link/Sessions.hpp
@@ -86,11 +86,11 @@ public:
if (sid == mCurrent.sessionId)
{
// matches our current session, update the timeline if necessary
- updateTimeline(mCurrent, move(timeline));
+ updateTimeline(mCurrent, std::move(timeline));
}
else
{
- auto session = Session{move(sid), move(timeline), {}};
+ auto session = Session{std::move(sid), std::move(timeline), {}};
const auto range =
equal_range(begin(mOtherSessions), end(mOtherSessions), session, SessionIdComp{});
if (range.first == range.second)
@@ -98,12 +98,12 @@ public:
// brand new session, insert it into our list of known
// sessions and launch a measurement
launchSessionMeasurement(session);
- mOtherSessions.insert(range.first, move(session));
+ mOtherSessions.insert(range.first, std::move(session));
}
else
{
// we've seen this session before, update its timeline if necessary
- updateTimeline(*range.first, move(timeline));
+ updateTimeline(*range.first, std::move(timeline));
}
}
return mCurrent.timeline;
@@ -125,7 +125,7 @@ private:
// mark that a session is in progress by clearing out the
// session's timestamp
session.measurement.timestamp = {};
- mMeasure(move(peer), MeasurementResultsHandler{*this, session.sessionId});
+ mMeasure(std::move(peer), MeasurementResultsHandler{*this, session.sessionId});
}
}
@@ -136,11 +136,11 @@ private:
debug(mIo->log()) << "Session " << id << " measurement completed with result "
<< "(" << xform.slope << ", " << xform.intercept.count() << ")";
- auto measurement = SessionMeasurement{move(xform), mClock.micros()};
+ auto measurement = SessionMeasurement{std::move(xform), mClock.micros()};
if (mCurrent.sessionId == id)
{
- mCurrent.measurement = move(measurement);
+ mCurrent.measurement = std::move(measurement);
mCallback(mCurrent);
}
else
@@ -156,7 +156,7 @@ private:
const auto curGhost = mCurrent.measurement.xform.hostToGhost(hostTime);
const auto newGhost = measurement.xform.hostToGhost(hostTime);
// update the measurement for the session entry
- range.first->measurement = move(measurement);
+ range.first->measurement = std::move(measurement);
// If session times too close - fall back to session id order
const auto ghostDiff = newGhost - curGhost;
if (ghostDiff > SESSION_EPS
@@ -165,13 +165,13 @@ private:
{
// The new session wins, switch over to it
auto current = mCurrent;
- mCurrent = move(*range.first);
+ mCurrent = std::move(*range.first);
mOtherSessions.erase(range.first);
// Put the old current session back into our list of known
// sessions so that we won't re-measure it
const auto it = upper_bound(
begin(mOtherSessions), end(mOtherSessions), current, SessionIdComp{});
- mOtherSessions.insert(it, move(current));
+ mOtherSessions.insert(it, std::move(current));
// And notify that we have a new session and make sure that
// we remeasure it periodically.
mCallback(mCurrent);
@@ -297,7 +297,7 @@ Sessions<Peers, MeasurePeer, JoinSessionCallback, IoContext, Clock> makeSessions
Clock clock)
{
using namespace std;
- return {move(init), move(peers), move(measure), move(join), move(io), move(clock)};
+ return {std::move(init), std::move(peers), std::move(measure), std::move(join), std::move(io), std::move(clock)};
}
} // namespace link
diff --git a/include/ableton/link/StartStopState.hpp b/include/ableton/link/StartStopState.hpp
index 74654ef..38fba5a 100644
--- a/include/ableton/link/StartStopState.hpp
+++ b/include/ableton/link/StartStopState.hpp
@@ -79,10 +79,10 @@ struct StartStopState
using namespace std;
using namespace discovery;
auto result =
- Deserialize<StartStopStateTuple>::fromNetworkByteStream(move(begin), move(end));
+ Deserialize<StartStopStateTuple>::fromNetworkByteStream(std::move(begin), std::move(end));
auto state =
StartStopState{get<0>(result.first), get<1>(result.first), get<2>(result.first)};
- return make_pair(move(state), move(result.second));
+ return make_pair(std::move(state), std::move(result.second));
}
bool isPlaying{false};
@@ -96,6 +96,35 @@ private:
}
};
+struct ClientStartStopState
+{
+ ClientStartStopState() = default;
+
+ ClientStartStopState(const bool aIsPlaying,
+ const std::chrono::microseconds aTime,
+ const std::chrono::microseconds aTimestamp)
+ : isPlaying(aIsPlaying)
+ , time(aTime)
+ , timestamp(aTimestamp)
+ {
+ }
+
+ friend bool operator==(const ClientStartStopState& lhs, const ClientStartStopState& rhs)
+ {
+ return std::tie(lhs.isPlaying, lhs.time, lhs.timestamp)
+ == std::tie(rhs.isPlaying, rhs.time, rhs.timestamp);
+ }
+
+ friend bool operator!=(const ClientStartStopState& lhs, const ClientStartStopState& rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+ bool isPlaying{false};
+ std::chrono::microseconds time{0};
+ std::chrono::microseconds timestamp{0};
+};
+
struct ApiStartStopState
{
ApiStartStopState() = default;
diff --git a/include/ableton/link/Tempo.hpp b/include/ableton/link/Tempo.hpp
index 2557928..69beb9c 100644
--- a/include/ableton/link/Tempo.hpp
+++ b/include/ableton/link/Tempo.hpp
@@ -27,41 +27,43 @@ namespace ableton
namespace link
{
-struct Tempo : std::tuple<double>
+struct Tempo
{
Tempo() = default;
// Beats per minute
explicit Tempo(const double bpm)
- : std::tuple<double>(bpm)
+ : mValue(bpm)
{
}
Tempo(const std::chrono::microseconds microsPerBeat)
- : std::tuple<double>(60. * 1e6 / microsPerBeat.count())
+ : mValue(60. * 1e6 / static_cast<double>(microsPerBeat.count()))
{
}
double bpm() const
{
- return std::get<0>(*this);
+ return mValue;
}
std::chrono::microseconds microsPerBeat() const
{
- return std::chrono::microseconds{llround(60. * 1e6 / bpm())};
+ return std::chrono::microseconds{std::llround(60. * 1e6 / bpm())};
}
// Given the tempo, convert a time to a beat value
Beats microsToBeats(const std::chrono::microseconds micros) const
{
- return Beats{micros.count() / static_cast<double>(microsPerBeat().count())};
+ return Beats{
+ static_cast<double>(micros.count()) / static_cast<double>(microsPerBeat().count())};
}
// Given the tempo, convert a beat to a time value
std::chrono::microseconds beatsToMicros(const Beats beats) const
{
- return std::chrono::microseconds{llround(beats.floating() * microsPerBeat().count())};
+ return std::chrono::microseconds{
+ std::llround(beats.floating() * static_cast<double>(microsPerBeat().count()))};
}
// Model the NetworkByteStreamSerializable concept
@@ -84,6 +86,39 @@ struct Tempo : std::tuple<double>
std::move(begin), std::move(end));
return std::make_pair(Tempo{std::move(result.first)}, std::move(result.second));
}
+
+ friend bool operator==(const Tempo lhs, const Tempo rhs)
+ {
+ return lhs.mValue == rhs.mValue;
+ }
+
+ friend bool operator!=(const Tempo lhs, const Tempo rhs)
+ {
+ return lhs.mValue != rhs.mValue;
+ }
+
+ friend bool operator<(const Tempo lhs, const Tempo rhs)
+ {
+ return lhs.mValue < rhs.mValue;
+ }
+
+ friend bool operator>(const Tempo lhs, const Tempo rhs)
+ {
+ return lhs.mValue > rhs.mValue;
+ }
+
+ friend bool operator<=(const Tempo lhs, const Tempo rhs)
+ {
+ return lhs.mValue <= rhs.mValue;
+ }
+
+ friend bool operator>=(const Tempo lhs, const Tempo rhs)
+ {
+ return lhs.mValue >= rhs.mValue;
+ }
+
+private:
+ double mValue = 0;
};
} // namespace link
diff --git a/include/ableton/link/Timeline.hpp b/include/ableton/link/Timeline.hpp
index f0f084e..941f943 100644
--- a/include/ableton/link/Timeline.hpp
+++ b/include/ableton/link/Timeline.hpp
@@ -83,9 +83,9 @@ struct Timeline
Timeline timeline;
auto result =
Deserialize<tuple<Tempo, Beats, chrono::microseconds>>::fromNetworkByteStream(
- move(begin), move(end));
- tie(timeline.tempo, timeline.beatOrigin, timeline.timeOrigin) = move(result.first);
- return make_pair(move(timeline), move(result.second));
+ std::move(begin), std::move(end));
+ tie(timeline.tempo, timeline.beatOrigin, timeline.timeOrigin) = std::move(result.first);
+ return make_pair(std::move(timeline), std::move(result.second));
}
Tempo tempo;
diff --git a/include/ableton/link/v1/Messages.hpp b/include/ableton/link/v1/Messages.hpp
index b1e864b..3844b40 100644
--- a/include/ableton/link/v1/Messages.hpp
+++ b/include/ableton/link/v1/Messages.hpp
@@ -88,7 +88,7 @@ It encodeMessage(const MessageType messageType, const Payload& payload, It out)
{
return toNetworkByteStream(
payload, toNetworkByteStream(
- header, copy(begin(kProtocolHeader), end(kProtocolHeader), move(out))));
+ header, copy(begin(kProtocolHeader), end(kProtocolHeader), std::move(out))));
}
else
{
diff --git a/include/ableton/platforms/Config.hpp b/include/ableton/platforms/Config.hpp
index cd1506c..63aea27 100644
--- a/include/ableton/platforms/Config.hpp
+++ b/include/ableton/platforms/Config.hpp
@@ -24,16 +24,24 @@
#if defined(LINK_PLATFORM_WINDOWS)
#include <ableton/platforms/asio/Context.hpp>
+#include <ableton/platforms/stl/Random.hpp>
#include <ableton/platforms/windows/Clock.hpp>
#include <ableton/platforms/windows/ScanIpIfAddrs.hpp>
#elif defined(LINK_PLATFORM_MACOSX)
#include <ableton/platforms/asio/Context.hpp>
#include <ableton/platforms/darwin/Clock.hpp>
#include <ableton/platforms/posix/ScanIpIfAddrs.hpp>
+#include <ableton/platforms/stl/Random.hpp>
#elif defined(LINK_PLATFORM_LINUX)
#include <ableton/platforms/asio/Context.hpp>
#include <ableton/platforms/linux/Clock.hpp>
#include <ableton/platforms/posix/ScanIpIfAddrs.hpp>
+#include <ableton/platforms/stl/Random.hpp>
+#elif defined(ESP_PLATFORM)
+#include <ableton/platforms/esp32/Clock.hpp>
+#include <ableton/platforms/esp32/Context.hpp>
+#include <ableton/platforms/esp32/Random.hpp>
+#include <ableton/platforms/esp32/ScanIpIfAddrs.hpp>
#endif
namespace ableton
@@ -47,20 +55,26 @@ namespace platform
using Clock = platforms::windows::Clock;
using IoContext =
platforms::asio::Context<platforms::windows::ScanIpIfAddrs, util::NullLog>;
+using Random = platforms::stl::Random;
#elif defined(LINK_PLATFORM_MACOSX)
using Clock = platforms::darwin::Clock;
using IoContext =
platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>;
+using Random = platforms::stl::Random;
#elif defined(LINK_PLATFORM_LINUX)
-using Clock = platforms::linux::ClockMonotonic;
+using Clock = platforms::linux::ClockMonotonicRaw;
using IoContext =
platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>;
-#endif
+using Random = platforms::stl::Random;
-using Controller =
- Controller<PeerCountCallback, TempoCallback, StartStopStateCallback, Clock, IoContext>;
+#elif defined(ESP_PLATFORM)
+using Clock = platforms::esp32::Clock;
+using IoContext =
+ platforms::esp32::Context<platforms::esp32::ScanIpIfAddrs, util::NullLog>;
+using Random = platforms::esp32::Random;
+#endif
} // namespace platform
} // namespace link
diff --git a/include/ableton/platforms/asio/AsioService.hpp b/include/ableton/platforms/asio/AsioService.hpp
deleted file mode 100644
index c33b892..0000000
--- a/include/ableton/platforms/asio/AsioService.hpp
+++ /dev/null
@@ -1,105 +0,0 @@
-/* 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 <http://www.gnu.org/licenses/>.
- *
- * If you would like to incorporate Link into a proprietary software application,
- * please contact <link-devs@ableton.com>.
- */
-
-#pragma once
-
-#include <ableton/platforms/asio/AsioTimer.hpp>
-#include <ableton/platforms/asio/AsioWrapper.hpp>
-#include <thread>
-
-namespace ableton
-{
-namespace platforms
-{
-namespace asio
-{
-
-class AsioService
-{
-public:
- using Timer = AsioTimer;
-
- AsioService()
- : AsioService(DefaultHandler{})
- {
- }
-
- template <typename ExceptionHandler>
- explicit AsioService(ExceptionHandler exceptHandler)
- : mpWork(new ::asio::io_service::work(mService))
- {
- mThread =
- std::thread{[](::asio::io_service& service, ExceptionHandler handler) {
- for (;;)
- {
- try
- {
- service.run();
- break;
- }
- catch (const typename ExceptionHandler::Exception& exception)
- {
- handler(exception);
- }
- }
- },
- std::ref(mService), std::move(exceptHandler)};
- }
-
- ~AsioService()
- {
- mpWork.reset();
- mThread.join();
- }
-
- AsioTimer makeTimer()
- {
- return {mService};
- }
-
- template <typename Handler>
- void post(Handler handler)
- {
- mService.post(std::move(handler));
- }
-
- ::asio::io_service mService;
-
-private:
- // Default handler is hidden and defines a hidden exception type
- // that will never be thrown by other code, so it effectively does
- // not catch.
- struct DefaultHandler
- {
- struct Exception
- {
- };
-
- void operator()(const Exception&)
- {
- }
- };
-
- std::unique_ptr<::asio::io_service::work> mpWork;
- std::thread mThread;
-};
-
-} // namespace asio
-} // namespace platforms
-} // namespace ableton
diff --git a/include/ableton/platforms/asio/AsioWrapper.hpp b/include/ableton/platforms/asio/AsioWrapper.hpp
index 02ae71f..6477e45 100644
--- a/include/ableton/platforms/asio/AsioWrapper.hpp
+++ b/include/ableton/platforms/asio/AsioWrapper.hpp
@@ -26,11 +26,13 @@
* by Link.
*/
+#if !defined(ESP_PLATFORM)
#pragma push_macro("ASIO_STANDALONE")
#define ASIO_STANDALONE 1
#pragma push_macro("ASIO_NO_TYPEID")
#define ASIO_NO_TYPEID 1
+#endif
#if defined(LINK_PLATFORM_WINDOWS)
#pragma push_macro("INCL_EXTRA_HTON_FUNCTIONS")
@@ -48,6 +50,9 @@
#if __has_warning("-Wcomma")
#pragma clang diagnostic ignored "-Wcomma"
#endif
+#if __has_warning("-Wshorten-64-to-32")
+#pragma clang diagnostic ignored "-Wshorten-64-to-32"
+#endif
#if __has_warning("-Wunused-local-typedef")
#pragma clang diagnostic ignored "-Wunused-local-typedef"
#endif
@@ -58,6 +63,7 @@
#pragma warning(push, 0)
#pragma warning(disable : 4242)
#pragma warning(disable : 4702)
+#pragma warning(disable : 5204)
#endif
#include <asio.hpp>
@@ -67,8 +73,10 @@
#pragma pop_macro("INCL_EXTRA_HTON_FUNCTIONS")
#endif
+#if !defined(ESP_PLATFORM)
#pragma pop_macro("ASIO_STANDALONE")
#pragma pop_macro("ASIO_NO_TYPEID")
+#endif
#if defined(_MSC_VER)
#pragma warning(pop)
diff --git a/include/ableton/platforms/asio/Context.hpp b/include/ableton/platforms/asio/Context.hpp
index ac30e5d..54125fc 100644
--- a/include/ableton/platforms/asio/Context.hpp
+++ b/include/ableton/platforms/asio/Context.hpp
@@ -87,13 +87,24 @@ public:
~Context()
{
- if (mpService)
+ if (mpService && mpWork)
{
mpWork.reset();
mThread.join();
}
}
+ void stop()
+ {
+ if (mpService && mpWork)
+ {
+ mpWork.reset();
+ mpService->stop();
+ mThread.join();
+ }
+ }
+
+
template <std::size_t BufferSize>
Socket<BufferSize> openUnicastSocket(const ::asio::ip::address_v4& addr)
{
diff --git a/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp b/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp
index a1e85df..4515eb6 100644
--- a/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp
+++ b/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp
@@ -27,13 +27,15 @@ namespace ableton
{
namespace platforms
{
+namespace asio
+{
// Utility to signal invocation of a callback on another thread in a lock free manner.
// The callback is evoked on a thread owned by the instance of this class.
//
// A condition variable is used to notify a waiting thread, but only if the required
// lock can be acquired immediately. If that fails, we fall back on signaling
-// after a timeout. This gives us a guaranteed minimum signalling rate which is defined
+// after a timeout. This gives us a guaranteed minimum signaling rate which is defined
// by the fallbackPeriod parameter.
template <typename Callback, typename Duration>
@@ -85,5 +87,6 @@ private:
std::thread mThread;
};
+} // namespace asio
} // namespace platforms
} // namespace ableton
diff --git a/include/ableton/platforms/esp32/Clock.hpp b/include/ableton/platforms/esp32/Clock.hpp
new file mode 100644
index 0000000..5cfaf0b
--- /dev/null
+++ b/include/ableton/platforms/esp32/Clock.hpp
@@ -0,0 +1,36 @@
+/* Copyright 2019, Mathias Bredholt, Torso Electronics, Copenhagen. 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "esp_timer.h"
+
+namespace ableton
+{
+namespace platforms
+{
+namespace esp32
+{
+struct Clock
+{
+ std::chrono::microseconds micros() const
+ {
+ return static_cast<std::chrono::microseconds>(esp_timer_get_time());
+ }
+};
+} // namespace esp32
+} // namespace platforms
+} // namespace ableton
diff --git a/include/ableton/platforms/esp32/Context.hpp b/include/ableton/platforms/esp32/Context.hpp
new file mode 100644
index 0000000..772d38e
--- /dev/null
+++ b/include/ableton/platforms/esp32/Context.hpp
@@ -0,0 +1,208 @@
+/* Copyright 2020, 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 <http://www.gnu.org/licenses/>.
+ *
+ * If you would like to incorporate Link into a proprietary software application,
+ * please contact <link-devs@ableton.com>.
+ */
+
+#pragma once
+
+#include <ableton/discovery/IpV4Interface.hpp>
+#include <ableton/platforms/asio/AsioTimer.hpp>
+#include <ableton/platforms/asio/AsioWrapper.hpp>
+#include <ableton/platforms/asio/Socket.hpp>
+#include <ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp>
+#include <freertos/task.h>
+
+namespace ableton
+{
+namespace platforms
+{
+namespace esp32
+{
+
+template <typename ScanIpIfAddrs, typename LogT>
+class Context
+{
+ class ServiceRunner
+ {
+
+ static void run(void* userParams)
+ {
+ auto runner = static_cast<ServiceRunner*>(userParams);
+ for (;;)
+ {
+ try
+ {
+ runner->mpService->poll_one();
+ }
+ catch (...)
+ {
+ }
+ portYIELD();
+ }
+ }
+
+ public:
+ ServiceRunner()
+ : mpService(new ::asio::io_service())
+ , mpWork(new ::asio::io_service::work(*mpService))
+ {
+ xTaskCreatePinnedToCore(
+ run, "link", 8192, this, 2 | portPRIVILEGE_BIT, &mTaskHandle, LINK_ESP_TASK_CORE_ID);
+ }
+
+ ~ServiceRunner()
+ {
+ vTaskDelete(mTaskHandle);
+ }
+
+ template <typename Handler>
+ void async(Handler handler)
+ {
+ mpService->post(std::move(handler));
+ }
+
+ ::asio::io_service& service() const
+ {
+ return *mpService;
+ }
+
+ private:
+ TaskHandle_t mTaskHandle;
+ std::unique_ptr<::asio::io_service> mpService;
+ std::unique_ptr<::asio::io_service::work> mpWork;
+ };
+
+public:
+ using Timer = ::ableton::platforms::asio::AsioTimer;
+ using Log = LogT;
+
+ template <typename Handler, typename Duration>
+ using LockFreeCallbackDispatcher = LockFreeCallbackDispatcher<Handler, Duration>;
+
+ template <std::size_t BufferSize>
+ using Socket = asio::Socket<BufferSize>;
+
+ Context()
+ : Context(DefaultHandler{})
+ {
+ }
+
+ template <typename ExceptionHandler>
+ explicit Context(ExceptionHandler exceptHandler)
+ {
+ }
+
+ Context(const Context&) = delete;
+
+ Context(Context&& rhs)
+ : mLog(std::move(rhs.mLog))
+ , mScanIpIfAddrs(std::move(rhs.mScanIpIfAddrs))
+ {
+ }
+
+ void stop()
+ {
+ }
+
+ template <std::size_t BufferSize>
+ Socket<BufferSize> openUnicastSocket(const ::asio::ip::address_v4& addr)
+ {
+ auto socket = Socket<BufferSize>{serviceRunner().service()};
+ socket.mpImpl->mSocket.set_option(
+ ::asio::ip::multicast::enable_loopback(addr.is_loopback()));
+ socket.mpImpl->mSocket.set_option(::asio::ip::multicast::outbound_interface(addr));
+ socket.mpImpl->mSocket.bind(::asio::ip::udp::endpoint{addr, 0});
+ return socket;
+ }
+
+ template <std::size_t BufferSize>
+ Socket<BufferSize> openMulticastSocket(const ::asio::ip::address_v4& addr)
+ {
+ auto socket = Socket<BufferSize>{serviceRunner().service()};
+ socket.mpImpl->mSocket.set_option(::asio::ip::udp::socket::reuse_address(true));
+ socket.mpImpl->mSocket.set_option(
+ ::asio::socket_base::broadcast(!addr.is_loopback()));
+ socket.mpImpl->mSocket.set_option(
+ ::asio::ip::multicast::enable_loopback(addr.is_loopback()));
+ socket.mpImpl->mSocket.set_option(::asio::ip::multicast::outbound_interface(addr));
+ socket.mpImpl->mSocket.bind({::asio::ip::address::from_string("0.0.0.0"),
+ discovery::multicastEndpoint().port()});
+ socket.mpImpl->mSocket.set_option(::asio::ip::multicast::join_group(
+ discovery::multicastEndpoint().address().to_v4(), addr));
+ return socket;
+ }
+
+ std::vector<::asio::ip::address> scanNetworkInterfaces()
+ {
+ return mScanIpIfAddrs();
+ }
+
+ Timer makeTimer() const
+ {
+ return {serviceRunner().service()};
+ }
+
+ Log& log()
+ {
+ return mLog;
+ }
+
+ template <typename Handler>
+ void async(Handler handler)
+ {
+ serviceRunner().service().post(std::move(handler));
+ }
+
+ Context clone() const
+ {
+ return {};
+ }
+
+ template <typename ExceptionHandler>
+ Context clone(ExceptionHandler handler) const
+ {
+ return Context{std::move(handler)};
+ }
+
+private:
+ // Default handler is hidden and defines a hidden exception type
+ // that will never be thrown by other code, so it effectively does
+ // not catch.
+ struct DefaultHandler
+ {
+ struct Exception
+ {
+ };
+
+ void operator()(const Exception&)
+ {
+ }
+ };
+
+ static ServiceRunner& serviceRunner()
+ {
+ static ServiceRunner runner;
+ return runner;
+ }
+
+ Log mLog;
+ ScanIpIfAddrs mScanIpIfAddrs;
+};
+
+} // namespace esp32
+} // namespace platforms
+} // namespace ableton
diff --git a/include/ableton/platforms/esp32/Esp32.hpp b/include/ableton/platforms/esp32/Esp32.hpp
new file mode 100644
index 0000000..9762ef4
--- /dev/null
+++ b/include/ableton/platforms/esp32/Esp32.hpp
@@ -0,0 +1,27 @@
+/* Copyright 2019, Mathias Bredholt, Torso Electronics, Copenhagen. 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <endian.h>
+
+#ifndef ntohll
+#define ntohll(x) bswap64(x)
+#endif
+
+#ifndef htonll
+#define htonll(x) bswap64(x)
+#endif
diff --git a/include/ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp b/include/ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp
new file mode 100644
index 0000000..b02ab73
--- /dev/null
+++ b/include/ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp
@@ -0,0 +1,94 @@
+/* Copyright 2020, 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 <http://www.gnu.org/licenses/>.
+ *
+ * If you would like to incorporate Link into a proprietary software application,
+ * please contact <link-devs@ableton.com>.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <condition_variable>
+#include <freertos/task.h>
+
+namespace ableton
+{
+namespace platforms
+{
+namespace esp32
+{
+
+// Utility to signal invocation of a callback on another thread in a lock free manner.
+// The callback is evoked on a thread owned by the instance of this class.
+//
+// A condition variable is used to notify a waiting thread, but only if the required
+// lock can be acquired immediately. If that fails, we fall back on signaling
+// after a timeout. This gives us a guaranteed minimum signaling rate which is defined
+// by the fallbackPeriod parameter.
+
+template <typename Callback, typename Duration>
+class LockFreeCallbackDispatcher
+{
+public:
+ LockFreeCallbackDispatcher(Callback callback, Duration fallbackPeriod)
+ : mCallback(std::move(callback))
+ , mFallbackPeriod(std::move(fallbackPeriod))
+ , mRunning(true)
+ {
+ xTaskCreate(run, "link", 4096, this, tskIDLE_PRIORITY, &mTaskHandle);
+ }
+
+ ~LockFreeCallbackDispatcher()
+ {
+ mRunning = false;
+ mCondition.notify_one();
+ vTaskDelete(mTaskHandle);
+ }
+
+ void invoke()
+ {
+ if (mMutex.try_lock())
+ {
+ mCondition.notify_one();
+ mMutex.unlock();
+ }
+ }
+
+private:
+ static void run(void* userData)
+ {
+ auto dispatcher = static_cast<LockFreeCallbackDispatcher*>(userData);
+ while (dispatcher->mRunning.load())
+ {
+ {
+ std::unique_lock<std::mutex> lock(dispatcher->mMutex);
+ dispatcher->mCondition.wait_for(lock, dispatcher->mFallbackPeriod);
+ }
+ dispatcher->mCallback();
+ portYIELD();
+ }
+ }
+
+ Callback mCallback;
+ Duration mFallbackPeriod;
+ std::atomic<bool> mRunning;
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ TaskHandle_t mTaskHandle;
+};
+
+} // namespace esp32
+} // namespace platforms
+} // namespace ableton
diff --git a/include/ableton/platforms/esp32/Random.hpp b/include/ableton/platforms/esp32/Random.hpp
new file mode 100644
index 0000000..adca049
--- /dev/null
+++ b/include/ableton/platforms/esp32/Random.hpp
@@ -0,0 +1,36 @@
+/* Copyright 2019, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+namespace ableton
+{
+namespace platforms
+{
+namespace esp32
+{
+
+struct Random
+{
+ uint8_t operator()()
+ {
+ return static_cast<uint8_t>((esp_random() % 93) + 33); // printable ascii chars
+ }
+};
+
+} // namespace esp32
+} // namespace platforms
+} // namespace ableton
diff --git a/include/ableton/platforms/esp32/ScanIpIfAddrs.hpp b/include/ableton/platforms/esp32/ScanIpIfAddrs.hpp
new file mode 100644
index 0000000..26406a1
--- /dev/null
+++ b/include/ableton/platforms/esp32/ScanIpIfAddrs.hpp
@@ -0,0 +1,51 @@
+/* Copyright 2020, Ableton AG, Berlin and 2019, Mathias Bredholt, Torso Electronics,
+ * Copenhagen. 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <ableton/platforms/asio/AsioWrapper.hpp>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <tcpip_adapter.h>
+#include <vector>
+
+namespace ableton
+{
+namespace platforms
+{
+namespace esp32
+{
+
+// ESP32 implementation of ip interface address scanner
+struct ScanIpIfAddrs
+{
+ std::vector<::asio::ip::address> operator()()
+ {
+ std::vector<::asio::ip::address> addrs;
+ tcpip_adapter_ip_info_t ip_info;
+ if (ESP_OK == tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info)
+ && tcpip_adapter_is_netif_up(TCPIP_ADAPTER_IF_STA) && ip_info.ip.addr)
+ {
+ addrs.emplace_back(::asio::ip::address_v4(ntohl(ip_info.ip.addr)));
+ }
+ return addrs;
+ }
+};
+
+} // namespace esp32
+} // namespace platforms
+} // namespace ableton
diff --git a/examples/qlinkhut/main.cpp b/include/ableton/platforms/stl/Random.hpp
index bc6058c..e4b224b 100644
--- a/examples/qlinkhut/main.cpp
+++ b/include/ableton/platforms/stl/Random.hpp
@@ -1,4 +1,4 @@
-/* Copyright 2016, Ableton AG, Berlin. All rights reserved.
+/* Copyright 2019, 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
@@ -17,20 +17,34 @@
* please contact <link-devs@ableton.com>.
*/
-#include "Controller.hpp"
-#include <QGuiApplication>
-#include <QQmlApplicationEngine>
-#include <QQmlContext>
-#include <QQuickView>
-#include <QQuickWindow>
+#pragma once
-int main(int argc, char* argv[])
+#include <random>
+
+namespace ableton
+{
+namespace platforms
+{
+namespace stl
+{
+
+struct Random
{
- ableton::qlinkhut::Controller controller;
- QGuiApplication app(argc, argv);
- QQuickView view;
- view.rootContext()->setContextProperty("controller", &controller);
- view.setSource(QUrl("qrc:/main.qml"));
- view.show();
- return app.exec();
-}
+ Random()
+ : gen(rd())
+ , dist(33, 126) // printable ascii chars
+ {}
+
+ uint8_t operator()()
+ {
+ return static_cast<uint8_t>(dist(gen));
+ }
+
+ std::random_device rd;
+ std::mt19937 gen;
+ std::uniform_int_distribution<unsigned> dist;
+};
+
+} // namespace stl
+} // namespace platforms
+} // namespace ableton
diff --git a/include/ableton/platforms/windows/Clock.hpp b/include/ableton/platforms/windows/Clock.hpp
index 87bfeab..b8f9029 100644
--- a/include/ableton/platforms/windows/Clock.hpp
+++ b/include/ableton/platforms/windows/Clock.hpp
@@ -38,17 +38,17 @@ struct Clock
{
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
- mTicksToMicros = 1.0e6 / frequency.QuadPart;
+ mTicksToMicros = 1.0e6 / static_cast<double>(frequency.QuadPart);
}
Micros ticksToMicros(const Ticks ticks) const
{
- return Micros{llround(mTicksToMicros * ticks)};
+ return Micros{llround(mTicksToMicros * static_cast<double>(ticks))};
}
Ticks microsToTicks(const Micros micros) const
{
- return static_cast<Ticks>(micros.count() / mTicksToMicros);
+ return static_cast<Ticks>(static_cast<double>(micros.count()) / mTicksToMicros);
}
Ticks ticks() const
diff --git a/examples/qlinkhut/BeatTile.qml b/include/ableton/platforms/windows/Windows.hpp
index 160a18f..0b835d1 100644
--- a/examples/qlinkhut/BeatTile.qml
+++ b/include/ableton/platforms/windows/Windows.hpp
@@ -1,4 +1,4 @@
-/* Copyright 2016, Ableton AG, Berlin. All rights reserved.
+/* Copyright 2020, 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
@@ -17,16 +17,16 @@
* please contact <link-devs@ableton.com>.
*/
-import QtQuick 2.0
+#pragma once
-Rectangle {
- property var index: 0
- property var currentBeat: -1
- property var countIn: false
- property var activeColor: "#404040"
- property var countInColor: "#909090"
- height: 328;
- border.width: 1;
- border.color: "#FFFFFF";
- color: index == currentBeat ? (countIn ? countInColor : activeColor) : "#404040"
-}
+// ntohll and htonll are not defined for MinGW
+
+#ifdef __MINGW32__
+#if __BIG_ENDIAN__
+#define htonll(x) (x)
+#define ntohll(x) (x)
+#else
+#define htonll(x) (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#define ntohll(x) (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#endif
+#endif
diff --git a/include/ableton/test/serial_io/Context.hpp b/include/ableton/test/serial_io/Context.hpp
index a2b1406..ecc738d 100644
--- a/include/ableton/test/serial_io/Context.hpp
+++ b/include/ableton/test/serial_io/Context.hpp
@@ -67,6 +67,10 @@ public:
{
}
+ void stop()
+ {
+ }
+
template <typename Handler>
void async(Handler handler)
{
diff --git a/include/ableton/test/serial_io/SchedulerTree.hpp b/include/ableton/test/serial_io/SchedulerTree.hpp
index e9f4766..6b85478 100644
--- a/include/ableton/test/serial_io/SchedulerTree.hpp
+++ b/include/ableton/test/serial_io/SchedulerTree.hpp
@@ -53,7 +53,7 @@ public:
void setTimer(const TimerId timerId, const TimePoint expiration, Handler handler)
{
using namespace std;
- mTimers[make_pair(move(expiration), timerId)] = move(handler);
+ mTimers[make_pair(std::move(expiration), timerId)] = std::move(handler);
}
void cancelTimer(const TimerId timerId);
diff --git a/include/ableton/test/serial_io/Socket.hpp b/include/ableton/test/serial_io/Socket.hpp
deleted file mode 100644
index 74b5c4c..0000000
--- a/include/ableton/test/serial_io/Socket.hpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/* 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 <http://www.gnu.org/licenses/>.
- *
- * If you would like to incorporate Link into a proprietary software application,
- * please contact <link-devs@ableton.com>.
- */
-
-#pragma once
-
-#include <ableton/platforms/AsioWrapper.hpp>
-#include <functional>
-
-namespace ableton
-{
-namespace test
-{
-namespace serial_io
-{
-
-struct Socket
-{
- using SendFn = std::function<std::size_t(
- const uint8_t* const, const size_t, const asio::ip::udp::endpoint&)>;
-
- struct ReceiveHandler
- {
- template <typename It>
- void operator()(const asio::ip::udp::endpoint& from, const It begin, const It end)
- {
- std::vector<uint8_t> buffer{begin, end};
- mReceive(from, buffer);
- }
-
- std::function<const asio::ip::udp::endpoint&, const std::vector<uint8_t>&> mReceive;
- };
-
- using ReceiveFn = std::function<void(ReceiveHandler)>;
-
- Socket(SendFn sendFn, ReceiveFn receiveFn)
- : mSendFn(std::move(sendFn))
- , mReceiveFn(std::move(receiveFn))
- {
- }
-
- std::size_t send(
- const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to)
- {
- return mSendFn(pData, numBytes, to);
- }
-
- template <typename Handler>
- void receive(Handler handler)
- {
- mReceiveFn(ReceiveHandler{
- [handler](const asio::ip::udp::endpoint& from, const std::vector<uint8_t>& buffer) {
- handler(from, begin(buffer), end(buffer));
- }});
- }
-
- asio::ip::udp::endpoint endpoint() const
- {
- return asio::ip::udp::endpoint({}, 0);
- }
-
- SendFn mSendFn;
- ReceiveFn mReceiveFn;
-};
-
-} // namespace serial_io
-} // namespace test
-} // namespace ableton
diff --git a/src/ableton/link/tst_Controller.cpp b/src/ableton/link/tst_Controller.cpp
index d65403c..4439cf2 100644
--- a/src/ableton/link/tst_Controller.cpp
+++ b/src/ableton/link/tst_Controller.cpp
@@ -19,7 +19,9 @@
#include <ableton/link/Controller.hpp>
#include <ableton/link/Tempo.hpp>
+#include <ableton/platforms/stl/Random.hpp>
#include <ableton/test/CatchWrapper.hpp>
+#include <ableton/util/Log.hpp>
#include <ableton/util/test/Timer.hpp>
namespace ableton
@@ -82,6 +84,10 @@ struct MockIoContext
}
};
+ void stop()
+ {
+ }
+
template <std::size_t BufferSize>
Socket<BufferSize> openUnicastSocket(const asio::ip::address_v4&)
{
@@ -151,6 +157,7 @@ using MockController = Controller<PeerCountCallback,
TempoCallback,
StartStopStateCallback,
MockClock,
+ platforms::stl::Random,
MockIoContext>;
const auto kAnyBeatTime = Beats{5.};
@@ -191,7 +198,7 @@ void testSetAndGetClientState(
const auto initialTimeline =
Optional<Timeline>{Timeline{Tempo{60.}, Beats{0.}, kAnyTime}};
const auto initialStartStopState =
- Optional<StartStopState>{StartStopState{true, kAnyBeatTime, clock.micros()}};
+ Optional<ClientStartStopState>{ClientStartStopState{false, kAnyTime, clock.micros()}};
const auto initialClientState =
IncomingClientState{initialTimeline, initialStartStopState, clock.micros()};
@@ -206,8 +213,8 @@ void testSetAndGetClientState(
{
// Set client state with a StartStopState having the same timestamp as the current
// StartStopState - don't advance clock
- const auto outdatedStartStopState =
- Optional<StartStopState>{StartStopState{false, kAnyBeatTime, clock.micros()}};
+ const auto outdatedStartStopState = Optional<ClientStartStopState>{
+ ClientStartStopState{false, kAnyTime, clock.micros()}};
setClientState(controller,
IncomingClientState{Optional<Timeline>{}, outdatedStartStopState, clock.micros()});
CHECK(initialClientState == getClientState(controller));
@@ -217,9 +224,8 @@ void testSetAndGetClientState(
SECTION("Set outdated start stop state (timestamp in past)")
{
- const auto outdatedStartStopState =
- Optional<StartStopState>{StartStopState{false, kAnyBeatTime, microseconds{0}}};
-
+ const auto outdatedStartStopState = Optional<ClientStartStopState>{
+ ClientStartStopState{false, kAnyTime, microseconds{0}}};
setClientState(controller,
IncomingClientState{Optional<Timeline>{}, outdatedStartStopState, clock.micros()});
CHECK(initialClientState == getClientState(controller));
@@ -228,7 +234,7 @@ void testSetAndGetClientState(
SECTION("Set empty client state")
{
setClientState(controller, IncomingClientState{Optional<Timeline>{},
- Optional<StartStopState>{}, clock.micros()});
+ Optional<ClientStartStopState>{}, clock.micros()});
CHECK(initialClientState == getClientState(controller));
}
@@ -236,8 +242,8 @@ void testSetAndGetClientState(
{
const auto expectedTimeline =
Optional<Timeline>{Timeline{Tempo{80.}, Beats{1.}, kAnyTime}};
- const auto expectedStartStopState =
- Optional<StartStopState>{StartStopState{false, kAnyBeatTime, clock.micros()}};
+ const auto expectedStartStopState = Optional<ClientStartStopState>{
+ ClientStartStopState{false, kAnyTime, clock.micros()}};
const auto expectedClientState =
IncomingClientState{expectedTimeline, expectedStartStopState, clock.micros()};
setClientState(controller, expectedClientState);
@@ -264,8 +270,8 @@ void testCallbackInvocation(SetClientStateFunctionT setClientState)
const auto initialTimeline =
Optional<Timeline>{Timeline{initialTempo, Beats{0.}, kAnyTime}};
- const auto initialStartStopState = Optional<StartStopState>{
- StartStopState{initialIsPlaying, kAnyBeatTime, clock.micros()}};
+ const auto initialStartStopState = Optional<ClientStartStopState>{
+ ClientStartStopState{initialIsPlaying, kAnyTime, clock.micros()}};
setClientState(controller, {initialTimeline, initialStartStopState, clock.micros()});
SECTION("Callbacks are called when setting new client state")
@@ -281,8 +287,8 @@ void testCallbackInvocation(SetClientStateFunctionT setClientState)
{
const auto timeline =
Optional<Timeline>{Timeline{initialTempo, Beats{1.}, kAnyTime}};
- const auto startStopState = Optional<StartStopState>{
- StartStopState{initialIsPlaying, kAnyBeatTime, clock.micros()}};
+ const auto startStopState = Optional<ClientStartStopState>{
+ ClientStartStopState{initialIsPlaying, kAnyTime, clock.micros()}};
setClientState(controller, {timeline, startStopState, clock.micros()});
CHECK(tempoCallback.tempos.empty());
CHECK(startStopStateCallback.startStopStates.empty());
@@ -408,7 +414,7 @@ TEST_CASE("Controller | GetClientStateRtSafeGracePeriod", "[Controller]")
const auto initialTimeline =
Optional<Timeline>{Timeline{Tempo{50.}, Beats{0.}, clock.micros()}};
const auto initialStartStopState =
- Optional<StartStopState>{StartStopState{true, kAnyBeatTime, clock.micros()}};
+ Optional<ClientStartStopState>{ClientStartStopState{true, kAnyTime, clock.micros()}};
const auto initialState =
IncomingClientState{initialTimeline, initialStartStopState, clock.micros()};
@@ -421,7 +427,7 @@ TEST_CASE("Controller | GetClientStateRtSafeGracePeriod", "[Controller]")
const auto newTimeline =
Optional<Timeline>{Timeline{Tempo{70.}, Beats{1.}, clock.micros()}};
const auto newStartStopState =
- Optional<StartStopState>{StartStopState{false, kAnyBeatTime, clock.micros()}};
+ Optional<ClientStartStopState>{ClientStartStopState{false, kAnyTime, clock.micros()}};
const auto newState =
IncomingClientState{newTimeline, newStartStopState, clock.micros()};
diff --git a/src/ableton/link/tst_Measurement.cpp b/src/ableton/link/tst_Measurement.cpp
index 9fea333..89edf1d 100644
--- a/src/ableton/link/tst_Measurement.cpp
+++ b/src/ableton/link/tst_Measurement.cpp
@@ -19,13 +19,12 @@
#include <ableton/discovery/Payload.hpp>
#include <ableton/discovery/test/Socket.hpp>
-#include <ableton/link/GhostXForm.hpp>
#include <ableton/link/Measurement.hpp>
#include <ableton/link/SessionId.hpp>
#include <ableton/link/v1/Messages.hpp>
-#include <ableton/platforms/asio/AsioWrapper.hpp>
+#include <ableton/platforms/stl/Random.hpp>
#include <ableton/test/CatchWrapper.hpp>
-#include <ableton/util/Log.hpp>
+#include <ableton/util/test/IoService.hpp>
#include <array>
namespace ableton
@@ -35,8 +34,6 @@ namespace link
namespace
{
-using Socket = discovery::test::Socket;
-
struct MockClock
{
std::chrono::microseconds micros() const
@@ -45,28 +42,56 @@ struct MockClock
}
};
+struct MockIoContext
+{
+ template <std::size_t BufferSize>
+ using Socket = discovery::test::Socket;
+
+ template <std::size_t BufferSize>
+ Socket<BufferSize> openUnicastSocket(const asio::ip::address_v4&)
+ {
+ return Socket<BufferSize>(mIo);
+ }
+
+ using Timer = util::test::Timer;
+
+ Timer makeTimer()
+ {
+ return {};
+ }
+
+ using Log = util::NullLog;
+
+ Log log() const
+ {
+ return {};
+ }
+
+ ableton::util::test::IoService mIo;
+};
+
struct TFixture
{
TFixture()
- : mAddress(asio::ip::address_v4::from_string("127.0.0.1"))
- , mMeasurement(mStateQuery(),
+ : mMeasurement(mStateQuery(),
[](std::vector<std::pair<double, double>>) {},
- mAddress,
+ {},
MockClock{},
- util::injectVal(util::NullLog{}))
+ MockIoContext{})
{
}
- Socket& socket()
+ discovery::test::Socket socket()
{
- return *(mMeasurement.mpImpl->mpSocket);
+ return mMeasurement.mpImpl->mSocket;
}
struct StateQuery
{
StateQuery()
{
- mState.nodeState.sessionId = NodeId::random();
+ using Random = ableton::platforms::stl::Random;
+ mState.nodeState.sessionId = NodeId::random<Random>();
mState.endpoint =
asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 9999);
}
@@ -78,24 +103,13 @@ struct TFixture
PeerState mState;
};
- struct GhostXFormQuery
- {
- GhostXForm operator()() const
- {
- return {1.0, std::chrono::microseconds{0}};
- }
- };
-
StateQuery mStateQuery;
- GhostXFormQuery mGhostXFormQuery;
- asio::ip::address_v4 mAddress;
- Measurement<util::test::IoService, MockClock, discovery::test::Socket, util::NullLog>
- mMeasurement;
+ Measurement<MockClock, MockIoContext> mMeasurement;
};
} // anonymous namespace
-TEST_CASE("Measurement | SendPingsOnConstruction", "[Measurement]")
+TEST_CASE("PeerMeasurement | SendPingsOnConstruction", "[PeerMeasurement]")
{
TFixture fixture;
CHECK(1 == fixture.socket().sentMessages.size());
@@ -116,7 +130,7 @@ TEST_CASE("Measurement | SendPingsOnConstruction", "[Measurement]")
CHECK(std::chrono::microseconds{0} == gt);
}
-TEST_CASE("Measurement | ReceiveInitPong", "[Measurement]")
+TEST_CASE("PeerMeasurement | ReceiveInitPong", "[PeerMeasurement]")
{
using Micros = std::chrono::microseconds;
TFixture fixture;
@@ -138,7 +152,7 @@ TEST_CASE("Measurement | ReceiveInitPong", "[Measurement]")
CHECK(0 == fixture.mMeasurement.mpImpl->mData.size());
}
-TEST_CASE("Measurement | ReceivePong", "[Measurement]")
+TEST_CASE("PeerMeasurement | ReceivePong", "[PeerMeasurement]")
{
using Micros = std::chrono::microseconds;
TFixture fixture;
diff --git a/src/ableton/link/tst_Peers.cpp b/src/ableton/link/tst_Peers.cpp
index b1ebb95..a8060f5 100644
--- a/src/ableton/link/tst_Peers.cpp
+++ b/src/ableton/link/tst_Peers.cpp
@@ -18,6 +18,7 @@
*/
#include <ableton/link/Peers.hpp>
+#include <ableton/platforms/stl/Random.hpp>
#include <ableton/test/CatchWrapper.hpp>
#include <ableton/test/serial_io/Fixture.hpp>
@@ -28,6 +29,8 @@ namespace link
namespace
{
+using Random = ableton::platforms::stl::Random;
+
struct SessionMembershipCallback
{
void operator()()
@@ -59,18 +62,18 @@ struct SessionStartStopStateCallback
};
const auto fooPeer =
- PeerState{{NodeId::random(), NodeId::random(),
+ PeerState{{NodeId::random<Random>(), NodeId::random<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(),
+ PeerState{{NodeId::random<Random>(), NodeId::random<Random>(),
Timeline{Tempo{120.}, Beats{10.}, std::chrono::microseconds{500}}, {}},
{}};
const auto bazPeer =
- PeerState{{NodeId::random(), NodeId::random(),
+ PeerState{{NodeId::random<Random>(), NodeId::random<Random>(),
Timeline{Tempo{100.}, Beats{4.}, std::chrono::microseconds{100}}, {}},
{}};
@@ -113,10 +116,10 @@ TEST_CASE("Peers | EmptySessionPeersAfterInit", "[Peers]")
TEST_CASE("Peers | AddAndFindPeer", "[Peers]")
{
- test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto sessions = SessionTimelineCallback{};
auto startStops = SessionStartStopStateCallback{};
+ test::serial_io::Fixture io;
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
std::ref(sessions), std::ref(startStops));
auto observer = makeGatewayObserver(peers, gateway1);
@@ -133,8 +136,8 @@ TEST_CASE("Peers | AddAndFindPeer", "[Peers]")
TEST_CASE("Peers | AddAndRemovePeer", "[Peers]")
{
- test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
+ test::serial_io::Fixture io;
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
SessionTimelineCallback{}, SessionStartStopStateCallback{});
auto observer = makeGatewayObserver(peers, gateway1);
@@ -149,10 +152,10 @@ TEST_CASE("Peers | AddAndRemovePeer", "[Peers]")
TEST_CASE("Peers | AddTwoPeersRemoveOne", "[Peers]")
{
- test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto sessions = SessionTimelineCallback{};
auto startStops = SessionStartStopStateCallback{};
+ test::serial_io::Fixture io;
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
std::ref(sessions), std::ref(startStops));
auto observer = makeGatewayObserver(peers, gateway1);
@@ -169,10 +172,10 @@ TEST_CASE("Peers | AddTwoPeersRemoveOne", "[Peers]")
TEST_CASE("Peers | AddThreePeersTwoOnSameGateway", "[Peers]")
{
- test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto sessions = SessionTimelineCallback{};
auto startStops = SessionStartStopStateCallback{};
+ test::serial_io::Fixture io;
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
std::ref(sessions), std::ref(startStops));
auto observer1 = makeGatewayObserver(peers, gateway1);
@@ -191,10 +194,10 @@ TEST_CASE("Peers | AddThreePeersTwoOnSameGateway", "[Peers]")
TEST_CASE("Peers | CloseGateway", "[Peers]")
{
- test::serial_io::Fixture io;
auto membership = SessionMembershipCallback{};
auto sessions = SessionTimelineCallback{};
auto startStops = SessionStartStopStateCallback{};
+ test::serial_io::Fixture io;
auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership),
std::ref(sessions), std::ref(startStops));
auto observer1 = makeGatewayObserver(peers, gateway1);
diff --git a/src/ableton/link/tst_PingResponder.cpp b/src/ableton/link/tst_PingResponder.cpp
index f6a7568..8f0c095 100644
--- a/src/ableton/link/tst_PingResponder.cpp
+++ b/src/ableton/link/tst_PingResponder.cpp
@@ -23,11 +23,9 @@
#include <ableton/link/NodeState.hpp>
#include <ableton/link/PayloadEntries.hpp>
#include <ableton/link/PingResponder.hpp>
-#include <ableton/link/SessionId.hpp>
#include <ableton/link/v1/Messages.hpp>
-#include <ableton/platforms/asio/AsioWrapper.hpp>
+#include <ableton/platforms/stl/Random.hpp>
#include <ableton/test/CatchWrapper.hpp>
-#include <ableton/util/Log.hpp>
#include <ableton/util/test/IoService.hpp>
#include <array>
@@ -38,16 +36,52 @@ namespace link
namespace
{
+using Random = ableton::platforms::stl::Random;
+
+struct MockClock
+{
+ std::chrono::microseconds micros() const
+ {
+ return std::chrono::microseconds{4};
+ }
+};
+
+struct MockIoContext
+{
+ template <std::size_t BufferSize>
+ using Socket = discovery::test::Socket;
+
+ template <std::size_t BufferSize>
+ Socket<BufferSize> openUnicastSocket(const asio::ip::address_v4&)
+ {
+ return Socket<BufferSize>(mIo);
+ }
+
+ using Log = util::NullLog;
+
+ Log log() const
+ {
+ return {};
+ }
+
+ template <typename Handler>
+ void async(Handler handler) const
+ {
+ handler();
+ }
+
+ ableton::util::test::IoService mIo;
+};
+
struct RpFixture
{
RpFixture()
: mAddress(asio::ip::address_v4::from_string("127.0.0.1"))
, mResponder(mAddress,
- NodeId::random(),
+ NodeId::random<Random>(),
GhostXForm{1.0, std::chrono::microseconds{0}},
- util::injectVal(util::test::IoService{}),
MockClock{},
- util::injectVal(util::NullLog{}))
+ util::injectRef(*mIo))
{
}
@@ -61,17 +95,9 @@ struct RpFixture
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<util::test::IoService, MockClock, discovery::test::Socket, util::NullLog>
- mResponder;
+ util::Injected<MockIoContext> mIo;
+ PingResponder<MockClock, MockIoContext> mResponder;
};
} // anonymous namespace