diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8171541..6e68d803 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -219,6 +219,7 @@ target_link_libraries (sfizz_parser PUBLIC absl::strings PRIVATE absl::flat_hash # OSC messaging library set (SFIZZ_MESSAGING_HEADERS sfizz/Messaging.h + sfizz/Messaging.hpp sfizz_message.h) set (SFIZZ_MESSAGING_SOURCES diff --git a/src/sfizz/Messaging.h b/src/sfizz/Messaging.h index 737fa1d4..0f48587b 100644 --- a/src/sfizz/Messaging.h +++ b/src/sfizz/Messaging.h @@ -9,6 +9,10 @@ namespace sfz { +template struct OscDataTraits; +template using OscType = typename OscDataTraits::type; +template using OscDecayedType = typename OscDataTraits::decayed_type; + class Client { public: explicit Client(void* data) : data_(data) {} @@ -17,15 +21,18 @@ public: bool canReceive() const { return receive_ != nullptr; } void receive(int delay, const char* path, const char* sig, const sfizz_arg_t* args); + template + void receive(int delay, const char* path, OscDecayedType... values); + +private: + template + sfizz_arg_t make_arg(OscDecayedType value); + private: void* data_ = nullptr; sfizz_receive_t* receive_ = nullptr; }; -inline void Client::receive(int delay, const char* path, const char* sig, const sfizz_arg_t* args) -{ - if (receive_) - receive_(data_, delay, path, sig, args); -} - } // namespace sfz + +#include "Messaging.hpp" diff --git a/src/sfizz/Messaging.hpp b/src/sfizz/Messaging.hpp new file mode 100644 index 00000000..587e671d --- /dev/null +++ b/src/sfizz/Messaging.hpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "Messaging.h" +#include +#include +#include + +namespace sfz { + +/// +inline void Client::receive(int delay, const char* path, const char* sig, const sfizz_arg_t* args) +{ + if (receive_) + receive_(data_, delay, path, sig, args); +} + +template +inline void Client::receive(int delay, const char* path, OscDecayedType... values) +{ + if (receive_) { + constexpr size_t size = sizeof...(Sig); + char sig[size + 1] { Sig..., '\0' }; + sfizz_arg_t args[size] { OscDataTraits::make_arg(values)... }; + receive_(data_, delay, path, sig, args); + } +} + +/// +#define OSC_SCALAR_TRAITS(tag, member) \ + template <> struct OscDataTraits { \ + typedef decltype(sfizz_arg_t::member) type; \ + typedef type decayed_type; \ + static_assert(std::is_scalar::value, ""); \ + static inline sfizz_arg_t make_arg(decayed_type v) { \ + sfizz_arg_t a; a.member = v; return a; \ + } \ + } +#define OSC_BYTEARRAY_TRAITS(tag, member) \ + template <> struct OscDataTraits { \ + typedef decltype(sfizz_arg_t::member) type; \ + static_assert(std::is_array::value, ""); \ + typedef const typename std::remove_all_extents::type* decayed_type; \ + static inline sfizz_arg_t make_arg(decayed_type v) { \ + sfizz_arg_t a; \ + std::memcpy(a.member, v, sizeof(a.member)); \ + return a; \ + } \ + } +#define OSC_VOID_TRAITS(tag) \ + template <> struct OscDataTraits { \ + typedef struct Nothing {} type; \ + typedef type decayed_type; \ + static inline sfizz_arg_t make_arg(decayed_type v) { \ + sfizz_arg_t a; (void)v; return a; \ + } \ + } + +OSC_SCALAR_TRAITS('i', i); +OSC_SCALAR_TRAITS('c', i); +OSC_SCALAR_TRAITS('r', i); +OSC_BYTEARRAY_TRAITS('m', m); +OSC_SCALAR_TRAITS('h', h); +OSC_SCALAR_TRAITS('f', f); +OSC_SCALAR_TRAITS('d', d); +OSC_SCALAR_TRAITS('s', s); +OSC_SCALAR_TRAITS('S', s); +OSC_SCALAR_TRAITS('b', b); +OSC_VOID_TRAITS('T'); +OSC_VOID_TRAITS('F'); +OSC_VOID_TRAITS('N'); +OSC_VOID_TRAITS('I'); + +#undef OSC_SCALAR_TRAITS +#undef OSC_BYTEARRAY_TRAITS +#undef OSC_VOID_TRAITS + +} // namespace sfz diff --git a/tests/MessagingT.cpp b/tests/MessagingT.cpp index 2b52df56..5496ca82 100644 --- a/tests/MessagingT.cpp +++ b/tests/MessagingT.cpp @@ -93,3 +93,33 @@ TEST_CASE("[Messaging] OSC message creation") REQUIRE(args2[4].f == 5.678f); } } + +TEST_CASE("[Messaging] Type-safe client API") +{ + sfz::Client client(nullptr); + + static const int32_t i = 777; + static const int64_t h = 0x100000000LL; + static const float f = 3.14f; + static const double d = 6.28; + static const uint8_t m[4] = {0x90, 0x40, 0xFF}; + static const sfizz_blob_t b { reinterpret_cast("MyBinaryString"), 14 }; + static const char s[] = "Hello, World!"; + + client.setReceiveCallback(+[](void*, int, const char* path, const char* sig, const sfizz_arg_t* args) { + REQUIRE(!strcmp(path, "/test")); + REQUIRE(!strcmp(sig, "imhfdsbTFNI")); + unsigned index = 0; + REQUIRE(args[index++].i == i); + REQUIRE(!memcmp(args[index++].m, m, 4)); + REQUIRE(args[index++].h == h); + REQUIRE(args[index++].f == f); + REQUIRE(args[index++].d == d); + REQUIRE(!strcmp(args[index++].s, s)); + REQUIRE(args[index ].b->data == b.data); + REQUIRE(args[index++].b->size == b.size); + }); + + client.receive<'i', 'm', 'h', 'f', 'd', 's', 'b', 'T', 'F', 'N', 'I'>( + 0, "/test", i, m, h, f, d, s, &b, {}, {}, {}, {}); +}