diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b319dcbe3a..816cb571fc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1066,6 +1066,8 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/preferences/dialog/dlgpreflibrarydlg.ui src/preferences/dialog/dlgprefmixer.cpp src/preferences/dialog/dlgprefmixerdlg.ui + src/preferences/dialog/dlgprefosc.cpp + src/preferences/dialog/dlgprefoscdlg.ui src/preferences/dialog/dlgprefrecord.cpp src/preferences/dialog/dlgprefrecorddlg.ui src/preferences/dialog/dlgprefreplaygain.cpp @@ -2557,6 +2559,42 @@ add_library(rekordbox_metadata STATIC EXCLUDE_FROM_ALL target_include_directories(rekordbox_metadata SYSTEM PUBLIC lib/rekordbox-metadata) target_link_libraries(mixxx-lib PRIVATE rekordbox_metadata) +IF(WIN32) + set(IpSystemTypePath src/osc/ip/win32) + set(LIBS ${LIBS} Ws2_32 winmm) + ELSE(WIN32) + set(IpSystemTypePath src/osc/ip/posix) + ENDIF(WIN32) + +#eve osc +ADD_LIBRARY(oscpack + src/osc/ip/IpEndpointName.cpp + src/osc/ip/IpEndpointName.h + src/osc/ip/NetworkingUtils.h + ${IpSystemTypePath}/NetworkingUtils.cpp + src/osc/ip/PacketListener.h + src/osc/ip/TimerListener.h + src/osc/ip/UdpSocket.h + ${IpSystemTypePath}/UdpSocket.cpp + src/osc/osc/MessageMappingOscPacketListener.h + src/osc/osc/OscException.h + src/osc/osc/OscHostEndianness.h + src/osc/osc/OscOutboundPacketStream.cpp + src/osc/osc/OscOutboundPacketStream.h + src/osc/osc/OscPacketListener.h + src/osc/osc/OscPrintReceivedElements.cpp + src/osc/osc/OscPrintReceivedElements.h + src/osc/osc/OscReceivedElements.cpp + src/osc/osc/OscReceivedElements.h + src/osc/osc/OscTypes.cpp + src/osc/osc/OscTypes.h + ) +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}) +#target_link_libraries(mixxx oscpack ${LIBS}) +target_include_directories(mixxx-lib SYSTEM PRIVATE ip) +target_include_directories(mixxx-lib SYSTEM PRIVATE osc) +target_link_libraries(mixxx-lib PRIVATE oscpack) + #silence "enumeration values not handled in switch" in generated code if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(rekordbox_metadata PRIVATE -Wno-switch) diff --git a/src/RCa13796 b/src/RCa13796 new file mode 100644 index 00000000000..7487ae5139f Binary files /dev/null and b/src/RCa13796 differ diff --git a/src/RCb13796 b/src/RCb13796 new file mode 100644 index 00000000000..7487ae5139f Binary files /dev/null and b/src/RCb13796 differ diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 68ba74dc41f..0eb758f06f5 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -54,6 +54,17 @@ const QString kAppGroup = QStringLiteral("[App]"); } // anonymous namespace +// EveOSC +void OscTrackLoadedInGroup(UserSettingsPointer m_pConfig, + const QString& OscGroup, + const QString& TrackArtist, + const QString& TrackTitle, + float track_loaded, + float duration, + float playposition); +void OscNoTrackLoadedInGroup(UserSettingsPointer m_pConfig, const QString& OscGroup); +// EveOSC + EngineBuffer::EngineBuffer(const QString& group, UserSettingsPointer pConfig, EngineChannel* pChannel, @@ -563,6 +574,18 @@ void EngineBuffer::slotTrackLoaded(TrackPointer pTrack, m_pTrackSampleRate->set(trackSampleRate.toDouble()); m_pTrackLoaded->forceSet(1); + // EveOSC begin + if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) { + OscTrackLoadedInGroup(m_pConfig, + getGroup(), + pTrack->getArtist().toLatin1(), + pTrack->getTitle().toLatin1(), + (float)1, + (float)pTrack->getDuration(), + (float)0); + } + // EveOSC end + // Reset slip mode m_pSlipButton->set(0); m_bSlipEnabledProcessing = false; @@ -632,6 +655,12 @@ void EngineBuffer::ejectTrack() { m_pTrackSampleRate->set(0); m_pTrackLoaded->forceSet(0); + // EveOSC begin + if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) { + OscNoTrackLoadedInGroup(m_pConfig, getGroup()); + } + // EveOSC end + m_playButton->set(0.0); m_playposSlider->set(0); m_pCueControl->resetIndicators(); diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 1b8162c7fe9..4f6c1a4f3b4 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -35,6 +35,12 @@ inline double trackColorToDouble(mixxx::RgbColor::optional_t color) { } } // namespace +// EveOSC +void OscChangedPlayState(UserSettingsPointer m_pConfig, + const QString& OscGroup, + float playstate); +// EveOSC + BaseTrackPlayer::BaseTrackPlayer(PlayerManager* pParent, const QString& group) : BasePlayer(pParent, group) { } @@ -924,6 +930,11 @@ void BaseTrackPlayerImpl::slotPlayToggled(double value) { if (value == 0 && m_replaygainPending) { setReplayGain(m_pLoadedTrack->getReplayGain().getRatio()); } + // EveOSC begin + if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) { + OscChangedPlayState(m_pConfig, getGroup(), (float)value); + } + // EveOSC endc } EngineDeck* BaseTrackPlayerImpl::getEngineDeck() const { diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp index c5b44237bf9..145ad84fb39 100644 --- a/src/mixxxmainwindow.cpp +++ b/src/mixxxmainwindow.cpp @@ -62,6 +62,11 @@ #include "vinylcontrol/vinylcontrolmanager.h" #endif +// EveOSC +#include "osc/oscfunctions.h" +#include "osc/oscreceiver.cpp" +// EveOSC + namespace { #ifdef __LINUX__ // Detect if the desktop supports a global menu to decide whether we need to rebuild @@ -132,6 +137,9 @@ MixxxMainWindow::MixxxMainWindow(std::shared_ptr pCoreServi m_pGuiTick = new GuiTick(); m_pVisualsManager = new VisualsManager(); + // EveOSC + oscEnable(); + // EveOSC } #ifdef MIXXX_USE_QOPENGL @@ -1445,3 +1453,13 @@ void MixxxMainWindow::initializationProgressUpdate(int progress, const QString& } qApp->processEvents(); } + +void MixxxMainWindow::oscEnable() { + UserSettingsPointer pConfig; + if (m_pCoreServices->getSettings()->getValue(ConfigKey("[OSC]", "OscEnabled"))) { + qDebug() << "Mixxx OSC Service Enabled"; + OscReceiverMain(m_pCoreServices->getSettings()); + } else { + qDebug() << "Mixxx OSC Service NOT Enabled"; + } +} diff --git a/src/mixxxmainwindow.h b/src/mixxxmainwindow.h index 1c673578ce3..4cde9e6afbc 100644 --- a/src/mixxxmainwindow.h +++ b/src/mixxxmainwindow.h @@ -104,6 +104,9 @@ class MixxxMainWindow : public QMainWindow { private: void initializeWindow(); void checkDirectRendering(); + // EveOSC + void oscEnable(); + // EveOSC /// Load skin to a QWidget that we set as the central widget. bool loadConfiguredSkin(); diff --git a/src/osc/ip/IpEndpointName.cpp b/src/osc/ip/IpEndpointName.cpp new file mode 100644 index 00000000000..d7b768ceeb2 --- /dev/null +++ b/src/osc/ip/IpEndpointName.cpp @@ -0,0 +1,110 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#include "IpEndpointName.h" + +#include + +#include "NetworkingUtils.h" + +unsigned long IpEndpointName::GetHostByName(const char* s) { + return ::GetHostByName(s); +} + +void IpEndpointName::AddressAsString(char* s) const { + if (address == ANY_ADDRESS) { + // std::sprintf(s, ""); + std::snprintf(s, 16, ""); + } else { + // std::sprintf(s, + // "%d.%d.%d.%d", + // (int)((address >> 24) & 0xFF), + // (int)((address >> 16) & 0xFF), + // (int)((address >> 8) & 0xFF), + // (int)(address & 0xFF)); + std::snprintf(s, + 16, + "%d.%d.%d.%d", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF)); + } +} + +void IpEndpointName::AddressAndPortAsString(char* s) const { + if (port == ANY_PORT) { + if (address == ANY_ADDRESS) { + // std::sprintf(s, ":"); + std::snprintf(s, 16, ":"); + } else { + // std::sprintf(s, + // "%d.%d.%d.%d:", + // (int)((address >> 24) & 0xFF), + // (int)((address >> 16) & 0xFF), + // (int)((address >> 8) & 0xFF), + // (int)(address & 0xFF)); + std::snprintf(s, + 16, + "%d.%d.%d.%d:", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF)); + } + } else { + if (address == ANY_ADDRESS) { + // std::sprintf(s, ":%d", port); + std::snprintf(s, 16, ":%d", port); + } else { + // std::sprintf(s, + // "%d.%d.%d.%d:%d", + // (int)((address >> 24) & 0xFF), + // (int)((address >> 16) & 0xFF), + // (int)((address >> 8) & 0xFF), + // (int)(address & 0xFF), + // (int)port); + std::snprintf(s, + 16, + "%d.%d.%d.%d:%d", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF), + (int)port); + } + } +} diff --git a/src/osc/ip/IpEndpointName.h b/src/osc/ip/IpEndpointName.h new file mode 100644 index 00000000000..07cf0845634 --- /dev/null +++ b/src/osc/ip/IpEndpointName.h @@ -0,0 +1,91 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_IPENDPOINTNAME_H +#define INCLUDED_OSCPACK_IPENDPOINTNAME_H + +class IpEndpointName { + static unsigned long GetHostByName(const char* s); + + public: + static const unsigned long ANY_ADDRESS = 0xFFFFFFFF; + static const int ANY_PORT = -1; + + IpEndpointName() + : address(ANY_ADDRESS), + port(ANY_PORT) { + } + IpEndpointName(int port_) + : address(ANY_ADDRESS), + port(port_) { + } + IpEndpointName(unsigned long ipAddress_, int port_) + : address(ipAddress_), + port(port_) { + } + IpEndpointName(const char* addressName, int port_ = ANY_PORT) + : address(GetHostByName(addressName)), + port(port_) { + } + IpEndpointName(int addressA, int addressB, int addressC, int addressD, int port_ = ANY_PORT) + : address(((addressA << 24) | (addressB << 16) | (addressC << 8) | addressD)), + port(port_) { + } + + // address and port are maintained in host byte order here + unsigned long address; + int port; + + bool IsMulticastAddress() const { + return ((address >> 24) & 0xFF) >= 224 && ((address >> 24) & 0xFF) <= 239; + } + + enum { ADDRESS_STRING_LENGTH = 17 }; + void AddressAsString(char* s) const; + + enum { ADDRESS_AND_PORT_STRING_LENGTH = 23 }; + void AddressAndPortAsString(char* s) const; +}; + +inline bool operator==(const IpEndpointName& lhs, const IpEndpointName& rhs) { + return (lhs.address == rhs.address && lhs.port == rhs.port); +} + +inline bool operator!=(const IpEndpointName& lhs, const IpEndpointName& rhs) { + return !(lhs == rhs); +} + +#endif /* INCLUDED_OSCPACK_IPENDPOINTNAME_H */ diff --git a/src/osc/ip/NetworkingUtils.h b/src/osc/ip/NetworkingUtils.h new file mode 100644 index 00000000000..690c7d651a0 --- /dev/null +++ b/src/osc/ip/NetworkingUtils.h @@ -0,0 +1,53 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_NETWORKINGUTILS_H +#define INCLUDED_OSCPACK_NETWORKINGUTILS_H + +// in general NetworkInitializer is only used internally, but if you're +// application creates multiple sockets from different threads at runtime you +// should instantiate one of these in main just to make sure the networking +// layer is initialized. +class NetworkInitializer { + public: + NetworkInitializer(); + ~NetworkInitializer(); +}; + +// return ip address of host name in host byte order +unsigned long GetHostByName(const char* name); + +#endif /* INCLUDED_OSCPACK_NETWORKINGUTILS_H */ diff --git a/src/osc/ip/PacketListener.h b/src/osc/ip/PacketListener.h new file mode 100644 index 00000000000..3c61a6e5440 --- /dev/null +++ b/src/osc/ip/PacketListener.h @@ -0,0 +1,51 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_PACKETLISTENER_H +#define INCLUDED_OSCPACK_PACKETLISTENER_H + +class IpEndpointName; + +class PacketListener { + public: + virtual ~PacketListener() { + } + virtual void ProcessPacket(const char* data, + int size, + const IpEndpointName& remoteEndpoint) = 0; +}; + +#endif /* INCLUDED_OSCPACK_PACKETLISTENER_H */ diff --git a/src/osc/ip/TimerListener.h b/src/osc/ip/TimerListener.h new file mode 100644 index 00000000000..b6d5a983ea5 --- /dev/null +++ b/src/osc/ip/TimerListener.h @@ -0,0 +1,47 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_TIMERLISTENER_H +#define INCLUDED_OSCPACK_TIMERLISTENER_H + +class TimerListener { + public: + virtual ~TimerListener() { + } + virtual void TimerExpired() = 0; +}; + +#endif /* INCLUDED_OSCPACK_TIMERLISTENER_H */ diff --git a/src/osc/ip/UdpSocket.h b/src/osc/ip/UdpSocket.h new file mode 100644 index 00000000000..7462ab770d4 --- /dev/null +++ b/src/osc/ip/UdpSocket.h @@ -0,0 +1,181 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_UDPSOCKET_H +#define INCLUDED_OSCPACK_UDPSOCKET_H + +#include // size_t + +#include "IpEndpointName.h" +#include "NetworkingUtils.h" + +class PacketListener; +class TimerListener; + +class UdpSocket; + +class SocketReceiveMultiplexer { + class Implementation; + Implementation* impl_; + + friend class UdpSocket; + + public: + SocketReceiveMultiplexer(); + ~SocketReceiveMultiplexer(); + + // only call the attach/detach methods _before_ calling Run + + // only one listener per socket, each socket at most once + void AttachSocketListener(UdpSocket* socket, PacketListener* listener); + void DetachSocketListener(UdpSocket* socket, PacketListener* listener); + + void AttachPeriodicTimerListener(int periodMilliseconds, TimerListener* listener); + void AttachPeriodicTimerListener( + int initialDelayMilliseconds, int periodMilliseconds, TimerListener* listener); + void DetachPeriodicTimerListener(TimerListener* listener); + + void Run(); // loop and block processing messages indefinitely + void RunUntilSigInt(); + void Break(); // call this from a listener to exit once the listener returns + void AsynchronousBreak(); // call this from another thread or signal handler + // to exit the Run() state +}; + +class UdpSocket { + class Implementation; + Implementation* impl_; + + friend class SocketReceiveMultiplexer::Implementation; + + public: + // Ctor throws std::runtime_error if there's a problem + // initializing the socket. + UdpSocket(); + virtual ~UdpSocket(); + + // Enable broadcast addresses (e.g. x.x.x.255) + // Sets SO_BROADCAST socket option. + void SetEnableBroadcast(bool enableBroadcast); + + // Enable multiple listeners for a single port on same + // network interface* + // Sets SO_REUSEADDR (also SO_REUSEPORT on OS X). + // [*] The exact behavior of SO_REUSEADDR and + // SO_REUSEPORT is undefined for some common cases + // and may have drastically different behavior on different + // operating systems. + void SetAllowReuse(bool allowReuse); + + // The socket is created in an unbound, unconnected state + // such a socket can only be used to send to an arbitrary + // address using SendTo(). To use Send() you need to first + // connect to a remote endpoint using Connect(). To use + // ReceiveFrom you need to first bind to a local endpoint + // using Bind(). + + // Retrieve the local endpoint name when sending to 'to' + IpEndpointName LocalEndpointFor(const IpEndpointName& remoteEndpoint) const; + + // Connect to a remote endpoint which is used as the target + // for calls to Send() + void Connect(const IpEndpointName& remoteEndpoint); + void Send(const char* data, std::size_t size); + void SendTo(const IpEndpointName& remoteEndpoint, const char* data, std::size_t size); + + // Bind a local endpoint to receive incoming data. Endpoint + // can be 'any' for the system to choose an endpoint + void Bind(const IpEndpointName& localEndpoint); + bool IsBound() const; + + std::size_t ReceiveFrom(IpEndpointName& remoteEndpoint, char* data, std::size_t size); +}; + +// convenience classes for transmitting and receiving +// they just call Connect and/or Bind in the ctor. +// note that you can still use a receive socket +// for transmitting etc + +class UdpTransmitSocket : public UdpSocket { + public: + UdpTransmitSocket(const IpEndpointName& remoteEndpoint) { + Connect(remoteEndpoint); + } +}; + +class UdpReceiveSocket : public UdpSocket { + public: + UdpReceiveSocket(const IpEndpointName& localEndpoint) { + Bind(localEndpoint); + } +}; + +// UdpListeningReceiveSocket provides a simple way to bind one listener +// to a single socket without having to manually set up a SocketReceiveMultiplexer + +class UdpListeningReceiveSocket : public UdpSocket { + SocketReceiveMultiplexer mux_; + PacketListener* listener_; + + public: + UdpListeningReceiveSocket(const IpEndpointName& localEndpoint, PacketListener* listener) + : listener_(listener) + + { + Bind(localEndpoint); + mux_.AttachSocketListener(this, listener_); + } + + ~UdpListeningReceiveSocket() { + mux_.DetachSocketListener(this, listener_); + } + + // see SocketReceiveMultiplexer above for the behaviour of these methods... + void Run() { + mux_.Run(); + } + void RunUntilSigInt() { + mux_.RunUntilSigInt(); + } + void Break() { + mux_.Break(); + } + void AsynchronousBreak() { + mux_.AsynchronousBreak(); + } +}; + +#endif /* INCLUDED_OSCPACK_UDPSOCKET_H */ diff --git a/src/osc/ip/posix/NetworkingUtils.cpp b/src/osc/ip/posix/NetworkingUtils.cpp new file mode 100644 index 00000000000..d9f6ec62f85 --- /dev/null +++ b/src/osc/ip/posix/NetworkingUtils.cpp @@ -0,0 +1,62 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#include "../NetworkingUtils.h" + +#include +#include +#include + +#include + +NetworkInitializer::NetworkInitializer() { +} + +NetworkInitializer::~NetworkInitializer() { +} + +unsigned long GetHostByName(const char* name) { + unsigned long result = 0; + + struct hostent* h = gethostbyname(name); + if (h) { + struct in_addr a; + std::memcpy(&a, h->h_addr_list[0], h->h_length); + result = ntohl(a.s_addr); + } + + return result; +} diff --git a/src/osc/ip/posix/UdpSocket.cpp b/src/osc/ip/posix/UdpSocket.cpp new file mode 100644 index 00000000000..632919ba90e --- /dev/null +++ b/src/osc/ip/posix/UdpSocket.cpp @@ -0,0 +1,577 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#include "../UdpSocket.h" + +#include +#include +#include +#include // for sockaddr_in +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include // for memset +#include +#include + +#include "../PacketListener.h" +#include "../TimerListener.h" + +#if defined(__APPLE__) && !defined(_SOCKLEN_T) +// pre system 10.3 didn't have socklen_t +typedef ssize_t socklen_t; +#endif + +static void SockaddrFromIpEndpointName( + struct sockaddr_in& sockAddr, const IpEndpointName& endpoint) { + std::memset((char*)&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin_family = AF_INET; + + sockAddr.sin_addr.s_addr = + (endpoint.address == IpEndpointName::ANY_ADDRESS) + ? INADDR_ANY + : htonl(endpoint.address); + + sockAddr.sin_port = + (endpoint.port == IpEndpointName::ANY_PORT) + ? 0 + : htons(endpoint.port); +} + +static IpEndpointName IpEndpointNameFromSockaddr(const struct sockaddr_in& sockAddr) { + return IpEndpointName( + (sockAddr.sin_addr.s_addr == INADDR_ANY) + ? IpEndpointName::ANY_ADDRESS + : ntohl(sockAddr.sin_addr.s_addr), + (sockAddr.sin_port == 0) + ? IpEndpointName::ANY_PORT + : ntohs(sockAddr.sin_port)); +} + +class UdpSocket::Implementation { + bool isBound_; + bool isConnected_; + + int socket_; + struct sockaddr_in connectedAddr_; + struct sockaddr_in sendToAddr_; + + public: + Implementation() + : isBound_(false), + isConnected_(false), + socket_(-1) { + if ((socket_ = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + throw std::runtime_error("unable to create udp socket\n"); + } + + std::memset(&sendToAddr_, 0, sizeof(sendToAddr_)); + sendToAddr_.sin_family = AF_INET; + } + + ~Implementation() { + if (socket_ != -1) + close(socket_); + } + + void SetEnableBroadcast(bool enableBroadcast) { + int broadcast = (enableBroadcast) ? 1 : 0; // int on posix + setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); + } + + void SetAllowReuse(bool allowReuse) { + int reuseAddr = (allowReuse) ? 1 : 0; // int on posix + setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)); + +#ifdef __APPLE__ + // needed also for OS X - enable multiple listeners for a single port on + // same network interface + int reusePort = (allowReuse) ? 1 : 0; // int on posix + setsockopt(socket_, SOL_SOCKET, SO_REUSEPORT, &reusePort, sizeof(reusePort)); +#endif + } + + IpEndpointName LocalEndpointFor(const IpEndpointName& remoteEndpoint) const { + assert(isBound_); + + // first connect the socket to the remote server + + struct sockaddr_in connectSockAddr; + SockaddrFromIpEndpointName(connectSockAddr, remoteEndpoint); + + if (connect(socket_, (struct sockaddr*)&connectSockAddr, sizeof(connectSockAddr)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + // get the address + + struct sockaddr_in sockAddr; + std::memset((char*)&sockAddr, 0, sizeof(sockAddr)); + socklen_t length = sizeof(sockAddr); + if (getsockname(socket_, (struct sockaddr*)&sockAddr, &length) < 0) { + throw std::runtime_error("unable to getsockname\n"); + } + + if (isConnected_) { + // reconnect to the connected address + + if (connect(socket_, (struct sockaddr*)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + } else { + // unconnect from the remote address + + struct sockaddr_in unconnectSockAddr; + std::memset((char*)&unconnectSockAddr, 0, sizeof(unconnectSockAddr)); + unconnectSockAddr.sin_family = AF_UNSPEC; + // address fields are zero + int connectResult = connect(socket_, + (struct sockaddr*)&unconnectSockAddr, + sizeof(unconnectSockAddr)); + if (connectResult < 0 && errno != EAFNOSUPPORT) { + throw std::runtime_error("unable to un-connect udp socket\n"); + } + } + + return IpEndpointNameFromSockaddr(sockAddr); + } + + void Connect(const IpEndpointName& remoteEndpoint) { + SockaddrFromIpEndpointName(connectedAddr_, remoteEndpoint); + + if (connect(socket_, (struct sockaddr*)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + isConnected_ = true; + } + + void Send(const char* data, std::size_t size) { + assert(isConnected_); + + send(socket_, data, size, 0); + } + + void SendTo(const IpEndpointName& remoteEndpoint, const char* data, std::size_t size) { + sendToAddr_.sin_addr.s_addr = htonl(remoteEndpoint.address); + sendToAddr_.sin_port = htons(remoteEndpoint.port); + + sendto(socket_, data, size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_)); + } + + void Bind(const IpEndpointName& localEndpoint) { + struct sockaddr_in bindSockAddr; + SockaddrFromIpEndpointName(bindSockAddr, localEndpoint); + + if (bind(socket_, (struct sockaddr*)&bindSockAddr, sizeof(bindSockAddr)) < 0) { + throw std::runtime_error("unable to bind udp socket\n"); + } + + isBound_ = true; + } + + bool IsBound() const { + return isBound_; + } + + std::size_t ReceiveFrom(IpEndpointName& remoteEndpoint, char* data, std::size_t size) { + assert(isBound_); + + struct sockaddr_in fromAddr; + socklen_t fromAddrLen = sizeof(fromAddr); + + ssize_t result = recvfrom(socket_, + data, + size, + 0, + (struct sockaddr*)&fromAddr, + (socklen_t*)&fromAddrLen); + if (result < 0) + return 0; + + remoteEndpoint.address = ntohl(fromAddr.sin_addr.s_addr); + remoteEndpoint.port = ntohs(fromAddr.sin_port); + + return (std::size_t)result; + } + + int Socket() { + return socket_; + } +}; + +UdpSocket::UdpSocket() { + impl_ = new Implementation(); +} + +UdpSocket::~UdpSocket() { + delete impl_; +} + +void UdpSocket::SetEnableBroadcast(bool enableBroadcast) { + impl_->SetEnableBroadcast(enableBroadcast); +} + +void UdpSocket::SetAllowReuse(bool allowReuse) { + impl_->SetAllowReuse(allowReuse); +} + +IpEndpointName UdpSocket::LocalEndpointFor(const IpEndpointName& remoteEndpoint) const { + return impl_->LocalEndpointFor(remoteEndpoint); +} + +void UdpSocket::Connect(const IpEndpointName& remoteEndpoint) { + impl_->Connect(remoteEndpoint); +} + +void UdpSocket::Send(const char* data, std::size_t size) { + impl_->Send(data, size); +} + +void UdpSocket::SendTo(const IpEndpointName& remoteEndpoint, const char* data, std::size_t size) { + impl_->SendTo(remoteEndpoint, data, size); +} + +void UdpSocket::Bind(const IpEndpointName& localEndpoint) { + impl_->Bind(localEndpoint); +} + +bool UdpSocket::IsBound() const { + return impl_->IsBound(); +} + +std::size_t UdpSocket::ReceiveFrom(IpEndpointName& remoteEndpoint, char* data, std::size_t size) { + return impl_->ReceiveFrom(remoteEndpoint, data, size); +} + +struct AttachedTimerListener { + AttachedTimerListener(int id, int p, TimerListener* tl) + : initialDelayMs(id), + periodMs(p), + listener(tl) { + } + int initialDelayMs; + int periodMs; + TimerListener* listener; +}; + +static bool CompareScheduledTimerCalls( + const std::pair& lhs, + const std::pair& rhs) { + return lhs.first < rhs.first; +} + +SocketReceiveMultiplexer* multiplexerInstanceToAbortWithSigInt_ = 0; + +extern "C" /*static*/ void InterruptSignalHandler(int); +/*static*/ void InterruptSignalHandler(int) { + multiplexerInstanceToAbortWithSigInt_->AsynchronousBreak(); + signal(SIGINT, SIG_DFL); +} + +class SocketReceiveMultiplexer::Implementation { + std::vector> socketListeners_; + std::vector timerListeners_; + + volatile bool break_; + int breakPipe_[2]; // [0] is the reader descriptor and [1] the writer + + double GetCurrentTimeMs() const { + struct timeval t; + + gettimeofday(&t, 0); + + return ((double)t.tv_sec * 1000.) + ((double)t.tv_usec / 1000.); + } + + public: + Implementation() { + if (pipe(breakPipe_) != 0) + throw std::runtime_error("creation of asynchronous break pipes failed\n"); + } + + ~Implementation() { + close(breakPipe_[0]); + close(breakPipe_[1]); + } + + void AttachSocketListener(UdpSocket* socket, PacketListener* listener) { + assert(std::find(socketListeners_.begin(), + socketListeners_.end(), + std::make_pair(listener, socket)) == + socketListeners_.end()); + // we don't check that the same socket has been added multiple times, + // even though this is an error + socketListeners_.push_back(std::make_pair(listener, socket)); + } + + void DetachSocketListener(UdpSocket* socket, PacketListener* listener) { + std::vector>::iterator i = + std::find(socketListeners_.begin(), + socketListeners_.end(), + std::make_pair(listener, socket)); + assert(i != socketListeners_.end()); + + socketListeners_.erase(i); + } + + void AttachPeriodicTimerListener(int periodMilliseconds, TimerListener* listener) { + timerListeners_.push_back(AttachedTimerListener( + periodMilliseconds, periodMilliseconds, listener)); + } + + void AttachPeriodicTimerListener(int initialDelayMilliseconds, + int periodMilliseconds, + TimerListener* listener) { + timerListeners_.push_back(AttachedTimerListener( + initialDelayMilliseconds, periodMilliseconds, listener)); + } + + void DetachPeriodicTimerListener(TimerListener* listener) { + std::vector::iterator i = timerListeners_.begin(); + while (i != timerListeners_.end()) { + if (i->listener == listener) + break; + ++i; + } + + assert(i != timerListeners_.end()); + + timerListeners_.erase(i); + } + + void Run() { + break_ = false; + char* data = 0; + + try { + // configure the master fd_set for select() + + fd_set masterfds, tempfds; + FD_ZERO(&masterfds); + FD_ZERO(&tempfds); + + // in addition to listening to the inbound sockets we + // also listen to the asynchronous break pipe, so that AsynchronousBreak() + // can break us out of select() from another thread. + FD_SET(breakPipe_[0], &masterfds); + int fdmax = breakPipe_[0]; + + for (std::vector>::iterator + i = socketListeners_.begin(); + i != socketListeners_.end(); + ++i) { + if (fdmax < i->second->impl_->Socket()) + fdmax = i->second->impl_->Socket(); + FD_SET(i->second->impl_->Socket(), &masterfds); + } + + // configure the timer queue + double currentTimeMs = GetCurrentTimeMs(); + + // expiry time ms, listener + std::vector> timerQueue_; + for (std::vector::iterator i = timerListeners_.begin(); + i != timerListeners_.end(); + ++i) + timerQueue_.push_back(std::make_pair(currentTimeMs + i->initialDelayMs, *i)); + std::sort(timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls); + + const int MAX_BUFFER_SIZE = 4098; + data = new char[MAX_BUFFER_SIZE]; + IpEndpointName remoteEndpoint; + + struct timeval timeout; + + while (!break_) { + tempfds = masterfds; + + struct timeval* timeoutPtr = 0; + if (!timerQueue_.empty()) { + double timeoutMs = timerQueue_.front().first - GetCurrentTimeMs(); + if (timeoutMs < 0) + timeoutMs = 0; + + long timoutSecondsPart = (long)(timeoutMs * .001); + timeout.tv_sec = (time_t)timoutSecondsPart; + // 1000000 microseconds in a second + timeout.tv_usec = + (suseconds_t)((timeoutMs - + (timoutSecondsPart * 1000)) * + 1000); + timeoutPtr = &timeout; + } + + if (select(fdmax + 1, &tempfds, 0, 0, timeoutPtr) < 0) { + if (break_) { + break; + } else if (errno == EINTR) { + // on returning an error, select() doesn't clear tempfds. + // so tempfds would remain all set, which would cause read( breakPipe_[0]... + // below to block indefinitely. therefore if select returns EINTR we restart + // the while() loop instead of continuing on to below. + continue; + } else { + throw std::runtime_error("select failed\n"); + } + } + + if (FD_ISSET(breakPipe_[0], &tempfds)) { + // clear pending data from the asynchronous break pipe + char c; + read(breakPipe_[0], &c, 1); + } + + if (break_) + break; + + for (std::vector>:: + iterator i = socketListeners_.begin(); + i != socketListeners_.end(); + ++i) { + if (FD_ISSET(i->second->impl_->Socket(), &tempfds)) { + std::size_t size = i->second->ReceiveFrom( + remoteEndpoint, data, MAX_BUFFER_SIZE); + if (size > 0) { + i->first->ProcessPacket(data, (int)size, remoteEndpoint); + if (break_) + break; + } + } + } + + // execute any expired timers + currentTimeMs = GetCurrentTimeMs(); + bool resort = false; + for (std::vector>:: + iterator i = timerQueue_.begin(); + i != timerQueue_.end() && i->first <= currentTimeMs; + ++i) { + i->second.listener->TimerExpired(); + if (break_) + break; + + i->first += i->second.periodMs; + resort = true; + } + if (resort) + std::sort(timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls); + } + + delete[] data; + } catch (...) { + if (data) + delete[] data; + throw; + } + } + + void Break() { + break_ = true; + } + + void AsynchronousBreak() { + break_ = true; + + // Send a termination message to the asynchronous break pipe, so select() will return + write(breakPipe_[1], "!", 1); + } +}; + +SocketReceiveMultiplexer::SocketReceiveMultiplexer() { + impl_ = new Implementation(); +} + +SocketReceiveMultiplexer::~SocketReceiveMultiplexer() { + delete impl_; +} + +void SocketReceiveMultiplexer::AttachSocketListener(UdpSocket* socket, PacketListener* listener) { + impl_->AttachSocketListener(socket, listener); +} + +void SocketReceiveMultiplexer::DetachSocketListener(UdpSocket* socket, PacketListener* listener) { + impl_->DetachSocketListener(socket, listener); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( + int periodMilliseconds, TimerListener* listener) { + impl_->AttachPeriodicTimerListener(periodMilliseconds, listener); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( + int initialDelayMilliseconds, + int periodMilliseconds, + TimerListener* listener) { + impl_->AttachPeriodicTimerListener(initialDelayMilliseconds, periodMilliseconds, listener); +} + +void SocketReceiveMultiplexer::DetachPeriodicTimerListener(TimerListener* listener) { + impl_->DetachPeriodicTimerListener(listener); +} + +void SocketReceiveMultiplexer::Run() { + impl_->Run(); +} + +void SocketReceiveMultiplexer::RunUntilSigInt() { + assert(multiplexerInstanceToAbortWithSigInt_ == + 0); /* at present we support only one multiplexer instance running + until sig int */ + multiplexerInstanceToAbortWithSigInt_ = this; + signal(SIGINT, InterruptSignalHandler); + impl_->Run(); + signal(SIGINT, SIG_DFL); + multiplexerInstanceToAbortWithSigInt_ = 0; +} + +void SocketReceiveMultiplexer::Break() { + impl_->Break(); +} + +void SocketReceiveMultiplexer::AsynchronousBreak() { + impl_->AsynchronousBreak(); +} diff --git a/src/osc/ip/win32/NetworkingUtils.cpp b/src/osc/ip/win32/NetworkingUtils.cpp new file mode 100644 index 00000000000..e3967ab23d5 --- /dev/null +++ b/src/osc/ip/win32/NetworkingUtils.cpp @@ -0,0 +1,89 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +// #include "ip/NetworkingUtils.h" +#include "../NetworkingUtils.h" + +#include + +#include + +static LONG initCount_ = 0; +static bool winsockInitialized_ = false; + +NetworkInitializer::NetworkInitializer() { + if (InterlockedIncrement(&initCount_) == 1) { + // there is a race condition here if one thread tries to access + // the library while another is still initializing it. + // i can't think of an easy way to fix it so i'm telling you here + // in case you need to init the library from two threads at once. + // this is why the header file advises to instantiate one of these + // in main() so that the initialization happens globally + + // initialize winsock + WSAData wsaData; + int nCode = WSAStartup(MAKEWORD(1, 1), &wsaData); + if (nCode != 0) { + // std::cout << "WSAStartup() failed with error code " << nCode << "\n"; + } else { + winsockInitialized_ = true; + } + } +} + +NetworkInitializer::~NetworkInitializer() { + if (InterlockedDecrement(&initCount_) == 0) { + if (winsockInitialized_) { + WSACleanup(); + winsockInitialized_ = false; + } + } +} + +unsigned long GetHostByName(const char* name) { + NetworkInitializer networkInitializer; + + unsigned long result = 0; + + struct hostent* h = gethostbyname(name); + if (h) { + struct in_addr a; + std::memcpy(&a, h->h_addr_list[0], h->h_length); + result = ntohl(a.s_addr); + } + + return result; +} diff --git a/src/osc/ip/win32/UdpSocket.cpp b/src/osc/ip/win32/UdpSocket.cpp new file mode 100644 index 00000000000..dc0780f8040 --- /dev/null +++ b/src/osc/ip/win32/UdpSocket.cpp @@ -0,0 +1,563 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ + +#include // this must come first to prevent errors with MSVC7 +// #include +// #include // for timeGetTime() + +#ifndef WINCE +#include +#endif + +#include +#include +#include // for memset +#include +#include + +#include "../UdpSocket.h" // usually I'd include the module header first + // but this is causing conflicts with BCB4 due to + // std::size_t usage. + +#include "../NetworkingUtils.h" +#include "../PacketListener.h" +#include "../TimerListener.h" + +typedef int socklen_t; + +static void SockaddrFromIpEndpointName( + struct sockaddr_in& sockAddr, const IpEndpointName& endpoint) { + std::memset((char*)&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin_family = AF_INET; + + sockAddr.sin_addr.s_addr = + (endpoint.address == IpEndpointName::ANY_ADDRESS) + ? INADDR_ANY + : htonl(endpoint.address); + + sockAddr.sin_port = + (endpoint.port == IpEndpointName::ANY_PORT) + ? (short)0 + : htons((short)endpoint.port); +} + +static IpEndpointName IpEndpointNameFromSockaddr(const struct sockaddr_in& sockAddr) { + return IpEndpointName( + (sockAddr.sin_addr.s_addr == INADDR_ANY) + ? IpEndpointName::ANY_ADDRESS + : ntohl(sockAddr.sin_addr.s_addr), + (sockAddr.sin_port == 0) + ? IpEndpointName::ANY_PORT + : ntohs(sockAddr.sin_port)); +} + +class UdpSocket::Implementation { + NetworkInitializer networkInitializer_; + + bool isBound_; + bool isConnected_; + + SOCKET socket_; + struct sockaddr_in connectedAddr_; + struct sockaddr_in sendToAddr_; + + public: + Implementation() + : isBound_(false), + isConnected_(false), + socket_(INVALID_SOCKET) { + if ((socket_ = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) { + throw std::runtime_error("unable to create udp socket\n"); + } + + std::memset(&sendToAddr_, 0, sizeof(sendToAddr_)); + sendToAddr_.sin_family = AF_INET; + } + + ~Implementation() { + if (socket_ != INVALID_SOCKET) + closesocket(socket_); + } + + void SetEnableBroadcast(bool enableBroadcast) { + char broadcast = (char)((enableBroadcast) ? 1 : 0); // char on win32 + setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); + } + + void SetAllowReuse(bool allowReuse) { + // Note: SO_REUSEADDR is non-deterministic for listening sockets on Win32. See MSDN article: + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" + // http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx + + char reuseAddr = (char)((allowReuse) ? 1 : 0); // char on win32 + setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)); + } + + IpEndpointName LocalEndpointFor(const IpEndpointName& remoteEndpoint) const { + assert(isBound_); + + // first connect the socket to the remote server + + struct sockaddr_in connectSockAddr; + SockaddrFromIpEndpointName(connectSockAddr, remoteEndpoint); + + if (connect(socket_, (struct sockaddr*)&connectSockAddr, sizeof(connectSockAddr)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + // get the address + + struct sockaddr_in sockAddr; + std::memset((char*)&sockAddr, 0, sizeof(sockAddr)); + socklen_t length = sizeof(sockAddr); + if (getsockname(socket_, (struct sockaddr*)&sockAddr, &length) < 0) { + throw std::runtime_error("unable to getsockname\n"); + } + + if (isConnected_) { + // reconnect to the connected address + + if (connect(socket_, (struct sockaddr*)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + } else { + // unconnect from the remote address + + struct sockaddr_in unconnectSockAddr; + SockaddrFromIpEndpointName(unconnectSockAddr, IpEndpointName()); + + if (connect(socket_, + (struct sockaddr*)&unconnectSockAddr, + sizeof(unconnectSockAddr)) < 0 && + WSAGetLastError() != WSAEADDRNOTAVAIL) { + throw std::runtime_error("unable to un-connect udp socket\n"); + } + } + + return IpEndpointNameFromSockaddr(sockAddr); + } + + void Connect(const IpEndpointName& remoteEndpoint) { + SockaddrFromIpEndpointName(connectedAddr_, remoteEndpoint); + + if (connect(socket_, (struct sockaddr*)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + isConnected_ = true; + } + + void Send(const char* data, std::size_t size) { + assert(isConnected_); + + send(socket_, data, (int)size, 0); + } + + void SendTo(const IpEndpointName& remoteEndpoint, const char* data, std::size_t size) { + sendToAddr_.sin_addr.s_addr = htonl(remoteEndpoint.address); + sendToAddr_.sin_port = htons((short)remoteEndpoint.port); + + sendto(socket_, data, (int)size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_)); + } + + void Bind(const IpEndpointName& localEndpoint) { + struct sockaddr_in bindSockAddr; + SockaddrFromIpEndpointName(bindSockAddr, localEndpoint); + + if (bind(socket_, (struct sockaddr*)&bindSockAddr, sizeof(bindSockAddr)) < 0) { + throw std::runtime_error("unable to bind udp socket\n"); + } + + isBound_ = true; + } + + bool IsBound() const { + return isBound_; + } + + std::size_t ReceiveFrom(IpEndpointName& remoteEndpoint, char* data, std::size_t size) { + assert(isBound_); + + struct sockaddr_in fromAddr; + socklen_t fromAddrLen = sizeof(fromAddr); + + int result = recvfrom(socket_, + data, + (int)size, + 0, + (struct sockaddr*)&fromAddr, + (socklen_t*)&fromAddrLen); + if (result < 0) + return 0; + + remoteEndpoint.address = ntohl(fromAddr.sin_addr.s_addr); + remoteEndpoint.port = ntohs(fromAddr.sin_port); + + return result; + } + + SOCKET& Socket() { + return socket_; + } +}; + +UdpSocket::UdpSocket() { + impl_ = new Implementation(); +} + +UdpSocket::~UdpSocket() { + delete impl_; +} + +void UdpSocket::SetEnableBroadcast(bool enableBroadcast) { + impl_->SetEnableBroadcast(enableBroadcast); +} + +void UdpSocket::SetAllowReuse(bool allowReuse) { + impl_->SetAllowReuse(allowReuse); +} + +IpEndpointName UdpSocket::LocalEndpointFor(const IpEndpointName& remoteEndpoint) const { + return impl_->LocalEndpointFor(remoteEndpoint); +} + +void UdpSocket::Connect(const IpEndpointName& remoteEndpoint) { + impl_->Connect(remoteEndpoint); +} + +void UdpSocket::Send(const char* data, std::size_t size) { + impl_->Send(data, size); +} + +void UdpSocket::SendTo(const IpEndpointName& remoteEndpoint, const char* data, std::size_t size) { + impl_->SendTo(remoteEndpoint, data, size); +} + +void UdpSocket::Bind(const IpEndpointName& localEndpoint) { + impl_->Bind(localEndpoint); +} + +bool UdpSocket::IsBound() const { + return impl_->IsBound(); +} + +std::size_t UdpSocket::ReceiveFrom(IpEndpointName& remoteEndpoint, char* data, std::size_t size) { + return impl_->ReceiveFrom(remoteEndpoint, data, size); +} + +struct AttachedTimerListener { + AttachedTimerListener(int id, int p, TimerListener* tl) + : initialDelayMs(id), + periodMs(p), + listener(tl) { + } + int initialDelayMs; + int periodMs; + TimerListener* listener; +}; + +static bool CompareScheduledTimerCalls( + const std::pair& lhs, + const std::pair& rhs) { + return lhs.first < rhs.first; +} + +SocketReceiveMultiplexer* multiplexerInstanceToAbortWithSigInt_ = 0; + +extern "C" /*static*/ void InterruptSignalHandler(int); +/*static*/ void InterruptSignalHandler(int) { + multiplexerInstanceToAbortWithSigInt_->AsynchronousBreak(); +#ifndef WINCE + signal(SIGINT, SIG_DFL); +#endif +} + +class SocketReceiveMultiplexer::Implementation { + NetworkInitializer networkInitializer_; + + std::vector> socketListeners_; + std::vector timerListeners_; + + volatile bool break_; + HANDLE breakEvent_; + + double GetCurrentTimeMs() const { +#ifndef WINCE + // EVE + // return timeGetTime(); // FIXME: bad choice if you want to run for more than 40 days + // QTime tm = QTime::currentTime(); + // DWORD dtime = tm.msecsSinceStartOfDay(); + // return QTime::msecsSinceStartOfDay(); + return 0; + // double TimeStamp = QDateTime::currentDateTime().toDouble(); + +// EVE +#else + return 0; +#endif + } + + public: + Implementation() { + breakEvent_ = CreateEvent(NULL, FALSE, FALSE, NULL); + } + + ~Implementation() { + CloseHandle(breakEvent_); + } + + void AttachSocketListener(UdpSocket* socket, PacketListener* listener) { + assert(std::find(socketListeners_.begin(), + socketListeners_.end(), + std::make_pair(listener, socket)) == + socketListeners_.end()); + // we don't check that the same socket has been added multiple times, + // even though this is an error + socketListeners_.push_back(std::make_pair(listener, socket)); + } + + void DetachSocketListener(UdpSocket* socket, PacketListener* listener) { + std::vector>::iterator i = + std::find(socketListeners_.begin(), + socketListeners_.end(), + std::make_pair(listener, socket)); + assert(i != socketListeners_.end()); + + socketListeners_.erase(i); + } + + void AttachPeriodicTimerListener(int periodMilliseconds, TimerListener* listener) { + timerListeners_.push_back(AttachedTimerListener( + periodMilliseconds, periodMilliseconds, listener)); + } + + void AttachPeriodicTimerListener(int initialDelayMilliseconds, + int periodMilliseconds, + TimerListener* listener) { + timerListeners_.push_back(AttachedTimerListener( + initialDelayMilliseconds, periodMilliseconds, listener)); + } + + void DetachPeriodicTimerListener(TimerListener* listener) { + std::vector::iterator i = timerListeners_.begin(); + while (i != timerListeners_.end()) { + if (i->listener == listener) + break; + ++i; + } + + assert(i != timerListeners_.end()); + + timerListeners_.erase(i); + } + + void Run() { + break_ = false; + + // prepare the window events which we use to wake up on incoming data + // we use this instead of select() primarily to support the AsyncBreak() + // mechanism. + + std::vector events(socketListeners_.size() + 1, 0); + int j = 0; + for (std::vector>::iterator i = + socketListeners_.begin(); + i != socketListeners_.end(); + ++i, ++j) { + HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL); + WSAEventSelect(i->second->impl_->Socket(), + event, + FD_READ); // note that this makes the socket non-blocking + // which is why we can safely call RecieveFrom() + // on all sockets below + events[j] = event; + } + + events[socketListeners_.size()] = + breakEvent_; // last event in the collection is the break event + + // configure the timer queue + double currentTimeMs = GetCurrentTimeMs(); + + // expiry time ms, listener + std::vector> timerQueue_; + for (std::vector::iterator i = timerListeners_.begin(); + i != timerListeners_.end(); + ++i) + timerQueue_.push_back(std::make_pair(currentTimeMs + i->initialDelayMs, *i)); + std::sort(timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls); + + const int MAX_BUFFER_SIZE = 4098; + char* data = new char[MAX_BUFFER_SIZE]; + IpEndpointName remoteEndpoint; + + while (!break_) { + double currentTimeMs = GetCurrentTimeMs(); + + DWORD waitTime = INFINITE; + if (!timerQueue_.empty()) { + waitTime = (DWORD)(timerQueue_.front().first >= currentTimeMs + ? timerQueue_.front().first - currentTimeMs + : 0); + } + + DWORD waitResult = + WaitForMultipleObjects((DWORD)socketListeners_.size() + 1, + &events[0], + FALSE, + waitTime); + if (break_) + break; + + if (waitResult != WAIT_TIMEOUT) { + for (int i = waitResult - WAIT_OBJECT_0; i < (int)socketListeners_.size(); ++i) { + std::size_t size = socketListeners_[i].second->ReceiveFrom( + remoteEndpoint, data, MAX_BUFFER_SIZE); + if (size > 0) { + socketListeners_[i].first->ProcessPacket(data, (int)size, remoteEndpoint); + if (break_) + break; + } + } + } + + // execute any expired timers + currentTimeMs = GetCurrentTimeMs(); + bool resort = false; + for (std::vector>::iterator + i = timerQueue_.begin(); + i != timerQueue_.end() && i->first <= currentTimeMs; + ++i) { + i->second.listener->TimerExpired(); + if (break_) + break; + + i->first += i->second.periodMs; + resort = true; + } + if (resort) + std::sort(timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls); + } + + delete[] data; + + // free events + j = 0; + for (std::vector>::iterator i = + socketListeners_.begin(); + i != socketListeners_.end(); + ++i, ++j) { + WSAEventSelect(i->second->impl_->Socket(), + events[j], + 0); // remove association between socket and event + CloseHandle(events[j]); + unsigned long enableNonblocking = 0; + ioctlsocket(i->second->impl_->Socket(), + FIONBIO, + &enableNonblocking); // make the socket blocking again + } + } + + void Break() { + break_ = true; + } + + void AsynchronousBreak() { + break_ = true; + SetEvent(breakEvent_); + } +}; + +SocketReceiveMultiplexer::SocketReceiveMultiplexer() { + impl_ = new Implementation(); +} + +SocketReceiveMultiplexer::~SocketReceiveMultiplexer() { + delete impl_; +} + +void SocketReceiveMultiplexer::AttachSocketListener(UdpSocket* socket, PacketListener* listener) { + impl_->AttachSocketListener(socket, listener); +} + +void SocketReceiveMultiplexer::DetachSocketListener(UdpSocket* socket, PacketListener* listener) { + impl_->DetachSocketListener(socket, listener); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( + int periodMilliseconds, TimerListener* listener) { + impl_->AttachPeriodicTimerListener(periodMilliseconds, listener); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( + int initialDelayMilliseconds, + int periodMilliseconds, + TimerListener* listener) { + impl_->AttachPeriodicTimerListener(initialDelayMilliseconds, periodMilliseconds, listener); +} + +void SocketReceiveMultiplexer::DetachPeriodicTimerListener(TimerListener* listener) { + impl_->DetachPeriodicTimerListener(listener); +} + +void SocketReceiveMultiplexer::Run() { + impl_->Run(); +} + +void SocketReceiveMultiplexer::RunUntilSigInt() { + assert(multiplexerInstanceToAbortWithSigInt_ == + 0); /* at present we support only one multiplexer instance running + until sig int */ + multiplexerInstanceToAbortWithSigInt_ = this; +#ifndef WINCE + signal(SIGINT, InterruptSignalHandler); +#endif + impl_->Run(); +#ifndef WINCE + signal(SIGINT, SIG_DFL); +#endif + multiplexerInstanceToAbortWithSigInt_ = 0; +} + +void SocketReceiveMultiplexer::Break() { + impl_->Break(); +} + +void SocketReceiveMultiplexer::AsynchronousBreak() { + impl_->AsynchronousBreak(); +} diff --git a/src/osc/osc/MessageMappingOscPacketListener.h b/src/osc/osc/MessageMappingOscPacketListener.h new file mode 100644 index 00000000000..29a527ea001 --- /dev/null +++ b/src/osc/osc/MessageMappingOscPacketListener.h @@ -0,0 +1,77 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_MESSAGEMAPPINGOSCPACKETLISTENER_H +#define INCLUDED_OSCPACK_MESSAGEMAPPINGOSCPACKETLISTENER_H + +#include +#include + +#include "OscPacketListener.h" + +namespace osc { + +template +class MessageMappingOscPacketListener : public OscPacketListener { + public: + typedef void (T::*function_type)(const osc::ReceivedMessage&, const IpEndpointName&); + + protected: + void RegisterMessageFunction(const char* addressPattern, function_type f) { + functions_.insert(std::make_pair(addressPattern, f)); + } + + virtual void ProcessMessage(const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint) { + typename function_map_type::iterator i = functions_.find(m.AddressPattern()); + if (i != functions_.end()) + (dynamic_cast(this)->*(i->second))(m, remoteEndpoint); + } + + private: + struct cstr_compare { + bool operator()(const char* lhs, const char* rhs) const { + return std::strcmp(lhs, rhs) < 0; + } + }; + + typedef std::map function_map_type; + function_map_type functions_; +}; + +} // namespace osc + +#endif /* INCLUDED_OSCPACK_MESSAGEMAPPINGOSCPACKETLISTENER_H */ diff --git a/src/osc/osc/OscException.h b/src/osc/osc/OscException.h new file mode 100644 index 00000000000..93f5e1027c5 --- /dev/null +++ b/src/osc/osc/OscException.h @@ -0,0 +1,70 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_OSCEXCEPTION_H +#define INCLUDED_OSCPACK_OSCEXCEPTION_H + +#include + +namespace osc { + +class Exception : public std::exception { + const char* what_; + + public: + Exception() throw() { + } + Exception(const Exception& src) throw() + : std::exception(src), + what_(src.what_) { + } + Exception(const char* w) throw() + : what_(w) { + } + Exception& operator=(const Exception& src) throw() { + what_ = src.what_; + return *this; + } + virtual ~Exception() throw() { + } + virtual const char* what() const throw() { + return what_; + } +}; + +} // namespace osc + +#endif /* INCLUDED_OSCPACK_OSCEXCEPTION_H */ diff --git a/src/osc/osc/OscHostEndianness.h b/src/osc/osc/OscHostEndianness.h new file mode 100644 index 00000000000..0bc525de8a1 --- /dev/null +++ b/src/osc/osc/OscHostEndianness.h @@ -0,0 +1,126 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_OSCHOSTENDIANNESS_H +#define INCLUDED_OSCPACK_OSCHOSTENDIANNESS_H + +/* + Make sure either OSC_HOST_LITTLE_ENDIAN or OSC_HOST_BIG_ENDIAN is defined + + We try to use preprocessor symbols to deduce the host endianness. + + Alternatively you can define one of the above symbols from the command line. + Usually you do this with the -D flag to the compiler. e.g.: + + $ g++ -DOSC_HOST_LITTLE_ENDIAN ... +*/ + +#if defined(OSC_HOST_LITTLE_ENDIAN) || defined(OSC_HOST_BIG_ENDIAN) + +// endianness defined on the command line. nothing to do here. + +#elif defined(__WIN32__) || defined(WIN32) || defined(WINCE) + +// assume that __WIN32__ is only defined on little endian systems + +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN + +#elif defined(__APPLE__) + +#if defined(__LITTLE_ENDIAN__) + +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN + +#elif defined(__BIG_ENDIAN__) + +#define OSC_HOST_BIG_ENDIAN 1 +#undef OSC_HOST_LITTLE_ENDIAN + +#endif + +#elif defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) + +// should cover gcc and clang + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN + +#elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + +#define OSC_HOST_BIG_ENDIAN 1 +#undef OSC_HOST_LITTLE_ENDIAN + +#endif + +#else + +// gcc defines __LITTLE_ENDIAN__ and __BIG_ENDIAN__ +// for others used here see http://sourceforge.net/p/predef/wiki/Endianness/ +#if (defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)) || \ + (defined(__ARMEL__) && !defined(__ARMEB__)) || \ + (defined(__AARCH64EL__) && !defined(__AARCH64EB__)) || \ + (defined(_MIPSEL) && !defined(_MIPSEB)) || \ + (defined(__MIPSEL) && !defined(__MIPSEB)) || \ + (defined(__MIPSEL__) && !defined(__MIPSEB__)) + +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN + +#elif (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) || \ + (defined(__ARMEB__) && !defined(__ARMEL__)) || \ + (defined(__AARCH64EB__) && !defined(__AARCH64EL__)) || \ + (defined(_MIPSEB) && !defined(_MIPSEL)) || \ + (defined(__MIPSEB) && !defined(__MIPSEL)) || \ + (defined(__MIPSEB__) && !defined(__MIPSEL__)) + +#define OSC_HOST_BIG_ENDIAN 1 +#undef OSC_HOST_LITTLE_ENDIAN + +#endif + +#endif + +#if !defined(OSC_HOST_LITTLE_ENDIAN) && !defined(OSC_HOST_BIG_ENDIAN) + +#error please edit OSCHostEndianness.h or define one of {OSC_HOST_LITTLE_ENDIAN, OSC_HOST_BIG_ENDIAN} to configure endianness + +#endif + +#endif /* INCLUDED_OSCPACK_OSCHOSTENDIANNESS_H */ diff --git a/src/osc/osc/OscOutboundPacketStream.cpp b/src/osc/osc/OscOutboundPacketStream.cpp new file mode 100644 index 00000000000..e52caedef0c --- /dev/null +++ b/src/osc/osc/OscOutboundPacketStream.cpp @@ -0,0 +1,600 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#include "OscOutboundPacketStream.h" + +#if defined(__WIN32__) || defined(WIN32) || defined(_WIN32) +#include // for alloca +#else +// #include // alloca on Linux (also OSX) +#include // alloca on OSX and FreeBSD (and Linux?) +#endif + +#include +#include // ptrdiff_t +#include // memcpy, memmove, strcpy, strlen + +#include "OscHostEndianness.h" + +#if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug +namespace std { +using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'. +} +#endif + +namespace osc { + +static void FromInt32(char* p, int32 x) { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::int32 i; + char c[4]; + } u; + + u.i = x; + + p[3] = u.c[0]; + p[2] = u.c[1]; + p[1] = u.c[2]; + p[0] = u.c[3]; +#else + *reinterpret_cast(p) = x; +#endif +} + +static void FromUInt32(char* p, uint32 x) { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::uint32 i; + char c[4]; + } u; + + u.i = x; + + p[3] = u.c[0]; + p[2] = u.c[1]; + p[1] = u.c[2]; + p[0] = u.c[3]; +#else + *reinterpret_cast(p) = x; +#endif +} + +static void FromInt64(char* p, int64 x) { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::int64 i; + char c[8]; + } u; + + u.i = x; + + p[7] = u.c[0]; + p[6] = u.c[1]; + p[5] = u.c[2]; + p[4] = u.c[3]; + p[3] = u.c[4]; + p[2] = u.c[5]; + p[1] = u.c[6]; + p[0] = u.c[7]; +#else + *reinterpret_cast(p) = x; +#endif +} + +static void FromUInt64(char* p, uint64 x) { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::uint64 i; + char c[8]; + } u; + + u.i = x; + + p[7] = u.c[0]; + p[6] = u.c[1]; + p[5] = u.c[2]; + p[4] = u.c[3]; + p[3] = u.c[4]; + p[2] = u.c[5]; + p[1] = u.c[6]; + p[0] = u.c[7]; +#else + *reinterpret_cast(p) = x; +#endif +} + +// round up to the next highest multiple of 4. unless x is already a multiple of 4 +static inline std::size_t RoundUp4(std::size_t x) { + return (x + 3) & ~((std::size_t)0x03); +} + +OutboundPacketStream::OutboundPacketStream(char* buffer, std::size_t capacity) + : data_(buffer), + end_(data_ + capacity), + typeTagsCurrent_(end_), + messageCursor_(data_), + argumentCurrent_(data_), + elementSizePtr_(0), + messageIsInProgress_(false) { + // sanity check integer types declared in OscTypes.h + // you'll need to fix OscTypes.h if any of these asserts fail + assert(sizeof(osc::int32) == 4); + assert(sizeof(osc::uint32) == 4); + assert(sizeof(osc::int64) == 8); + assert(sizeof(osc::uint64) == 8); +} + +OutboundPacketStream::~OutboundPacketStream() { +} + +char* OutboundPacketStream::BeginElement(char* beginPtr) { + if (elementSizePtr_ == 0) { + elementSizePtr_ = reinterpret_cast(data_); + + return beginPtr; + + } else { + // store an offset to the old element size ptr in the element size slot + // we store an offset rather than the actual pointer to be 64 bit clean. + *reinterpret_cast(beginPtr) = + (uint32)(reinterpret_cast(elementSizePtr_) - data_); + + elementSizePtr_ = reinterpret_cast(beginPtr); + + return beginPtr + 4; + } +} + +void OutboundPacketStream::EndElement(char* endPtr) { + assert(elementSizePtr_ != 0); + + if (elementSizePtr_ == reinterpret_cast(data_)) { + elementSizePtr_ = 0; + + } else { + // while building an element, an offset to the containing element's + // size slot is stored in the elements size slot (or a ptr to data_ + // if there is no containing element). We retrieve that here + uint32* previousElementSizePtr = + reinterpret_cast(data_ + *elementSizePtr_); + + // then we store the element size in the slot. note that the element + // size does not include the size slot, hence the - 4 below. + + std::ptrdiff_t d = endPtr - reinterpret_cast(elementSizePtr_); + // assert( d >= 4 && d <= 0x7FFFFFFF ); // assume packets smaller than 2Gb + + uint32 elementSize = static_cast(d - 4); + FromUInt32(reinterpret_cast(elementSizePtr_), elementSize); + + // finally, we reset the element size ptr to the containing element + elementSizePtr_ = previousElementSizePtr; + } +} + +bool OutboundPacketStream::ElementSizeSlotRequired() const { + return (elementSizePtr_ != 0); +} + +void OutboundPacketStream::CheckForAvailableBundleSpace() { + std::size_t required = Size() + ((ElementSizeSlotRequired()) ? 4 : 0) + 16; + + if (required > Capacity()) + throw OutOfBufferMemoryException(); +} + +void OutboundPacketStream::CheckForAvailableMessageSpace(const char* addressPattern) { + // plus 4 for at least four bytes of type tag + std::size_t required = Size() + ((ElementSizeSlotRequired()) ? 4 : 0) + + RoundUp4(std::strlen(addressPattern) + 1) + 4; + + if (required > Capacity()) + throw OutOfBufferMemoryException(); +} + +void OutboundPacketStream::CheckForAvailableArgumentSpace(std::size_t argumentLength) { + // plus three for extra type tag, comma and null terminator + std::size_t required = (argumentCurrent_ - data_) + argumentLength + + RoundUp4((end_ - typeTagsCurrent_) + 3); + + if (required > Capacity()) + throw OutOfBufferMemoryException(); +} + +void OutboundPacketStream::Clear() { + typeTagsCurrent_ = end_; + messageCursor_ = data_; + argumentCurrent_ = data_; + elementSizePtr_ = 0; + messageIsInProgress_ = false; +} + +std::size_t OutboundPacketStream::Capacity() const { + return end_ - data_; +} + +std::size_t OutboundPacketStream::Size() const { + std::size_t result = argumentCurrent_ - data_; + if (IsMessageInProgress()) { + // account for the length of the type tag string. the total type tag + // includes an initial comma, plus at least one terminating \0 + result += RoundUp4((end_ - typeTagsCurrent_) + 2); + } + + return result; +} + +const char* OutboundPacketStream::Data() const { + return data_; +} + +bool OutboundPacketStream::IsReady() const { + return (!IsMessageInProgress() && !IsBundleInProgress()); +} + +bool OutboundPacketStream::IsMessageInProgress() const { + return messageIsInProgress_; +} + +bool OutboundPacketStream::IsBundleInProgress() const { + return (elementSizePtr_ != 0); +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const BundleInitiator& rhs) { + if (IsMessageInProgress()) + throw MessageInProgressException(); + + CheckForAvailableBundleSpace(); + + messageCursor_ = BeginElement(messageCursor_); + + std::memcpy(messageCursor_, "#bundle\0", 8); + FromUInt64(messageCursor_ + 8, rhs.timeTag); + + messageCursor_ += 16; + argumentCurrent_ = messageCursor_; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const BundleTerminator& rhs) { + (void)rhs; + + if (!IsBundleInProgress()) + throw BundleNotInProgressException(); + if (IsMessageInProgress()) + throw MessageInProgressException(); + + EndElement(messageCursor_); + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const BeginMessage& rhs) { + if (IsMessageInProgress()) + throw MessageInProgressException(); + + CheckForAvailableMessageSpace(rhs.addressPattern); + + messageCursor_ = BeginElement(messageCursor_); + + std::strcpy(messageCursor_, rhs.addressPattern); + std::size_t rhsLength = std::strlen(rhs.addressPattern); + messageCursor_ += rhsLength + 1; + + // zero pad to 4-byte boundary + std::size_t i = rhsLength + 1; + while (i & 0x3) { + *messageCursor_++ = '\0'; + ++i; + } + + argumentCurrent_ = messageCursor_; + typeTagsCurrent_ = end_; + + messageIsInProgress_ = true; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const MessageTerminator& rhs) { + (void)rhs; + + if (!IsMessageInProgress()) + throw MessageNotInProgressException(); + + std::size_t typeTagsCount = end_ - typeTagsCurrent_; + + if (typeTagsCount) { + char* tempTypeTags = (char*)alloca(typeTagsCount); + std::memcpy(tempTypeTags, typeTagsCurrent_, typeTagsCount); + + // slot size includes comma and null terminator + std::size_t typeTagSlotSize = RoundUp4(typeTagsCount + 2); + + std::size_t argumentsSize = argumentCurrent_ - messageCursor_; + + std::memmove(messageCursor_ + typeTagSlotSize, messageCursor_, argumentsSize); + + messageCursor_[0] = ','; + // copy type tags in reverse (really forward) order + for (std::size_t i = 0; i < typeTagsCount; ++i) + messageCursor_[i + 1] = tempTypeTags[(typeTagsCount - 1) - i]; + + char* p = messageCursor_ + 1 + typeTagsCount; + for (std::size_t i = 0; i < (typeTagSlotSize - (typeTagsCount + 1)); ++i) + *p++ = '\0'; + + typeTagsCurrent_ = end_; + + // advance messageCursor_ for next message + messageCursor_ += typeTagSlotSize + argumentsSize; + + } else { + // send an empty type tags string + std::memcpy(messageCursor_, ",\0\0\0", 4); + + // advance messageCursor_ for next message + messageCursor_ += 4; + } + + argumentCurrent_ = messageCursor_; + + EndElement(messageCursor_); + + messageIsInProgress_ = false; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(bool rhs) { + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = (char)((rhs) ? TRUE_TYPE_TAG : FALSE_TYPE_TAG); + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const NilType& rhs) { + (void)rhs; + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = NIL_TYPE_TAG; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const InfinitumType& rhs) { + (void)rhs; + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = INFINITUM_TYPE_TAG; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(int32 rhs) { + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = INT32_TYPE_TAG; + FromInt32(argumentCurrent_, rhs); + argumentCurrent_ += 4; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(float rhs) { + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = FLOAT_TYPE_TAG; + +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + float f; + char c[4]; + } u; + + u.f = rhs; + + argumentCurrent_[3] = u.c[0]; + argumentCurrent_[2] = u.c[1]; + argumentCurrent_[1] = u.c[2]; + argumentCurrent_[0] = u.c[3]; +#else + *reinterpret_cast(argumentCurrent_) = rhs; +#endif + + argumentCurrent_ += 4; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(char rhs) { + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = CHAR_TYPE_TAG; + FromInt32(argumentCurrent_, rhs); + argumentCurrent_ += 4; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const RgbaColor& rhs) { + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = RGBA_COLOR_TYPE_TAG; + FromUInt32(argumentCurrent_, rhs); + argumentCurrent_ += 4; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const MidiMessage& rhs) { + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = MIDI_MESSAGE_TYPE_TAG; + FromUInt32(argumentCurrent_, rhs); + argumentCurrent_ += 4; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(int64 rhs) { + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = INT64_TYPE_TAG; + FromInt64(argumentCurrent_, rhs); + argumentCurrent_ += 8; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const TimeTag& rhs) { + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = TIME_TAG_TYPE_TAG; + FromUInt64(argumentCurrent_, rhs); + argumentCurrent_ += 8; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(double rhs) { + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = DOUBLE_TYPE_TAG; + +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + double f; + char c[8]; + } u; + + u.f = rhs; + + argumentCurrent_[7] = u.c[0]; + argumentCurrent_[6] = u.c[1]; + argumentCurrent_[5] = u.c[2]; + argumentCurrent_[4] = u.c[3]; + argumentCurrent_[3] = u.c[4]; + argumentCurrent_[2] = u.c[5]; + argumentCurrent_[1] = u.c[6]; + argumentCurrent_[0] = u.c[7]; +#else + *reinterpret_cast(argumentCurrent_) = rhs; +#endif + + argumentCurrent_ += 8; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const char* rhs) { + CheckForAvailableArgumentSpace(RoundUp4(std::strlen(rhs) + 1)); + + *(--typeTagsCurrent_) = STRING_TYPE_TAG; + std::strcpy(argumentCurrent_, rhs); + std::size_t rhsLength = std::strlen(rhs); + argumentCurrent_ += rhsLength + 1; + + // zero pad to 4-byte boundary + std::size_t i = rhsLength + 1; + while (i & 0x3) { + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const Symbol& rhs) { + CheckForAvailableArgumentSpace(RoundUp4(std::strlen(rhs) + 1)); + + *(--typeTagsCurrent_) = SYMBOL_TYPE_TAG; + std::strcpy(argumentCurrent_, rhs); + std::size_t rhsLength = std::strlen(rhs); + argumentCurrent_ += rhsLength + 1; + + // zero pad to 4-byte boundary + std::size_t i = rhsLength + 1; + while (i & 0x3) { + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const Blob& rhs) { + CheckForAvailableArgumentSpace(4 + RoundUp4(rhs.size)); + + *(--typeTagsCurrent_) = BLOB_TYPE_TAG; + FromUInt32(argumentCurrent_, rhs.size); + argumentCurrent_ += 4; + + std::memcpy(argumentCurrent_, rhs.data, rhs.size); + argumentCurrent_ += rhs.size; + + // zero pad to 4-byte boundary + unsigned long i = rhs.size; + while (i & 0x3) { + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const ArrayInitiator& rhs) { + (void)rhs; + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = ARRAY_BEGIN_TYPE_TAG; + + return *this; +} + +OutboundPacketStream& OutboundPacketStream::operator<<(const ArrayTerminator& rhs) { + (void)rhs; + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = ARRAY_END_TYPE_TAG; + + return *this; +} + +} // namespace osc diff --git a/src/osc/osc/OscOutboundPacketStream.h b/src/osc/osc/OscOutboundPacketStream.h new file mode 100644 index 00000000000..c045e47bf20 --- /dev/null +++ b/src/osc/osc/OscOutboundPacketStream.h @@ -0,0 +1,157 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_OSCOUTBOUNDPACKETSTREAM_H +#define INCLUDED_OSCPACK_OSCOUTBOUNDPACKETSTREAM_H + +#include // size_t + +#include "OscException.h" +#include "OscTypes.h" + +namespace osc { + +class OutOfBufferMemoryException : public Exception { + public: + OutOfBufferMemoryException(const char* w = "out of buffer memory") + : Exception(w) { + } +}; + +class BundleNotInProgressException : public Exception { + public: + BundleNotInProgressException( + const char* w = "call to EndBundle when bundle is not in progress") + : Exception(w) { + } +}; + +class MessageInProgressException : public Exception { + public: + MessageInProgressException( + const char* w = "opening or closing bundle or message while message is in progress") + : Exception(w) { + } +}; + +class MessageNotInProgressException : public Exception { + public: + MessageNotInProgressException( + const char* w = "call to EndMessage when message is not in progress") + : Exception(w) { + } +}; + +class OutboundPacketStream { + public: + OutboundPacketStream(char* buffer, std::size_t capacity); + ~OutboundPacketStream(); + + void Clear(); + + std::size_t Capacity() const; + + // invariant: size() is valid even while building a message. + std::size_t Size() const; + + const char* Data() const; + + // indicates that all messages have been closed with a matching EndMessage + // and all bundles have been closed with a matching EndBundle + bool IsReady() const; + + bool IsMessageInProgress() const; + bool IsBundleInProgress() const; + + OutboundPacketStream& operator<<(const BundleInitiator& rhs); + OutboundPacketStream& operator<<(const BundleTerminator& rhs); + + OutboundPacketStream& operator<<(const BeginMessage& rhs); + OutboundPacketStream& operator<<(const MessageTerminator& rhs); + + OutboundPacketStream& operator<<(bool rhs); + OutboundPacketStream& operator<<(const NilType& rhs); + OutboundPacketStream& operator<<(const InfinitumType& rhs); + OutboundPacketStream& operator<<(int32 rhs); + +#if !(defined(__x86_64__) || defined(_M_X64)) + OutboundPacketStream& operator<<(int rhs) { + *this << (int32)rhs; + return *this; + } +#endif + + OutboundPacketStream& operator<<(float rhs); + OutboundPacketStream& operator<<(char rhs); + OutboundPacketStream& operator<<(const RgbaColor& rhs); + OutboundPacketStream& operator<<(const MidiMessage& rhs); + OutboundPacketStream& operator<<(int64 rhs); + OutboundPacketStream& operator<<(const TimeTag& rhs); + OutboundPacketStream& operator<<(double rhs); + OutboundPacketStream& operator<<(const char* rhs); + OutboundPacketStream& operator<<(const Symbol& rhs); + OutboundPacketStream& operator<<(const Blob& rhs); + + OutboundPacketStream& operator<<(const ArrayInitiator& rhs); + OutboundPacketStream& operator<<(const ArrayTerminator& rhs); + + private: + char* BeginElement(char* beginPtr); + void EndElement(char* endPtr); + + bool ElementSizeSlotRequired() const; + void CheckForAvailableBundleSpace(); + void CheckForAvailableMessageSpace(const char* addressPattern); + void CheckForAvailableArgumentSpace(std::size_t argumentLength); + + char* data_; + char* end_; + + char* typeTagsCurrent_; // stored in reverse order + char* messageCursor_; + char* argumentCurrent_; + + // elementSizePtr_ has two special values: 0 indicates that a bundle + // isn't open, and elementSizePtr_==data_ indicates that a bundle is + // open but that it doesn't have a size slot (ie the outermost bundle) + uint32* elementSizePtr_; + + bool messageIsInProgress_; +}; + +} // namespace osc + +#endif /* INCLUDED_OSCPACK_OSCOUTBOUNDPACKETSTREAM_H */ diff --git a/src/osc/osc/OscPacketListener.h b/src/osc/osc/OscPacketListener.h new file mode 100644 index 00000000000..c3a16f7d4f2 --- /dev/null +++ b/src/osc/osc/OscPacketListener.h @@ -0,0 +1,77 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ + +#ifndef INCLUDED_OSCPACK_OSCPACKETLISTENER_H +#define INCLUDED_OSCPACK_OSCPACKETLISTENER_H + +#include "../ip/PacketListener.h" +#include "OscReceivedElements.h" + +namespace osc { + +class OscPacketListener : public PacketListener { + protected: + virtual void ProcessBundle(const osc::ReceivedBundle& b, + const IpEndpointName& remoteEndpoint) { + // ignore bundle time tag for now + + for (ReceivedBundle::const_iterator i = b.ElementsBegin(); + i != b.ElementsEnd(); + ++i) { + if (i->IsBundle()) + ProcessBundle(ReceivedBundle(*i), remoteEndpoint); + else + ProcessMessage(ReceivedMessage(*i), remoteEndpoint); + } + } + + virtual void ProcessMessage(const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint) = 0; + + public: + virtual void ProcessPacket(const char* data, int size, const IpEndpointName& remoteEndpoint) { + osc::ReceivedPacket p(data, size); + if (p.IsBundle()) + ProcessBundle(ReceivedBundle(p), remoteEndpoint); + else + ProcessMessage(ReceivedMessage(p), remoteEndpoint); + } +}; + +} // namespace osc + +#endif /* INCLUDED_OSCPACK_OSCPACKETLISTENER_H */ diff --git a/src/osc/osc/OscPrintReceivedElements.cpp b/src/osc/osc/OscPrintReceivedElements.cpp new file mode 100644 index 00000000000..63ffce948a2 --- /dev/null +++ b/src/osc/osc/OscPrintReceivedElements.cpp @@ -0,0 +1,245 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#include "OscPrintReceivedElements.h" + +#include +#include +#include +#include + +#if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug +namespace std { +using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'. +} +#endif + +namespace osc { + +std::ostream& operator<<(std::ostream& os, + const ReceivedMessageArgument& arg) { + switch (arg.TypeTag()) { + case TRUE_TYPE_TAG: + os << "bool:true"; + break; + + case FALSE_TYPE_TAG: + os << "bool:false"; + break; + + case NIL_TYPE_TAG: + os << "(Nil)"; + break; + + case INFINITUM_TYPE_TAG: + os << "(Infinitum)"; + break; + + case INT32_TYPE_TAG: + os << "int32:" << arg.AsInt32Unchecked(); + break; + + case FLOAT_TYPE_TAG: + os << "float32:" << arg.AsFloatUnchecked(); + break; + + case CHAR_TYPE_TAG: { + char s[2] = {0}; + s[0] = arg.AsCharUnchecked(); + os << "char:'" << s << "'"; + } break; + + case RGBA_COLOR_TYPE_TAG: { + uint32 color = arg.AsRgbaColorUnchecked(); + + os << "RGBA:0x" + << std::hex << std::setfill('0') + << std::setw(2) << (int)((color >> 24) & 0xFF) + << std::setw(2) << (int)((color >> 16) & 0xFF) + << std::setw(2) << (int)((color >> 8) & 0xFF) + << std::setw(2) << (int)(color & 0xFF) + << std::setfill(' '); + os.unsetf(std::ios::basefield); + } break; + + case MIDI_MESSAGE_TYPE_TAG: { + uint32 m = arg.AsMidiMessageUnchecked(); + os << "midi (port, status, data1, data2):<<" + << std::hex << std::setfill('0') + << "0x" << std::setw(2) << (int)((m >> 24) & 0xFF) + << " 0x" << std::setw(2) << (int)((m >> 16) & 0xFF) + << " 0x" << std::setw(2) << (int)((m >> 8) & 0xFF) + << " 0x" << std::setw(2) << (int)(m & 0xFF) + << std::setfill(' ') << ">>"; + os.unsetf(std::ios::basefield); + } break; + + case INT64_TYPE_TAG: + os << "int64:" << arg.AsInt64Unchecked(); + break; + + case TIME_TAG_TYPE_TAG: { + os << "OSC-timetag:" << arg.AsTimeTagUnchecked() << " "; + + std::time_t t = + (unsigned long)(arg.AsTimeTagUnchecked() >> 32); + + const char* timeString = std::ctime(&t); + size_t len = std::strlen(timeString); + + // -1 to omit trailing newline from string returned by ctime() + if (len > 1) + os.write(timeString, len - 1); + } break; + + case DOUBLE_TYPE_TAG: + os << "double:" << arg.AsDoubleUnchecked(); + break; + + case STRING_TYPE_TAG: + os << "OSC-string:`" << arg.AsStringUnchecked() << "'"; + break; + + case SYMBOL_TYPE_TAG: + os << "OSC-string (symbol):`" << arg.AsSymbolUnchecked() << "'"; + break; + + case BLOB_TYPE_TAG: { + const void* data; + osc_bundle_element_size_t size; + arg.AsBlobUnchecked(data, size); + os << "OSC-blob:<<" << std::hex << std::setfill('0'); + unsigned char* p = (unsigned char*)data; + for (osc_bundle_element_size_t i = 0; i < size; ++i) { + os << "0x" << std::setw(2) << int(p[i]); + if (i != size - 1) + os << ' '; + } + os.unsetf(std::ios::basefield); + os << ">>" << std::setfill(' '); + } break; + + case ARRAY_BEGIN_TYPE_TAG: + os << "["; + break; + + case ARRAY_END_TYPE_TAG: + os << "]"; + break; + + default: + os << "unknown"; + } + + return os; +} + +std::ostream& operator<<(std::ostream& os, const ReceivedMessage& m) { + os << "["; + if (m.AddressPatternIsUInt32()) + os << m.AddressPatternAsUInt32(); + else + os << m.AddressPattern(); + + bool first = true; + for (ReceivedMessage::const_iterator i = m.ArgumentsBegin(); + i != m.ArgumentsEnd(); + ++i) { + if (first) { + os << " "; + first = false; + } else { + os << ", "; + } + + os << *i; + } + + os << "]"; + + return os; +} + +std::ostream& operator<<(std::ostream& os, const ReceivedBundle& b) { + static int indent = 0; + + for (int j = 0; j < indent; ++j) + os << " "; + os << "{ ( "; + if (b.TimeTag() == 1) + os << "immediate"; + else + os << b.TimeTag(); + os << " )\n"; + + ++indent; + + for (ReceivedBundle::const_iterator i = b.ElementsBegin(); + i != b.ElementsEnd(); + ++i) { + if (i->IsBundle()) { + ReceivedBundle b(*i); + os << b << "\n"; + } else { + ReceivedMessage m(*i); + for (int j = 0; j < indent; ++j) + os << " "; + os << m << "\n"; + } + } + + --indent; + + for (int j = 0; j < indent; ++j) + os << " "; + os << "}"; + + return os; +} + +std::ostream& operator<<(std::ostream& os, const ReceivedPacket& p) { + if (p.IsBundle()) { + ReceivedBundle b(p); + os << b << "\n"; + } else { + ReceivedMessage m(p); + os << m << "\n"; + } + + return os; +} + +} // namespace osc diff --git a/src/osc/osc/OscPrintReceivedElements.h b/src/osc/osc/OscPrintReceivedElements.h new file mode 100644 index 00000000000..b2000b59aea --- /dev/null +++ b/src/osc/osc/OscPrintReceivedElements.h @@ -0,0 +1,53 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_OSCPRINTRECEIVEDELEMENTS_H +#define INCLUDED_OSCPACK_OSCPRINTRECEIVEDELEMENTS_H + +#include + +#include "OscReceivedElements.h" + +namespace osc { + +std::ostream& operator<<(std::ostream& os, const ReceivedPacket& p); +std::ostream& operator<<(std::ostream& os, const ReceivedMessageArgument& arg); +std::ostream& operator<<(std::ostream& os, const ReceivedMessage& m); +std::ostream& operator<<(std::ostream& os, const ReceivedBundle& b); + +} // namespace osc + +#endif /* INCLUDED_OSCPACK_OSCPRINTRECEIVEDELEMENTS_H */ diff --git a/src/osc/osc/OscReceivedElements.cpp b/src/osc/osc/OscReceivedElements.cpp new file mode 100644 index 00000000000..40ec2fa1486 --- /dev/null +++ b/src/osc/osc/OscReceivedElements.cpp @@ -0,0 +1,709 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#include "OscReceivedElements.h" + +#include // ptrdiff_t + +#include "OscHostEndianness.h" + +namespace osc { + +// return the first 4 byte boundary after the end of a str4 +// be careful about calling this version if you don't know whether +// the string is terminated correctly. +static inline const char* FindStr4End(const char* p) { + if (p[0] == '\0') // special case for SuperCollider integer address pattern + return p + 4; + + p += 3; + + while (*p) + p += 4; + + return p + 1; +} + +// return the first 4 byte boundary after the end of a str4 +// returns 0 if p == end or if the string is unterminated +static inline const char* FindStr4End(const char* p, const char* end) { + if (p >= end) + return 0; + + if (p[0] == '\0') // special case for SuperCollider integer address pattern + return p + 4; + + p += 3; + end -= 1; + + while (p < end && *p) + p += 4; + + if (*p) + return 0; + else + return p + 1; +} + +// round up to the next highest multiple of 4. unless x is already a multiple of 4 +static inline uint32 RoundUp4(uint32 x) { + return (x + 3) & ~((uint32)0x03); +} + +static inline int32 ToInt32(const char* p) { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::int32 i; + char c[4]; + } u; + + u.c[0] = p[3]; + u.c[1] = p[2]; + u.c[2] = p[1]; + u.c[3] = p[0]; + + return u.i; +#else + return *(int32*)p; +#endif +} + +static inline uint32 ToUInt32(const char* p) { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::uint32 i; + char c[4]; + } u; + + u.c[0] = p[3]; + u.c[1] = p[2]; + u.c[2] = p[1]; + u.c[3] = p[0]; + + return u.i; +#else + return *(uint32*)p; +#endif +} + +static inline int64 ToInt64(const char* p) { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::int64 i; + char c[8]; + } u; + + u.c[0] = p[7]; + u.c[1] = p[6]; + u.c[2] = p[5]; + u.c[3] = p[4]; + u.c[4] = p[3]; + u.c[5] = p[2]; + u.c[6] = p[1]; + u.c[7] = p[0]; + + return u.i; +#else + return *(int64*)p; +#endif +} + +static inline uint64 ToUInt64(const char* p) { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::uint64 i; + char c[8]; + } u; + + u.c[0] = p[7]; + u.c[1] = p[6]; + u.c[2] = p[5]; + u.c[3] = p[4]; + u.c[4] = p[3]; + u.c[5] = p[2]; + u.c[6] = p[1]; + u.c[7] = p[0]; + + return u.i; +#else + return *(uint64*)p; +#endif +} + +//------------------------------------------------------------------------------ + +bool ReceivedPacket::IsBundle() const { + return (Size() > 0 && Contents()[0] == '#'); +} + +//------------------------------------------------------------------------------ + +bool ReceivedBundleElement::IsBundle() const { + return (Size() > 0 && Contents()[0] == '#'); +} + +osc_bundle_element_size_t ReceivedBundleElement::Size() const { + return ToInt32(sizePtr_); +} + +//------------------------------------------------------------------------------ + +bool ReceivedMessageArgument::AsBool() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == TRUE_TYPE_TAG) + return true; + else if (*typeTagPtr_ == FALSE_TYPE_TAG) + return false; + else + throw WrongArgumentTypeException(); +} + +bool ReceivedMessageArgument::AsBoolUnchecked() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == TRUE_TYPE_TAG) + return true; + else + return false; +} + +int32 ReceivedMessageArgument::AsInt32() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == INT32_TYPE_TAG) + return AsInt32Unchecked(); + else + throw WrongArgumentTypeException(); +} + +int32 ReceivedMessageArgument::AsInt32Unchecked() const { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + osc::int32 i; + char c[4]; + } u; + + u.c[0] = argumentPtr_[3]; + u.c[1] = argumentPtr_[2]; + u.c[2] = argumentPtr_[1]; + u.c[3] = argumentPtr_[0]; + + return u.i; +#else + return *(int32*)argument_; +#endif +} + +float ReceivedMessageArgument::AsFloat() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == FLOAT_TYPE_TAG) + return AsFloatUnchecked(); + else + throw WrongArgumentTypeException(); +} + +float ReceivedMessageArgument::AsFloatUnchecked() const { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + float f; + char c[4]; + } u; + + u.c[0] = argumentPtr_[3]; + u.c[1] = argumentPtr_[2]; + u.c[2] = argumentPtr_[1]; + u.c[3] = argumentPtr_[0]; + + return u.f; +#else + return *(float*)argument_; +#endif +} + +char ReceivedMessageArgument::AsChar() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == CHAR_TYPE_TAG) + return AsCharUnchecked(); + else + throw WrongArgumentTypeException(); +} + +char ReceivedMessageArgument::AsCharUnchecked() const { + return (char)ToInt32(argumentPtr_); +} + +uint32 ReceivedMessageArgument::AsRgbaColor() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == RGBA_COLOR_TYPE_TAG) + return AsRgbaColorUnchecked(); + else + throw WrongArgumentTypeException(); +} + +uint32 ReceivedMessageArgument::AsRgbaColorUnchecked() const { + return ToUInt32(argumentPtr_); +} + +uint32 ReceivedMessageArgument::AsMidiMessage() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == MIDI_MESSAGE_TYPE_TAG) + return AsMidiMessageUnchecked(); + else + throw WrongArgumentTypeException(); +} + +uint32 ReceivedMessageArgument::AsMidiMessageUnchecked() const { + return ToUInt32(argumentPtr_); +} + +int64 ReceivedMessageArgument::AsInt64() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == INT64_TYPE_TAG) + return AsInt64Unchecked(); + else + throw WrongArgumentTypeException(); +} + +int64 ReceivedMessageArgument::AsInt64Unchecked() const { + return ToInt64(argumentPtr_); +} + +uint64 ReceivedMessageArgument::AsTimeTag() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == TIME_TAG_TYPE_TAG) + return AsTimeTagUnchecked(); + else + throw WrongArgumentTypeException(); +} + +uint64 ReceivedMessageArgument::AsTimeTagUnchecked() const { + return ToUInt64(argumentPtr_); +} + +double ReceivedMessageArgument::AsDouble() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == DOUBLE_TYPE_TAG) + return AsDoubleUnchecked(); + else + throw WrongArgumentTypeException(); +} + +double ReceivedMessageArgument::AsDoubleUnchecked() const { +#ifdef OSC_HOST_LITTLE_ENDIAN + union { + double d; + char c[8]; + } u; + + u.c[0] = argumentPtr_[7]; + u.c[1] = argumentPtr_[6]; + u.c[2] = argumentPtr_[5]; + u.c[3] = argumentPtr_[4]; + u.c[4] = argumentPtr_[3]; + u.c[5] = argumentPtr_[2]; + u.c[6] = argumentPtr_[1]; + u.c[7] = argumentPtr_[0]; + + return u.d; +#else + return *(double*)argument_; +#endif +} + +const char* ReceivedMessageArgument::AsString() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == STRING_TYPE_TAG) + return argumentPtr_; + else + throw WrongArgumentTypeException(); +} + +const char* ReceivedMessageArgument::AsSymbol() const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == SYMBOL_TYPE_TAG) + return argumentPtr_; + else + throw WrongArgumentTypeException(); +} + +void ReceivedMessageArgument::AsBlob(const void*& data, osc_bundle_element_size_t& size) const { + if (!typeTagPtr_) + throw MissingArgumentException(); + else if (*typeTagPtr_ == BLOB_TYPE_TAG) + AsBlobUnchecked(data, size); + else + throw WrongArgumentTypeException(); +} + +void ReceivedMessageArgument::AsBlobUnchecked( + const void*& data, osc_bundle_element_size_t& size) const { + // read blob size as an unsigned int then validate + osc_bundle_element_size_t sizeResult = (osc_bundle_element_size_t)ToUInt32(argumentPtr_); + if (!IsValidElementSizeValue(sizeResult)) + throw MalformedMessageException("invalid blob size"); + + size = sizeResult; + data = (void*)(argumentPtr_ + osc::OSC_SIZEOF_INT32); +} + +std::size_t ReceivedMessageArgument::ComputeArrayItemCount() const { + // it is only valid to call ComputeArrayItemCount when the argument is the array start marker + if (!IsArrayBegin()) + throw WrongArgumentTypeException(); + + std::size_t result = 0; + unsigned int level = 0; + const char* typeTag = typeTagPtr_ + 1; + + // iterate through all type tags. note that ReceivedMessage::Init + // has already checked that the message is well formed. + while (*typeTag) { + switch (*typeTag++) { + case ARRAY_BEGIN_TYPE_TAG: + level += 1; + break; + + case ARRAY_END_TYPE_TAG: + if (level == 0) + return result; + level -= 1; + break; + + default: + if (level == 0) // only count items at level 0 + ++result; + } + } + + return result; +} + +//------------------------------------------------------------------------------ + +void ReceivedMessageArgumentIterator::Advance() { + if (!value_.typeTagPtr_) + return; + + switch (*value_.typeTagPtr_++) { + case '\0': + // don't advance past end + --value_.typeTagPtr_; + break; + + case TRUE_TYPE_TAG: + case FALSE_TYPE_TAG: + case NIL_TYPE_TAG: + case INFINITUM_TYPE_TAG: + + // zero length + break; + + case INT32_TYPE_TAG: + case FLOAT_TYPE_TAG: + case CHAR_TYPE_TAG: + case RGBA_COLOR_TYPE_TAG: + case MIDI_MESSAGE_TYPE_TAG: + + value_.argumentPtr_ += 4; + break; + + case INT64_TYPE_TAG: + case TIME_TAG_TYPE_TAG: + case DOUBLE_TYPE_TAG: + + value_.argumentPtr_ += 8; + break; + + case STRING_TYPE_TAG: + case SYMBOL_TYPE_TAG: + + // we use the unsafe function FindStr4End(char*) here because all of + // the arguments have already been validated in + // ReceivedMessage::Init() below. + + value_.argumentPtr_ = FindStr4End(value_.argumentPtr_); + break; + + case BLOB_TYPE_TAG: { + // treat blob size as an unsigned int for the purposes of this calculation + uint32 blobSize = ToUInt32(value_.argumentPtr_); + value_.argumentPtr_ = value_.argumentPtr_ + osc::OSC_SIZEOF_INT32 + RoundUp4(blobSize); + } break; + + case ARRAY_BEGIN_TYPE_TAG: + case ARRAY_END_TYPE_TAG: + + // [ Indicates the beginning of an array. The tags following are for + // data in the Array until a close brace tag is reached. + // ] Indicates the end of an array. + + // zero length, don't advance argument ptr + break; + + default: // unknown type tag + // don't advance + --value_.typeTagPtr_; + break; + } +} + +//------------------------------------------------------------------------------ + +ReceivedMessage::ReceivedMessage(const ReceivedPacket& packet) + : addressPattern_(packet.Contents()) { + Init(packet.Contents(), packet.Size()); +} + +ReceivedMessage::ReceivedMessage(const ReceivedBundleElement& bundleElement) + : addressPattern_(bundleElement.Contents()) { + Init(bundleElement.Contents(), bundleElement.Size()); +} + +bool ReceivedMessage::AddressPatternIsUInt32() const { + return (addressPattern_[0] == '\0'); +} + +uint32 ReceivedMessage::AddressPatternAsUInt32() const { + return ToUInt32(addressPattern_); +} + +void ReceivedMessage::Init(const char* message, osc_bundle_element_size_t size) { + if (!IsValidElementSizeValue(size)) + throw MalformedMessageException("invalid message size"); + + if (size == 0) + throw MalformedMessageException("zero length messages not permitted"); + + if (!IsMultipleOf4(size)) + throw MalformedMessageException("message size must be multiple of four"); + + const char* end = message + size; + + typeTagsBegin_ = FindStr4End(addressPattern_, end); + if (typeTagsBegin_ == 0) { + // address pattern was not terminated before end + throw MalformedMessageException("unterminated address pattern"); + } + + if (typeTagsBegin_ == end) { + // message consists of only the address pattern - no arguments or type tags. + typeTagsBegin_ = 0; + typeTagsEnd_ = 0; + arguments_ = 0; + + } else { + if (*typeTagsBegin_ != ',') + throw MalformedMessageException("type tags not present"); + + if (*(typeTagsBegin_ + 1) == '\0') { + // zero length type tags + typeTagsBegin_ = 0; + typeTagsEnd_ = 0; + arguments_ = 0; + + } else { + // check that all arguments are present and well formed + + arguments_ = FindStr4End(typeTagsBegin_, end); + if (arguments_ == 0) { + throw MalformedMessageException( + "type tags were not terminated before end of message"); + } + + ++typeTagsBegin_; // advance past initial ',' + + const char* typeTag = typeTagsBegin_; + const char* argument = arguments_; + unsigned int arrayLevel = 0; + + do { + switch (*typeTag) { + case TRUE_TYPE_TAG: + case FALSE_TYPE_TAG: + case NIL_TYPE_TAG: + case INFINITUM_TYPE_TAG: + // zero length + break; + + // [ Indicates the beginning of an array. The tags following are for + // data in the Array until a close brace tag is reached. + // ] Indicates the end of an array. + case ARRAY_BEGIN_TYPE_TAG: + ++arrayLevel; + // (zero length argument data) + break; + + case ARRAY_END_TYPE_TAG: + --arrayLevel; + // (zero length argument data) + break; + + case INT32_TYPE_TAG: + case FLOAT_TYPE_TAG: + case CHAR_TYPE_TAG: + case RGBA_COLOR_TYPE_TAG: + case MIDI_MESSAGE_TYPE_TAG: + + if (argument == end) + throw MalformedMessageException("arguments exceed message size"); + argument += 4; + if (argument > end) + throw MalformedMessageException("arguments exceed message size"); + break; + + case INT64_TYPE_TAG: + case TIME_TAG_TYPE_TAG: + case DOUBLE_TYPE_TAG: + + if (argument == end) + throw MalformedMessageException("arguments exceed message size"); + argument += 8; + if (argument > end) + throw MalformedMessageException("arguments exceed message size"); + break; + + case STRING_TYPE_TAG: + case SYMBOL_TYPE_TAG: + + if (argument == end) + throw MalformedMessageException("arguments exceed message size"); + argument = FindStr4End(argument, end); + if (argument == 0) + throw MalformedMessageException("unterminated string argument"); + break; + + case BLOB_TYPE_TAG: { + if (argument + osc::OSC_SIZEOF_INT32 > end) + MalformedMessageException("arguments exceed message size"); + + // treat blob size as an unsigned int for the purposes of this calculation + uint32 blobSize = ToUInt32(argument); + argument = argument + osc::OSC_SIZEOF_INT32 + RoundUp4(blobSize); + if (argument > end) + MalformedMessageException("arguments exceed message size"); + } break; + + default: + throw MalformedMessageException("unknown type tag"); + } + + } while (*++typeTag != '\0'); + typeTagsEnd_ = typeTag; + + if (arrayLevel != 0) + throw MalformedMessageException( + "array was not terminated before end of message " + "(expected ']' end of array tag)"); + } + + // These invariants should be guaranteed by the above code. + // we depend on them in the implementation of ArgumentCount() +#ifndef NDEBUG + std::ptrdiff_t argumentCount = typeTagsEnd_ - typeTagsBegin_; + assert(argumentCount >= 0); + assert(argumentCount <= OSC_INT32_MAX); +#endif + } +} + +//------------------------------------------------------------------------------ + +ReceivedBundle::ReceivedBundle(const ReceivedPacket& packet) + : elementCount_(0) { + Init(packet.Contents(), packet.Size()); +} + +ReceivedBundle::ReceivedBundle(const ReceivedBundleElement& bundleElement) + : elementCount_(0) { + Init(bundleElement.Contents(), bundleElement.Size()); +} + +void ReceivedBundle::Init(const char* bundle, osc_bundle_element_size_t size) { + if (!IsValidElementSizeValue(size)) + throw MalformedBundleException("invalid bundle size"); + + if (size < 16) + throw MalformedBundleException("packet too short for bundle"); + + if (!IsMultipleOf4(size)) + throw MalformedBundleException("bundle size must be multiple of four"); + + if (bundle[0] != '#' || bundle[1] != 'b' || bundle[2] != 'u' || + bundle[3] != 'n' || bundle[4] != 'd' || bundle[5] != 'l' || + bundle[6] != 'e' || bundle[7] != '\0') + throw MalformedBundleException("bad bundle address pattern"); + + end_ = bundle + size; + + timeTag_ = bundle + 8; + + const char* p = timeTag_ + 8; + + while (p < end_) { + if (p + osc::OSC_SIZEOF_INT32 > end_) + throw MalformedBundleException("packet too short for elementSize"); + + // treat element size as an unsigned int for the purposes of this calculation + uint32 elementSize = ToUInt32(p); + if ((elementSize & ((uint32)0x03)) != 0) + throw MalformedBundleException("bundle element size must be multiple of four"); + + p += osc::OSC_SIZEOF_INT32 + elementSize; + if (p > end_) + throw MalformedBundleException("packet too short for bundle element"); + + ++elementCount_; + } + + if (p != end_) + throw MalformedBundleException("bundle contents "); +} + +uint64 ReceivedBundle::TimeTag() const { + return ToUInt64(timeTag_); +} + +} // namespace osc diff --git a/src/osc/osc/OscReceivedElements.h b/src/osc/osc/OscReceivedElements.h new file mode 100644 index 00000000000..dde8d76e6d1 --- /dev/null +++ b/src/osc/osc/OscReceivedElements.h @@ -0,0 +1,588 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_OSCRECEIVEDELEMENTS_H +#define INCLUDED_OSCPACK_OSCRECEIVEDELEMENTS_H + +#include +#include +#include // size_t + +#include "OscException.h" +#include "OscTypes.h" + +namespace osc { + +class MalformedPacketException : public Exception { + public: + MalformedPacketException(const char* w = "malformed packet") + : Exception(w) { + } +}; + +class MalformedMessageException : public Exception { + public: + MalformedMessageException(const char* w = "malformed message") + : Exception(w) { + } +}; + +class MalformedBundleException : public Exception { + public: + MalformedBundleException(const char* w = "malformed bundle") + : Exception(w) { + } +}; + +class WrongArgumentTypeException : public Exception { + public: + WrongArgumentTypeException(const char* w = "wrong argument type") + : Exception(w) { + } +}; + +class MissingArgumentException : public Exception { + public: + MissingArgumentException(const char* w = "missing argument") + : Exception(w) { + } +}; + +class ExcessArgumentException : public Exception { + public: + ExcessArgumentException(const char* w = "too many arguments") + : Exception(w) { + } +}; + +class ReceivedPacket { + public: + // Although the OSC spec is not entirely clear on this, we only support + // packets up to 0x7FFFFFFC bytes long (the maximum 4-byte aligned value + // representable by an int32). An exception will be raised if you pass a + // larger value to the ReceivedPacket() constructor. + + ReceivedPacket(const char* contents, osc_bundle_element_size_t size) + : contents_(contents), + size_(ValidateSize(size)) { + } + + ReceivedPacket(const char* contents, std::size_t size) + : contents_(contents), + size_(ValidateSize((osc_bundle_element_size_t)size)) { + } + +#if !(defined(__x86_64__) || defined(_M_X64)) + ReceivedPacket(const char* contents, int size) + : contents_(contents), + size_(ValidateSize((osc_bundle_element_size_t)size)) { + } +#endif + + bool IsMessage() const { + return !IsBundle(); + } + bool IsBundle() const; + + osc_bundle_element_size_t Size() const { + return size_; + } + const char* Contents() const { + return contents_; + } + + private: + const char* contents_; + osc_bundle_element_size_t size_; + + static osc_bundle_element_size_t ValidateSize(osc_bundle_element_size_t size) { + // sanity check integer types declared in OscTypes.h + // you'll need to fix OscTypes.h if any of these asserts fail + assert(sizeof(osc::int32) == 4); + assert(sizeof(osc::uint32) == 4); + assert(sizeof(osc::int64) == 8); + assert(sizeof(osc::uint64) == 8); + + if (!IsValidElementSizeValue(size)) + throw MalformedPacketException("invalid packet size"); + + if (size == 0) + throw MalformedPacketException("zero length elements not permitted"); + + if (!IsMultipleOf4(size)) + throw MalformedPacketException("element size must be multiple of four"); + + return size; + } +}; + +class ReceivedBundleElement { + public: + ReceivedBundleElement(const char* sizePtr) + : sizePtr_(sizePtr) { + } + + friend class ReceivedBundleElementIterator; + + bool IsMessage() const { + return !IsBundle(); + } + bool IsBundle() const; + + osc_bundle_element_size_t Size() const; + const char* Contents() const { + return sizePtr_ + osc::OSC_SIZEOF_INT32; + } + + private: + const char* sizePtr_; +}; + +class ReceivedBundleElementIterator { + public: + ReceivedBundleElementIterator(const char* sizePtr) + : value_(sizePtr) { + } + + ReceivedBundleElementIterator operator++() { + Advance(); + return *this; + } + + ReceivedBundleElementIterator operator++(int) { + ReceivedBundleElementIterator old(*this); + Advance(); + return old; + } + + const ReceivedBundleElement& operator*() const { + return value_; + } + + const ReceivedBundleElement* operator->() const { + return &value_; + } + + friend bool operator==(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs); + + private: + ReceivedBundleElement value_; + + void Advance() { + value_.sizePtr_ = value_.Contents() + value_.Size(); + } + + bool IsEqualTo(const ReceivedBundleElementIterator& rhs) const { + return value_.sizePtr_ == rhs.value_.sizePtr_; + } +}; + +inline bool operator==(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs) { + return lhs.IsEqualTo(rhs); +} + +inline bool operator!=(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs) { + return !(lhs == rhs); +} + +class ReceivedMessageArgument { + public: + ReceivedMessageArgument(const char* typeTagPtr, const char* argumentPtr) + : typeTagPtr_(typeTagPtr), + argumentPtr_(argumentPtr) { + } + + friend class ReceivedMessageArgumentIterator; + + char TypeTag() const { + return *typeTagPtr_; + } + + // the unchecked methods below don't check whether the argument actually + // is of the specified type. they should only be used if you've already + // checked the type tag or the associated IsType() method. + + bool IsBool() const { + return *typeTagPtr_ == TRUE_TYPE_TAG || *typeTagPtr_ == FALSE_TYPE_TAG; + } + bool AsBool() const; + bool AsBoolUnchecked() const; + + bool IsNil() const { + return *typeTagPtr_ == NIL_TYPE_TAG; + } + bool IsInfinitum() const { + return *typeTagPtr_ == INFINITUM_TYPE_TAG; + } + + bool IsInt32() const { + return *typeTagPtr_ == INT32_TYPE_TAG; + } + int32 AsInt32() const; + int32 AsInt32Unchecked() const; + + bool IsFloat() const { + return *typeTagPtr_ == FLOAT_TYPE_TAG; + } + float AsFloat() const; + float AsFloatUnchecked() const; + + bool IsChar() const { + return *typeTagPtr_ == CHAR_TYPE_TAG; + } + char AsChar() const; + char AsCharUnchecked() const; + + bool IsRgbaColor() const { + return *typeTagPtr_ == RGBA_COLOR_TYPE_TAG; + } + uint32 AsRgbaColor() const; + uint32 AsRgbaColorUnchecked() const; + + bool IsMidiMessage() const { + return *typeTagPtr_ == MIDI_MESSAGE_TYPE_TAG; + } + uint32 AsMidiMessage() const; + uint32 AsMidiMessageUnchecked() const; + + bool IsInt64() const { + return *typeTagPtr_ == INT64_TYPE_TAG; + } + int64 AsInt64() const; + int64 AsInt64Unchecked() const; + + bool IsTimeTag() const { + return *typeTagPtr_ == TIME_TAG_TYPE_TAG; + } + uint64 AsTimeTag() const; + uint64 AsTimeTagUnchecked() const; + + bool IsDouble() const { + return *typeTagPtr_ == DOUBLE_TYPE_TAG; + } + double AsDouble() const; + double AsDoubleUnchecked() const; + + bool IsString() const { + return *typeTagPtr_ == STRING_TYPE_TAG; + } + const char* AsString() const; + const char* AsStringUnchecked() const { + return argumentPtr_; + } + + bool IsSymbol() const { + return *typeTagPtr_ == SYMBOL_TYPE_TAG; + } + const char* AsSymbol() const; + const char* AsSymbolUnchecked() const { + return argumentPtr_; + } + + bool IsBlob() const { + return *typeTagPtr_ == BLOB_TYPE_TAG; + } + void AsBlob(const void*& data, osc_bundle_element_size_t& size) const; + void AsBlobUnchecked(const void*& data, osc_bundle_element_size_t& size) const; + + bool IsArrayBegin() const { + return *typeTagPtr_ == ARRAY_BEGIN_TYPE_TAG; + } + bool IsArrayEnd() const { + return *typeTagPtr_ == ARRAY_END_TYPE_TAG; + } + // Calculate the number of top-level items in the array. Nested arrays count as one item. + // Only valid at array start. Will throw an exception if IsArrayStart() == false. + std::size_t ComputeArrayItemCount() const; + + private: + const char* typeTagPtr_; + const char* argumentPtr_; +}; + +class ReceivedMessageArgumentIterator { + public: + ReceivedMessageArgumentIterator(const char* typeTags, const char* arguments) + : value_(typeTags, arguments) { + } + + ReceivedMessageArgumentIterator operator++() { + Advance(); + return *this; + } + + ReceivedMessageArgumentIterator operator++(int) { + ReceivedMessageArgumentIterator old(*this); + Advance(); + return old; + } + + const ReceivedMessageArgument& operator*() const { + return value_; + } + + const ReceivedMessageArgument* operator->() const { + return &value_; + } + + friend bool operator==(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs); + + private: + ReceivedMessageArgument value_; + + void Advance(); + + bool IsEqualTo(const ReceivedMessageArgumentIterator& rhs) const { + return value_.typeTagPtr_ == rhs.value_.typeTagPtr_; + } +}; + +inline bool operator==(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs) { + return lhs.IsEqualTo(rhs); +} + +inline bool operator!=(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs) { + return !(lhs == rhs); +} + +class ReceivedMessageArgumentStream { + friend class ReceivedMessage; + ReceivedMessageArgumentStream(const ReceivedMessageArgumentIterator& begin, + const ReceivedMessageArgumentIterator& end) + : p_(begin), + end_(end) { + } + + ReceivedMessageArgumentIterator p_, end_; + + public: + // end of stream + bool Eos() const { + return p_ == end_; + } + + ReceivedMessageArgumentStream& operator>>(bool& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs = (*p_++).AsBool(); + return *this; + } + + // not sure if it would be useful to stream Nil and Infinitum + // for now it's not possible + // same goes for array boundaries + + ReceivedMessageArgumentStream& operator>>(int32& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs = (*p_++).AsInt32(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(float& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs = (*p_++).AsFloat(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(char& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs = (*p_++).AsChar(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(RgbaColor& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsRgbaColor(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(MidiMessage& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsMidiMessage(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(int64& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs = (*p_++).AsInt64(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(TimeTag& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsTimeTag(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(double& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs = (*p_++).AsDouble(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(Blob& rhs) { + if (Eos()) + throw MissingArgumentException(); + + (*p_++).AsBlob(rhs.data, rhs.size); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(const char*& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs = (*p_++).AsString(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(Symbol& rhs) { + if (Eos()) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsSymbol(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>(MessageTerminator& rhs) { + (void)rhs; // suppress unused parameter warning + + if (!Eos()) + throw ExcessArgumentException(); + + return *this; + } +}; + +class ReceivedMessage { + void Init(const char* bundle, osc_bundle_element_size_t size); + + public: + explicit ReceivedMessage(const ReceivedPacket& packet); + explicit ReceivedMessage(const ReceivedBundleElement& bundleElement); + + const char* AddressPattern() const { + return addressPattern_; + } + + // Support for non-standard SuperCollider integer address patterns: + bool AddressPatternIsUInt32() const; + uint32 AddressPatternAsUInt32() const; + + uint32 ArgumentCount() const { + return static_cast(typeTagsEnd_ - typeTagsBegin_); + } + + const char* TypeTags() const { + return typeTagsBegin_; + } + + typedef ReceivedMessageArgumentIterator const_iterator; + + ReceivedMessageArgumentIterator ArgumentsBegin() const { + return ReceivedMessageArgumentIterator(typeTagsBegin_, arguments_); + } + + ReceivedMessageArgumentIterator ArgumentsEnd() const { + return ReceivedMessageArgumentIterator(typeTagsEnd_, 0); + } + + ReceivedMessageArgumentStream ArgumentStream() const { + return ReceivedMessageArgumentStream(ArgumentsBegin(), ArgumentsEnd()); + } + + private: + const char* addressPattern_; + const char* typeTagsBegin_; + const char* typeTagsEnd_; + const char* arguments_; +}; + +class ReceivedBundle { + void Init(const char* message, osc_bundle_element_size_t size); + + public: + explicit ReceivedBundle(const ReceivedPacket& packet); + explicit ReceivedBundle(const ReceivedBundleElement& bundleElement); + + uint64 TimeTag() const; + + uint32 ElementCount() const { + return elementCount_; + } + + typedef ReceivedBundleElementIterator const_iterator; + + ReceivedBundleElementIterator ElementsBegin() const { + return ReceivedBundleElementIterator(timeTag_ + 8); + } + + ReceivedBundleElementIterator ElementsEnd() const { + return ReceivedBundleElementIterator(end_); + } + + private: + const char* timeTag_; + const char* end_; + uint32 elementCount_; +}; + +} // namespace osc + +#endif /* INCLUDED_OSCPACK_OSCRECEIVEDELEMENTS_H */ diff --git a/src/osc/osc/OscTypes.cpp b/src/osc/osc/OscTypes.cpp new file mode 100644 index 00000000000..f29ae1d1715 --- /dev/null +++ b/src/osc/osc/OscTypes.cpp @@ -0,0 +1,52 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#include "OscTypes.h" + +namespace osc { + +BundleInitiator BeginBundleImmediate(1); +BundleTerminator EndBundle; +MessageTerminator EndMessage; +NilType OscNil; +#ifndef _OBJC_OBJC_H_ +NilType Nil; // Objective-C defines Nil. so our Nil is deprecated. use OscNil instead +#endif +InfinitumType Infinitum; +ArrayInitiator BeginArray; +ArrayTerminator EndArray; + +} // namespace osc diff --git a/src/osc/osc/OscTypes.h b/src/osc/osc/OscTypes.h new file mode 100644 index 00000000000..9a4244e9b8b --- /dev/null +++ b/src/osc/osc/OscTypes.h @@ -0,0 +1,247 @@ +/* + oscpack -- Open Sound Control (OSC) packet manipulation library + http://www.rossbencina.com/code/oscpack + + Copyright (c) 2004-2013 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. +*/ +#ifndef INCLUDED_OSCPACK_OSCTYPES_H +#define INCLUDED_OSCPACK_OSCTYPES_H + +namespace osc { + +// basic types + +#if defined(__BORLANDC__) || defined(_MSC_VER) + +typedef __int64 int64; +typedef unsigned __int64 uint64; + +#elif defined(__x86_64__) || defined(_M_X64) + +typedef long int64; +typedef unsigned long uint64; + +#else + +typedef long long int64; +typedef unsigned long long uint64; + +#endif + +#if defined(__x86_64__) || defined(_M_X64) + +typedef signed int int32; +typedef unsigned int uint32; + +#else + +typedef signed long int32; +typedef unsigned long uint32; + +#endif + +enum ValueTypeSizes { + OSC_SIZEOF_INT32 = 4, + OSC_SIZEOF_UINT32 = 4, + OSC_SIZEOF_INT64 = 8, + OSC_SIZEOF_UINT64 = 8, +}; + +// osc_bundle_element_size_t is used for the size of bundle elements and blobs +// the OSC spec specifies these as int32 (signed) but we ensure that they +// are always positive since negative field sizes make no sense. + +typedef int32 osc_bundle_element_size_t; + +enum { + OSC_INT32_MAX = 0x7FFFFFFF, + + // Element sizes are specified to be int32, and are always rounded up to nearest + // multiple of 4. Therefore their values can't be greater than 0x7FFFFFFC. + OSC_BUNDLE_ELEMENT_SIZE_MAX = 0x7FFFFFFC +}; + +inline bool IsValidElementSizeValue(osc_bundle_element_size_t x) { + // sizes may not be negative or exceed OSC_BUNDLE_ELEMENT_SIZE_MAX + return x >= 0 && x <= OSC_BUNDLE_ELEMENT_SIZE_MAX; +} + +inline bool IsMultipleOf4(osc_bundle_element_size_t x) { + return (x & ((osc_bundle_element_size_t)0x03)) == 0; +} + +enum TypeTagValues { + TRUE_TYPE_TAG = 'T', + FALSE_TYPE_TAG = 'F', + NIL_TYPE_TAG = 'N', + INFINITUM_TYPE_TAG = 'I', + INT32_TYPE_TAG = 'i', + FLOAT_TYPE_TAG = 'f', + CHAR_TYPE_TAG = 'c', + RGBA_COLOR_TYPE_TAG = 'r', + MIDI_MESSAGE_TYPE_TAG = 'm', + INT64_TYPE_TAG = 'h', + TIME_TAG_TYPE_TAG = 't', + DOUBLE_TYPE_TAG = 'd', + STRING_TYPE_TAG = 's', + SYMBOL_TYPE_TAG = 'S', + BLOB_TYPE_TAG = 'b', + ARRAY_BEGIN_TYPE_TAG = '[', + ARRAY_END_TYPE_TAG = ']' +}; + +// i/o manipulators used for streaming interfaces + +struct BundleInitiator { + explicit BundleInitiator(uint64 timeTag_) + : timeTag(timeTag_) { + } + uint64 timeTag; +}; + +extern BundleInitiator BeginBundleImmediate; + +inline BundleInitiator BeginBundle(uint64 timeTag = 1) { + return BundleInitiator(timeTag); +} + +struct BundleTerminator { +}; + +extern BundleTerminator EndBundle; + +struct BeginMessage { + explicit BeginMessage(const char* addressPattern_) + : addressPattern(addressPattern_) { + } + const char* addressPattern; +}; + +struct MessageTerminator { +}; + +extern MessageTerminator EndMessage; + +// osc specific types. they are defined as structs so they can be used +// as separately identifiable types with the streaming operators. + +struct NilType { +}; + +extern NilType OscNil; + +#ifndef _OBJC_OBJC_H_ +extern NilType Nil; // Objective-C defines Nil. so our Nil is deprecated. use OscNil instead +#endif + +struct InfinitumType { +}; + +extern InfinitumType Infinitum; + +struct RgbaColor { + RgbaColor() { + } + explicit RgbaColor(uint32 value_) + : value(value_) { + } + uint32 value; + + operator uint32() const { + return value; + } +}; + +struct MidiMessage { + MidiMessage() { + } + explicit MidiMessage(uint32 value_) + : value(value_) { + } + uint32 value; + + operator uint32() const { + return value; + } +}; + +struct TimeTag { + TimeTag() { + } + explicit TimeTag(uint64 value_) + : value(value_) { + } + uint64 value; + + operator uint64() const { + return value; + } +}; + +struct Symbol { + Symbol() { + } + explicit Symbol(const char* value_) + : value(value_) { + } + const char* value; + + operator const char*() const { + return value; + } +}; + +struct Blob { + Blob() { + } + explicit Blob(const void* data_, osc_bundle_element_size_t size_) + : data(data_), + size(size_) { + } + const void* data; + osc_bundle_element_size_t size; +}; + +struct ArrayInitiator { +}; + +extern ArrayInitiator BeginArray; + +struct ArrayTerminator { +}; + +extern ArrayTerminator EndArray; + +} // namespace osc + +#endif /* INCLUDED_OSCPACK_OSCTYPES_H */ diff --git a/src/osc/oscfunctions.h b/src/osc/oscfunctions.h new file mode 100644 index 00000000000..2180821f131 --- /dev/null +++ b/src/osc/oscfunctions.h @@ -0,0 +1,220 @@ +#ifndef OSCFUNCTIONS_H +#define OSCFUNCTIONS_H + +constexpr int OUTPUT_BUFFER_SIZE = 1024; +constexpr int IP_MTU_SIZE = 1536; + +#include +#include +#include +#include +#include + +#include "osc/ip/UdpSocket.h" +#include "osc/osc/OscOutboundPacketStream.h" +#include "osc/osc/OscPacketListener.h" +#include "osc/osc/OscReceivedElements.h" + +enum class DefOscBodyType { + STRINGBODY, + INTBODY, + DOUBLEBODY, + FLOATBODY +}; + +void sendOscMessage(const char* receiverIp, + int port, + osc::OutboundPacketStream& p, + const QString& header, + const QString& statusTxtBody) { + if (receiverIp) { + UdpTransmitSocket transmitSocket(IpEndpointName(receiverIp, port)); + transmitSocket.Send(p.Data(), p.Size()); + qDebug() << QString("OSC Msg Send to Receiver (%1:%2) : <%3 : %4>") + .arg(receiverIp) + .arg(port) + .arg(header, statusTxtBody); + } +} + +void OscFunctionsSendPtrType(UserSettingsPointer m_pConfig, + const QString& OscGroup, + const QString& OscKey, + enum DefOscBodyType OscBodyType, + const QString& OscMessageBodyQString, + int OscMessageBodyInt, + double OscMessageBodyDouble, + float OscMessageBodyFloat) { + QString OscMessageHeader = "/" + OscGroup + "@" + OscKey; + OscMessageHeader.replace("[", "("); + OscMessageHeader.replace("]", ")"); + + QByteArray OscMessageHeaderBa = OscMessageHeader.toLocal8Bit(); + const char* OscMessageHeaderChar = OscMessageHeaderBa.data(); + + if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) { + char buffer[IP_MTU_SIZE]; + osc::OutboundPacketStream p(buffer, IP_MTU_SIZE); + QString OscStatusTxtBody; + + // Creation of package + p.Clear(); + p << osc::BeginBundle(); + switch (OscBodyType) { + case DefOscBodyType::STRINGBODY: + p << osc::BeginMessage(OscMessageHeaderChar) + << OscMessageBodyQString.toLocal8Bit().data() << osc::EndMessage; + OscStatusTxtBody = OscMessageBodyQString; + break; + case DefOscBodyType::INTBODY: + p << osc::BeginMessage(OscMessageHeaderChar) << OscMessageBodyInt << osc::EndMessage; + OscStatusTxtBody = QString::number(OscMessageBodyInt); + break; + case DefOscBodyType::DOUBLEBODY: + p << osc::BeginMessage(OscMessageHeaderChar) << OscMessageBodyDouble << osc::EndMessage; + OscStatusTxtBody = QString::number(OscMessageBodyDouble); + break; + case DefOscBodyType::FLOATBODY: + p << osc::BeginMessage(OscMessageHeaderChar) << OscMessageBodyFloat << osc::EndMessage; + OscStatusTxtBody = QString::number(OscMessageBodyFloat); + break; + } + p << osc::EndBundle; + + // Retrieve output port + int CKOscPortOutInt = m_pConfig->getValue(ConfigKey("[OSC]", "OscPortOut")).toInt(); + + // List of similar parts of receiver + const QList> receivers = { + {"[OSC]", "OscReceiver1"}, + {"[OSC]", "OscReceiver2"}, + {"[OSC]", "OscReceiver3"}, + {"[OSC]", "OscReceiver4"}, + {"[OSC]", "OscReceiver5"}}; + + // Send to active receivers + for (const auto& receiver : receivers) { + if (m_pConfig->getValue(ConfigKey(receiver.first, receiver.second + "Active"))) { + QByteArray receiverIpBa = + m_pConfig + ->getValue(ConfigKey( + receiver.first, receiver.second + "Ip")) + .toLocal8Bit(); + sendOscMessage(receiverIpBa.data(), + CKOscPortOutInt, + p, + OscMessageHeader, + OscStatusTxtBody); + } + } + } else { + qDebug() << "OSC NOT Enabled"; + } +} + +void OscNoTrackLoadedInGroup(UserSettingsPointer m_pConfig, const QString& OscGroup) { + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "TrackArtist", + DefOscBodyType::STRINGBODY, + "no track loaded", + 0, + 0, + 0); + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "TrackTitle", + DefOscBodyType::STRINGBODY, + "no track loaded", + 0, + 0, + 0); + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "duration", + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + 0); + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "track_loaded", + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + 0); + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "playposition", + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + 0); +} + +void OscTrackLoadedInGroup(UserSettingsPointer m_pConfig, + const QString& OscGroup, + const QString& TrackArtist, + const QString& TrackTitle, + float track_loaded, + float duration, + float playposition) { + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "TrackArtist", + DefOscBodyType::STRINGBODY, + TrackArtist, + 0, + 0, + 0); + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "TrackTitle", + DefOscBodyType::STRINGBODY, + TrackTitle, + 0, + 0, + 0); + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "track_loaded", + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + track_loaded); + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "duration", + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + duration); + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "playposition", + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + playposition); +} + +void OscChangedPlayState(UserSettingsPointer m_pConfig, + const QString& OscGroup, + float playstate) { + OscFunctionsSendPtrType(m_pConfig, + OscGroup, + "play", + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + playstate); +} + +#endif /* OSCFUNCTIONS_H */ diff --git a/src/osc/oscreceiver.cpp b/src/osc/oscreceiver.cpp new file mode 100644 index 00000000000..132631ed38e --- /dev/null +++ b/src/osc/oscreceiver.cpp @@ -0,0 +1,210 @@ + +#include "oscreceiver.h" + +#include +#include +#include +#include +#include + +#include "control/controlobject.h" +#include "control/controlproxy.h" +#include "control/pollingcontrolproxy.h" +#include "osc/ip/UdpSocket.h" +#include "osc/osc/OscPacketListener.h" +#include "osc/osc/OscReceivedElements.h" +#include "oscfunctions.h" + +void OscFunctionsSendPtrType(UserSettingsPointer m_pConfig, + const QString& OscGroup, + const QString& OscKey, + enum DefOscBodyType OscBodyType, + const QString& OscMessageBodyQString, + int OscMessageBodyInt, + double OscMessageBodyDouble, + float OscMessageBodyFloat); + +class OscReceivePacketListener : public osc::OscPacketListener { + public: + UserSettingsPointer m_pConfig; + OscReceivePacketListener(UserSettingsPointer aPointerHerePlease) { + m_pConfig = aPointerHerePlease; + }; + + private: + void ProcessMessage(const osc::ReceivedMessage& oscMessage, + const IpEndpointName& remoteEndpoint) { + (void)remoteEndpoint; + try { + if (m_pConfig->getValue(ConfigKey("[OSC]", "OscSendSyncTriggers"))) { + sendOscSyncTriggers(m_pConfig); + } + processOscMessage(oscMessage); + } catch (const osc::Exception& e) { + qDebug() << "OSC Error parsing Msg from " + << oscMessage.AddressPattern() << " error: " << e.what(); + } + } + + void processOscMessage(const osc::ReceivedMessage& message) { + oscResult oscIn; + osc::ReceivedMessageArgumentStream args = message.ArgumentStream(); + args >> oscIn.oscValue >> osc::EndMessage; + oscIn.oscAddress = message.AddressPattern(); + oscIn.oscAddress.replace("/", "").replace("(", "[").replace(")", "]"); + determineOscAction(oscIn); + } + + void determineOscAction(oscResult& oscIn) { + bool oscGetP = oscIn.oscAddress.startsWith("GetP#", Qt::CaseInsensitive); + bool oscGetV = oscIn.oscAddress.startsWith("GetV#", Qt::CaseInsensitive); + bool oscSet = !(oscGetP || oscGetV); + + int posDel = oscIn.oscAddress.indexOf("@", 0, Qt::CaseInsensitive); + if (posDel > 0) { + if (oscSet) { + oscIn.oscGroup = oscIn.oscAddress.mid(0, posDel); + oscIn.oscKey = oscIn.oscAddress.mid(posDel + 1, oscIn.oscAddress.length()); + } else { + oscIn.oscGroup = oscIn.oscAddress.mid(5, posDel - 5); + oscIn.oscKey = oscIn.oscAddress.mid(posDel + 1); + } + + if (oscGetP) { + doGetP(oscIn); + } else if (oscGetV) { + doGetV(oscIn); + } else if (oscSet) { + doSet(oscIn, oscIn.oscValue); + } + } + } + + // OSC wants info from Mixxx -> Parameter + void doGetP(oscResult& oscIn) { + if (ControlObject::exists(ConfigKey(oscIn.oscGroup, oscIn.oscKey))) { + auto proxy = std::make_unique(oscIn.oscGroup, oscIn.oscKey); + OscFunctionsSendPtrType(m_pConfig, + oscIn.oscGroup, + oscIn.oscKey, + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + static_cast(proxy->get())); + qDebug() << "OSC Msg Snd: Group, Key: Value:" << oscIn.oscGroup + << "," << oscIn.oscKey << ":" << proxy->get(); + } + } + + // OSC wants info from Mixxx -> Value + void doGetV(oscResult& oscIn) { + if (ControlObject::exists(ConfigKey(oscIn.oscGroup, oscIn.oscKey))) { + OscFunctionsSendPtrType(m_pConfig, + oscIn.oscGroup, + oscIn.oscKey, + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + static_cast(ControlObject::getControl( + oscIn.oscGroup, oscIn.oscKey) + ->get())); + qDebug() << "OSC Msg Rcvd: Get Group, Key: Value:" << oscIn.oscGroup + << "," << oscIn.oscKey << ":" << oscIn.oscValue; + } + } + + // Input from OSC -> Changes in Mixxx + void doSet(oscResult& oscIn, float value) { + if (ControlObject::exists(ConfigKey(oscIn.oscGroup, oscIn.oscKey))) { + auto proxy = std::make_unique(oscIn.oscGroup, oscIn.oscKey); + proxy->set(value); + OscFunctionsSendPtrType(m_pConfig, + oscIn.oscGroup, + oscIn.oscKey, + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + value); + qDebug() << "OSC Msg Rcvd: Group, Key: Value:" << oscIn.oscGroup + << "," << oscIn.oscKey << ":" << value; + } else { + qDebug() << "OSC Msg Rcvd for non-existing Control Object: Group, " + "Key: Value:" + << oscIn.oscGroup << "," << oscIn.oscKey << ":" << value; + } + } + + // trigger OSC to sync + void sendOscSyncTriggers(UserSettingsPointer m_pConfig) { + qDebug() << "Mixxx OSC SendSyncTrigger"; + int interval = m_pConfig + ->getValue(ConfigKey( + "[OSC]", "OscSendSyncTriggersInterval")) + .toInt() / + 1000; + int checkStamp = QDateTime::currentDateTime().toString("ss").toInt(); + + qDebug() << "Mixxx OSC SENT SendSyncTrigger: checkStamp:" << checkStamp; + if (checkStamp % interval == 0) { + OscFunctionsSendPtrType(m_pConfig, + "[Osc]", + "OscSync", + DefOscBodyType::FLOATBODY, + "", + 0, + 0, + 1); + // qDebug() << "Mixxx OSC SENT SendSyncTrigger"; + } + } +}; + +void RunOscReceiver(int OscPortIn, UserSettingsPointer m_pConfig) { + qDebug() << "Mixxx OSC Service Thread started (RunOscReceiver -> OscReceivePacketListener)"; + OscReceivePacketListener listener(m_pConfig); + UdpListeningReceiveSocket socket(IpEndpointName(IpEndpointName::ANY_ADDRESS, OscPortIn), + &listener); + socket.Run(); +} + +// #ifndef NO_OSC_TEST_MAIN + +void OscReceiverMain(UserSettingsPointer m_pConfig) { + if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) { + int CKOscPortInInt = m_pConfig->getValue(ConfigKey("[OSC]", "OscPortIn")).toInt(); + qDebug() << "OSC Enabled -> Started"; + + // qDebuf print active receivers and ip's + for (int i = 1; i <= 5; ++i) { + QString receiverActive = QString("OscReceiver%1Active").arg(i); + QString receiverIp = QString("OscReceiver%1Ip").arg(i); + + if (m_pConfig->getValue(ConfigKey("[OSC]", receiverActive))) { + const QString& CKOscRecIp = m_pConfig->getValue(ConfigKey("[OSC]", receiverIp)); + qDebug() << QString( + "Mixxx OSC Receiver %1 with ip-address: %2 Activated") + .arg(i) + .arg(CKOscRecIp); + } else { + qDebug() << QString("Mixxx OSC Receiver %1 Not Activated").arg(i); + } + } + + for (int i = 1; i < 5; i++) { + const QString& OscTrackGroup = QString("[Channel%1]").arg(i); + OscNoTrackLoadedInGroup(m_pConfig, OscTrackGroup); + } + + qDebug() << "Mixxx OSC Service Thread starting"; + std::thread oscThread(RunOscReceiver, CKOscPortInInt, m_pConfig); + oscThread.detach(); + qDebug() << "Mixxx OSC Service Thread quit"; + } else { + qDebug() << "Mixxx OSC Service NOT Enabled"; + } +} + +// #endif /* NO_OSC_TEST_MAIN */ diff --git a/src/osc/oscreceiver.h b/src/osc/oscreceiver.h new file mode 100644 index 00000000000..0e3fe55f02e --- /dev/null +++ b/src/osc/oscreceiver.h @@ -0,0 +1,33 @@ +#ifndef INCLUDED_OSCRECEIVER_H +#define INCLUDED_OSCRECEIVER_H + +#include +#include +#include +#include + +#include "control/controlproxy.h" +#include "control/pollingcontrolproxy.h" +#include "preferences/settingsmanager.h" +#include "preferences/usersettings.h" +#include "util/class.h" + +class ControlProxy; + +class oscResult { + public: + QString oscAddress; + QString oscGroup; + QString oscKey; + float oscValue; +}; + +class oscReceiver { + public: + UserSettingsPointer m_pConfig; +}; + +void RunOscReceiver(int OscPortIn, UserSettingsPointer m_pConfig); +void OscReceiverMain(UserSettingsPointer m_pConfig); + +#endif /* INCLUDED_OSCRECEIVER_H */ diff --git a/src/preferences/dialog/dlgpreferences.cpp b/src/preferences/dialog/dlgpreferences.cpp index 47b96b9fa06..2e61a59a977 100644 --- a/src/preferences/dialog/dlgpreferences.cpp +++ b/src/preferences/dialog/dlgpreferences.cpp @@ -36,6 +36,7 @@ #include "preferences/dialog/dlgprefbeats.h" #include "preferences/dialog/dlgprefkey.h" +#include "preferences/dialog/dlgprefosc.h" #include "preferences/dialog/dlgprefrecord.h" #include "preferences/dialog/dlgprefreplaygain.h" @@ -211,6 +212,12 @@ DlgPreferences::DlgPreferences( tr("Recording"), "ic_preferences_recording.svg"); + addPageWidget(PreferencesPage( + new DlgPrefOsc(this, m_pConfig), + new QTreeWidgetItem(contentsTreeWidget, QTreeWidgetItem::Type)), + tr("OSC"), + "ic_preferences_broadcast.svg"); + addPageWidget(PreferencesPage( new DlgPrefBeats(this, m_pConfig), new QTreeWidgetItem(contentsTreeWidget, QTreeWidgetItem::Type)), diff --git a/src/preferences/dialog/dlgprefosc.cpp b/src/preferences/dialog/dlgprefosc.cpp new file mode 100644 index 00000000000..6bac1a90657 --- /dev/null +++ b/src/preferences/dialog/dlgprefosc.cpp @@ -0,0 +1,233 @@ +#include "preferences/dialog/dlgprefosc.h" + +#include "moc_dlgprefosc.cpp" + +DlgPrefOsc::DlgPrefOsc(QWidget* pParent, + UserSettingsPointer pConfig) + : DlgPreferencePage(pParent), + m_pConfig(pConfig) { + setupUi(this); + + // If OSC Receiver X is active -> OSC messages from Mixxx will be send to this receiver + + setScrollSafeGuardForAllInputWidgets(this); +} + +void DlgPrefOsc::slotUpdate() { + // Enable OSC-functions in Mixxx + OscEnabledCheckBox->setChecked(m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"), false)); + + OscPortIn->setValue(m_pConfig->getValue(ConfigKey("[OSC]", "OscPortIn"), 9000)); + OscPortOut->setValue( + m_pConfig->getValue(ConfigKey("[OSC]", "OscPortOut"), 9001)); + // OscOutputBufferSize->setValue(m_pConfig->getValue(ConfigKey("[OSC]", + // "OscOutputBufferSize"), 1024)); + // OscIpMtuSize->setValue(m_pConfig->getValue(ConfigKey("[OSC]", + // "OscIpMtuSize"), 1536)); + + OscReceiver1ActiveCheckBox->setChecked(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver1Active"), false)); + OscReceiver1IpByte1->setValue( + m_pConfig->getValue(ConfigKey("[OSC]", "OscReceiver1IpByte1"), 1)); + OscReceiver1IpByte2->setValue( + m_pConfig->getValue(ConfigKey("[OSC]", "OscReceiver1IpByte2"), 1)); + OscReceiver1IpByte3->setValue( + m_pConfig->getValue(ConfigKey("[OSC]", "OscReceiver1IpByte3"), 1)); + OscReceiver1IpByte4->setValue( + m_pConfig->getValue(ConfigKey("[OSC]", "OscReceiver1IpByte4"), 1)); + + OscReceiver2ActiveCheckBox->setChecked(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver2Active"), false)); + OscReceiver2IpByte1->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver2IpByte1"), 1)); + OscReceiver2IpByte2->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver2IpByte2"), 1)); + OscReceiver2IpByte3->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver2IpByte3"), 1)); + OscReceiver2IpByte4->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver2IpByte4"), 1)); + + OscReceiver3ActiveCheckBox->setChecked(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver3Active"), false)); + OscReceiver3IpByte1->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver3IpByte1"), 1)); + OscReceiver3IpByte2->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver3IpByte2"), 1)); + OscReceiver3IpByte3->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver3IpByte3"), 1)); + OscReceiver3IpByte4->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver3IpByte4"), 1)); + + OscReceiver4ActiveCheckBox->setChecked(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver4Active"), false)); + OscReceiver4IpByte1->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver4IpByte1"), 1)); + OscReceiver4IpByte2->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver4IpByte2"), 1)); + OscReceiver4IpByte3->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver4IpByte3"), 1)); + OscReceiver4IpByte4->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver4IpByte4"), 1)); + + OscReceiver5ActiveCheckBox->setChecked(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver5Active"), false)); + OscReceiver5IpByte1->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver5IpByte1"), 1)); + OscReceiver5IpByte2->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver5IpByte2"), 1)); + OscReceiver5IpByte3->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver5IpByte3"), 1)); + OscReceiver5IpByte4->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscReceiver5IpByte4"), 1)); + + OscSendSyncTriggers->setChecked(m_pConfig->getValue( + ConfigKey("[OSC]", "OscSendSyncTriggers"), false)); + OscSendSyncTriggersInterval->setValue(m_pConfig->getValue( + ConfigKey("[OSC]", "OscSendSyncTriggersInterval"), 5000)); +} + +void DlgPrefOsc::slotApply() { + m_pConfig->setValue(ConfigKey("[OSC]", "OscEnabled"), + OscEnabledCheckBox->isChecked()); + + m_pConfig->setValue(ConfigKey("[OSC]", "OscPortIn"), + OscPortIn->value()); + + m_pConfig->setValue(ConfigKey("[OSC]", "OscPortOut"), + OscPortOut->value()); + + // m_pConfig->setValue(ConfigKey("[OSC]", "OscOutputBufferSize"), + // OscOutputBufferSize->value()); + + // m_pConfig->setValue(ConfigKey("[OSC]", "OscIpMtuSize"), + // OscIpMtuSize->value()); + + QString OscReceiverIp, OscReceiverIpByte1, OscReceiverIpByte2, + OscReceiverIpByte3, OscReceiverIpByte4; + + // Receiver 1 + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver1Active"), + OscReceiver1ActiveCheckBox->isChecked()); + OscReceiverIpByte1 = QString("%1").arg(OscReceiver1IpByte1->value()); + OscReceiverIpByte2 = QString("%1").arg(OscReceiver1IpByte2->value()); + OscReceiverIpByte3 = QString("%1").arg(OscReceiver1IpByte3->value()); + OscReceiverIpByte4 = QString("%1").arg(OscReceiver1IpByte4->value()); + OscReceiverIp = QString("%1.%2.%3.%4") + .arg(OscReceiverIpByte1, + OscReceiverIpByte2, + OscReceiverIpByte3, + OscReceiverIpByte4); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver1Ip"), OscReceiverIp); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver1IpByte1"), + OscReceiver1IpByte1->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver1IpByte2"), + OscReceiver1IpByte2->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver1IpByte3"), + OscReceiver1IpByte3->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver1IpByte4"), + OscReceiver1IpByte4->value()); + + // Receiver 2 + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver2Active"), + OscReceiver2ActiveCheckBox->isChecked()); + OscReceiverIpByte1 = QString("%1").arg(OscReceiver2IpByte1->value()); + OscReceiverIpByte2 = QString("%1").arg(OscReceiver2IpByte2->value()); + OscReceiverIpByte3 = QString("%1").arg(OscReceiver2IpByte3->value()); + OscReceiverIpByte4 = QString("%1").arg(OscReceiver2IpByte4->value()); + OscReceiverIp = QString("%1.%2.%3.%4") + .arg(OscReceiverIpByte1, + OscReceiverIpByte2, + OscReceiverIpByte3, + OscReceiverIpByte4); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver2Ip"), OscReceiverIp); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver2IpByte1"), + OscReceiver2IpByte1->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver2IpByte2"), + OscReceiver2IpByte2->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver2IpByte3"), + OscReceiver2IpByte3->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver2IpByte4"), + OscReceiver2IpByte4->value()); + + // Receiver 3 + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver3Active"), + OscReceiver3ActiveCheckBox->isChecked()); + OscReceiverIpByte1 = QString("%1").arg(OscReceiver3IpByte1->value()); + OscReceiverIpByte2 = QString("%1").arg(OscReceiver3IpByte2->value()); + OscReceiverIpByte3 = QString("%1").arg(OscReceiver3IpByte3->value()); + OscReceiverIpByte4 = QString("%1").arg(OscReceiver3IpByte4->value()); + OscReceiverIp = QString("%1.%2.%3.%4") + .arg(OscReceiverIpByte1, + OscReceiverIpByte2, + OscReceiverIpByte3, + OscReceiverIpByte4); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver3Ip"), OscReceiverIp); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver3IpByte1"), + OscReceiver3IpByte1->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver3IpByte2"), + OscReceiver3IpByte2->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver3IpByte3"), + OscReceiver3IpByte3->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver3IpByte4"), + OscReceiver3IpByte4->value()); + + // Receiver 4 + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver4Active"), + OscReceiver4ActiveCheckBox->isChecked()); + OscReceiverIpByte1 = QString("%1").arg(OscReceiver4IpByte1->value()); + OscReceiverIpByte2 = QString("%1").arg(OscReceiver4IpByte2->value()); + OscReceiverIpByte3 = QString("%1").arg(OscReceiver4IpByte3->value()); + OscReceiverIpByte4 = QString("%1").arg(OscReceiver4IpByte4->value()); + OscReceiverIp = QString("%1.%2.%3.%4") + .arg(OscReceiverIpByte1, + OscReceiverIpByte2, + OscReceiverIpByte3, + OscReceiverIpByte4); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver4Ip"), OscReceiverIp); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver4IpByte1"), + OscReceiver4IpByte1->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver4IpByte2"), + OscReceiver4IpByte2->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver4IpByte3"), + OscReceiver4IpByte3->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver4IpByte4"), + OscReceiver4IpByte4->value()); + + // Receiver 5 + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver5Active"), + OscReceiver5ActiveCheckBox->isChecked()); + OscReceiverIpByte1 = QString("%1").arg(OscReceiver5IpByte1->value()); + OscReceiverIpByte2 = QString("%1").arg(OscReceiver5IpByte2->value()); + OscReceiverIpByte3 = QString("%1").arg(OscReceiver5IpByte3->value()); + OscReceiverIpByte4 = QString("%1").arg(OscReceiver5IpByte4->value()); + OscReceiverIp = QString("%1.%2.%3.%4") + .arg(OscReceiverIpByte1, + OscReceiverIpByte2, + OscReceiverIpByte3, + OscReceiverIpByte4); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver5Ip"), OscReceiverIp); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver5IpByte1"), + OscReceiver5IpByte1->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver5IpByte2"), + OscReceiver5IpByte2->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver5IpByte3"), + OscReceiver5IpByte3->value()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscReceiver5IpByte4"), + OscReceiver5IpByte4->value()); + + // Sync Triggers + m_pConfig->setValue(ConfigKey("[OSC]", "OscSendSyncTriggers"), + OscSendSyncTriggers->isChecked()); + m_pConfig->setValue(ConfigKey("[OSC]", "OscSendSyncTriggersInterval"), + OscSendSyncTriggersInterval->value()); +} + +void DlgPrefOsc::slotResetToDefaults() { + OscEnabledCheckBox->setChecked(false); + OscReceiver1ActiveCheckBox->setChecked(false); + OscReceiver2ActiveCheckBox->setChecked(false); + OscReceiver3ActiveCheckBox->setChecked(false); + OscReceiver4ActiveCheckBox->setChecked(false); + OscReceiver5ActiveCheckBox->setChecked(false); + OscSendSyncTriggers->setChecked(false); +} diff --git a/src/preferences/dialog/dlgprefosc.h b/src/preferences/dialog/dlgprefosc.h new file mode 100644 index 00000000000..3c82a4b32ab --- /dev/null +++ b/src/preferences/dialog/dlgprefosc.h @@ -0,0 +1,23 @@ +#pragma once + +#include "preferences/dialog/dlgpreferencepage.h" +#include "preferences/dialog/ui_dlgprefoscdlg.h" +#include "preferences/usersettings.h" + +class QWidget; + +class DlgPrefOsc : public DlgPreferencePage, public Ui::DlgPrefOscDlg { + Q_OBJECT + public: + DlgPrefOsc(QWidget* pParent, UserSettingsPointer pConfig); + + public slots: + void slotUpdate() override; + void slotApply() override; + void slotResetToDefaults() override; + + private slots: + + private: + UserSettingsPointer m_pConfig; +}; diff --git a/src/preferences/dialog/dlgprefoscdlg.ui b/src/preferences/dialog/dlgprefoscdlg.ui new file mode 100644 index 00000000000..8e1e31a3193 --- /dev/null +++ b/src/preferences/dialog/dlgprefoscdlg.ui @@ -0,0 +1,1065 @@ + + + DlgPrefOscDlg + + + + 0 + 0 + 865 + 619 + + + + OSC Preferences + + + + + + + + + 16777215 + 200 + + + + + 0 + 150 + + + + Mixxx as an OSC-Receiver(Client) + + + + + 10 + 20 + 120 + 20 + + + + + 0 + 0 + + + + Uncheck, to ignore all played tracks. + + + OSC-In Port (UDP) + + + OscEnabledCheckBox + + + + + + 150 + 20 + 100 + 20 + + + + + 150 + 16777215 + + + + + 150 + 0 + + + + 1 + + + 9999 + + + + + + 0 + 80 + 809 + 13 + + + + + 16777215 + 150 + + + + + 0 + 150 + + + + Mixxx as an OSC-Sender (Server) + + + + + + 10 + 100 + 120 + 20 + + + + OSC-Out Port (UDP) + + + + + + 150 + 100 + 100 + 20 + + + + 1 + + + 9999 + + + + + + 10 + 140 + 291 + 17 + + + + Send SyncTriggers + + + + + + 340 + 160 + 100 + 20 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1000 + + + 60000 + + + 1000 + + + 1000 + + + + + + 30 + 160 + 300 + 21 + + + + Interval between the SyncTriggers (in milliseconds) + + + + + + + + + 16777215 + 80 + + + + OSC Settings + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + Uncheck, to ignore all played tracks. + + + + + + + + + + Enable OSC in Mixxx + + + + + + + Mixxx must be restarted for changes to take effect + + + + + + + + + + OSC Receivers (Clients) (Machines that need to receive OSC-messages from Mixxx) + + + + + 10 + 30 + 50 + 20 + + + + Receiver + + + + + + 120 + 60 + 77 + 19 + + + + Active + + + 0 + + + + + + 220 + 60 + 80 + 20 + + + + 1 + + + 255 + + + + + + 220 + 30 + 60 + 20 + + + + 1st byte + + + + + + 10 + 60 + 80 + 13 + + + + Receiver 1 + + + + + + 120 + 30 + 50 + 20 + + + + Active + + + + + + 320 + 30 + 60 + 20 + + + + 2nd byte + + + + + + 420 + 30 + 60 + 20 + + + + 3rd byte + + + + + + 520 + 30 + 60 + 20 + + + + 4th byte + + + + + + 200 + 30 + 20 + 20 + + + + IP: + + + + + + 320 + 60 + 80 + 20 + + + + 0 + + + 255 + + + + + + 420 + 60 + 80 + 20 + + + + 0 + + + 255 + + + + + + 520 + 60 + 80 + 20 + + + + 1 + + + 255 + + + + + + 10 + 90 + 80 + 20 + + + + Receiver 2 + + + + + + 120 + 90 + 80 + 20 + + + + Active + + + + + + 10 + 120 + 80 + 20 + + + + Receiver 3 + + + + + + 120 + 120 + 80 + 20 + + + + Active + + + + + + 10 + 150 + 80 + 20 + + + + Receiver 4 + + + + + + 120 + 150 + 80 + 20 + + + + Active + + + + + + 10 + 180 + 80 + 20 + + + + Receiver 5 + + + + + + 120 + 180 + 80 + 20 + + + + Active + + + + + + 520 + 90 + 80 + 20 + + + + 1 + + + 255 + + + + + + 220 + 90 + 80 + 20 + + + + 1 + + + 255 + + + + + + 420 + 90 + 80 + 20 + + + + 0 + + + 255 + + + + + + 320 + 90 + 80 + 20 + + + + 0 + + + 255 + + + + + + 520 + 120 + 80 + 20 + + + + 1 + + + 255 + + + + + + 220 + 120 + 80 + 20 + + + + 1 + + + 255 + + + + + + 420 + 120 + 80 + 20 + + + + 0 + + + 255 + + + + + + 320 + 120 + 80 + 20 + + + + 0 + + + 255 + + + + + + 520 + 150 + 80 + 20 + + + + 1 + + + 255 + + + + + + 320 + 150 + 80 + 20 + + + + 0 + + + 255 + + + + + + 220 + 150 + 80 + 20 + + + + 1 + + + 255 + + + + + + 420 + 150 + 80 + 20 + + + + 0 + + + 255 + + + + + + 520 + 180 + 80 + 20 + + + + 1 + + + 255 + + + + + + 320 + 180 + 80 + 20 + + + + 0 + + + 255 + + + + + + 220 + 180 + 80 + 20 + + + + 1 + + + 255 + + + + + + 420 + 180 + 80 + 20 + + + + 0 + + + 255 + + + + + + 300 + 60 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 400 + 60 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 500 + 60 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 400 + 90 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 300 + 90 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 500 + 90 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 400 + 120 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 300 + 120 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 500 + 120 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 400 + 150 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 300 + 150 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 500 + 150 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 400 + 180 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 300 + 180 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + 500 + 180 + 10 + 20 + + + + . + + + Qt::AlignCenter + + + + + + + + + + OscEnabledCheckBox + OscPortIn + OscPortOut + OscReceiver1ActiveCheckBox + OscReceiver1IpByte1 + OscReceiver1IpByte2 + OscReceiver1IpByte3 + OscReceiver1IpByte4 + OscReceiver2ActiveCheckBox + OscReceiver2IpByte1 + OscReceiver2IpByte2 + OscReceiver2IpByte3 + OscReceiver2IpByte4 + OscReceiver3ActiveCheckBox + OscReceiver3IpByte1 + OscReceiver3IpByte2 + OscReceiver3IpByte3 + OscReceiver3IpByte4 + OscReceiver4ActiveCheckBox + OscReceiver4IpByte1 + OscReceiver4IpByte2 + OscReceiver4IpByte3 + OscReceiver4IpByte4 + OscReceiver5ActiveCheckBox + OscReceiver5IpByte1 + OscReceiver5IpByte2 + OscReceiver5IpByte3 + OscReceiver5IpByte4 + + + +