diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b319dcbe3a..b5995db3660 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,43 @@ 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) +#eve osc + #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/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 00fdadd8a1e..c13af6036f0 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -859,6 +859,10 @@ void CueControl::hotcueSet(HotcueControl* pControl, double value, HotcueSetMode mixxx::audio::FramePos cueStartPosition; mixxx::audio::FramePos cueEndPosition; mixxx::CueType cueType = mixxx::CueType::Invalid; + int passStem1Vol; + int passStem2Vol; + int passStem3Vol; + int passStem4Vol; bool loopEnabled = m_pLoopEnabled->toBool(); if (mode == HotcueSetMode::Auto) { @@ -888,6 +892,49 @@ void CueControl::hotcueSet(HotcueControl* pControl, double value, HotcueSetMode // If no loop is enabled, just store regular jump cue cueStartPosition = getQuantizedCurrentPosition(); cueType = mixxx::CueType::HotCue; + // EveCue + bool TrackStem = m_pLoadedTrack->hasStem(); + + if (TrackStem) { + const QString groupBaseName = getGroup().remove("[").remove("]"); + const QString stemGroups[] = { + QString("[%1Stem1]").arg(groupBaseName), + QString("[%1Stem2]").arg(groupBaseName), + QString("[%1Stem3]").arg(groupBaseName), + QString("[%1Stem4]").arg(groupBaseName), + }; + + // Helper lambda to get the mute multiplier + auto getMuteMultiplier = [](const QString& group) -> int { + if (ControlObject::exists(ConfigKey(group, "mute"))) { + auto proxyMute = std::make_unique(group, "mute"); + return proxyMute->get() ? -1 : 1; + } + return 1; // Default multiplier when no mute exists + }; + + // Helper lambda to get the volume value adjusted by the mute multiplier + auto getVolume = [](const QString& group, int muteMultiplier) -> int { + if (ControlObject::exists(ConfigKey(group, "volume"))) { + auto proxyVolume = std::make_unique(group, "volume"); + return static_cast(proxyVolume->get() * 100 * muteMultiplier); + } + return 100 * muteMultiplier; // Default value when no volume exists + }; + + // Retrieve and assign stem volume values using helpers + passStem1Vol = getVolume(stemGroups[0], getMuteMultiplier(stemGroups[0])); + passStem2Vol = getVolume(stemGroups[1], getMuteMultiplier(stemGroups[1])); + passStem3Vol = getVolume(stemGroups[2], getMuteMultiplier(stemGroups[2])); + passStem4Vol = getVolume(stemGroups[3], getMuteMultiplier(stemGroups[3])); + + } else { + passStem1Vol = 100; + passStem2Vol = 100; + passStem3Vol = 100; + passStem4Vol = 100; + } + // EveCue break; } case HotcueSetMode::Loop: { @@ -912,6 +959,49 @@ void CueControl::hotcueSet(HotcueControl* pControl, double value, HotcueSetMode cueEndPosition = pBeats->findNBeatsFromPosition(cueStartPosition, beatloopSize); } } + // EveLoop + bool TrackStem = m_pLoadedTrack->hasStem(); + + if (TrackStem) { + const QString groupBaseName = getGroup().remove("[").remove("]"); + const QString stemGroups[] = { + QString("[%1Stem1]").arg(groupBaseName), + QString("[%1Stem2]").arg(groupBaseName), + QString("[%1Stem3]").arg(groupBaseName), + QString("[%1Stem4]").arg(groupBaseName), + }; + + // Helper lambda to get the mute multiplier + auto getMuteMultiplier = [](const QString& group) -> int { + if (ControlObject::exists(ConfigKey(group, "mute"))) { + auto proxyMute = std::make_unique(group, "mute"); + return proxyMute->get() ? -1 : 1; + } + return 1; // Default multiplier when no mute exists + }; + + // Helper lambda to get the volume value adjusted by the mute multiplier + auto getVolume = [](const QString& group, int muteMultiplier) -> int { + if (ControlObject::exists(ConfigKey(group, "volume"))) { + auto proxyVolume = std::make_unique(group, "volume"); + return static_cast(proxyVolume->get() * 100 * muteMultiplier); + } + return 100 * muteMultiplier; // Default value when no volume exists + }; + + // Retrieve and assign stem volume values using helpers + passStem1Vol = getVolume(stemGroups[0], getMuteMultiplier(stemGroups[0])); + passStem2Vol = getVolume(stemGroups[1], getMuteMultiplier(stemGroups[1])); + passStem3Vol = getVolume(stemGroups[2], getMuteMultiplier(stemGroups[2])); + passStem4Vol = getVolume(stemGroups[3], getMuteMultiplier(stemGroups[3])); + + } else { + passStem1Vol = 100; + passStem2Vol = 100; + passStem3Vol = 100; + passStem4Vol = 100; + } + // EveLoop cueType = mixxx::CueType::Loop; break; } @@ -954,7 +1044,11 @@ void CueControl::hotcueSet(HotcueControl* pControl, double value, HotcueSetMode hotcueIndex, cueStartPosition, cueEndPosition, - color); + color, + passStem1Vol, + passStem2Vol, + passStem3Vol, + passStem4Vol); // TODO(XXX) deal with spurious signals attachCue(pCue, pControl); @@ -1115,13 +1209,43 @@ void CueControl::hotcueActivate(HotcueControl* pControl, double value, HotcueSet // pressed if (pCue && pCue->getPosition().isValid() && pCue->getType() != mixxx::CueType::Invalid) { + bool TrackStem = m_pLoadedTrack->hasStem(); + + if (TrackStem) { + const QString groupBaseName = getGroup().remove('[').remove(']'); + const std::vector stemGroups = { + QString("[%1Stem1]").arg(groupBaseName), + QString("[%1Stem2]").arg(groupBaseName), + QString("[%1Stem3]").arg(groupBaseName), + QString("[%1Stem4]").arg(groupBaseName)}; + + auto setMuteAndVolume = [](const QString& group, int volume) { + if (ControlObject::exists(ConfigKey(group, "mute"))) { + auto proxyMute = std::make_unique(group, "mute"); + proxyMute->set(volume < 1 ? 1 : 0); + } + if (ControlObject::exists(ConfigKey(group, "volume"))) { + auto proxyVol = std::make_unique(group, "volume"); + proxyVol->set(std::abs(volume) / 100.0); + } + }; + + setMuteAndVolume(stemGroups[0], pCue->getStem1vol()); + setMuteAndVolume(stemGroups[1], pCue->getStem2vol()); + setMuteAndVolume(stemGroups[2], pCue->getStem3vol()); + setMuteAndVolume(stemGroups[3], pCue->getStem4vol()); + } + if (m_pPlay->toBool() && m_currentlyPreviewingIndex == Cue::kNoHotCue) { // playing by Play button + switch (pCue->getType()) { case mixxx::CueType::HotCue: + hotcueGoto(pControl, value); break; case mixxx::CueType::Loop: + if (m_pCurrentSavedLoopControl != pControl) { setCurrentSavedLoopControlAndActivate(pControl); } else { @@ -2475,6 +2599,15 @@ HotcueControl::HotcueControl(const QString& group, int hotcueIndex) // Add an alias for the legacy hotcue_X_enabled CO m_pHotcueStatus->addAlias(keyForControl(QStringLiteral("enabled"))); + m_hotcueStem1vol = std::make_unique(keyForControl(QStringLiteral("stem1vol"))); + m_hotcueStem2vol = std::make_unique(keyForControl(QStringLiteral("stem2vol"))); + m_hotcueStem3vol = std::make_unique(keyForControl(QStringLiteral("stem3vol"))); + m_hotcueStem4vol = std::make_unique(keyForControl(QStringLiteral("stem4vol"))); + m_hotcueStem1vol->setReadOnly(); + m_hotcueStem2vol->setReadOnly(); + m_hotcueStem3vol->setReadOnly(); + m_hotcueStem4vol->setReadOnly(); + m_hotcueType = std::make_unique(keyForControl(QStringLiteral("type"))); m_hotcueType->setReadOnly(); @@ -2685,6 +2818,7 @@ void HotcueControl::setCue(const CuePointer& pCue) { setEndPosition(pos.endPosition); // qDebug() << "HotcueControl::setCue"; setColor(pCue->getColor()); + // setStemvol(int stemvol); setStatus((pCue->getType() == mixxx::CueType::Invalid) ? HotcueControl::Status::Empty : HotcueControl::Status::Set); @@ -2726,6 +2860,19 @@ void HotcueControl::setType(mixxx::CueType type) { m_hotcueType->forceSet(static_cast(type)); } +void HotcueControl::setStem1vol(int stem1vol) { + m_hotcueStem1vol->set(static_cast(stem1vol)); +} +void HotcueControl::setStem2vol(int stem2vol) { + m_hotcueStem2vol->set(static_cast(stem2vol)); +} +void HotcueControl::setStem3vol(int stem3vol) { + m_hotcueStem3vol->set(static_cast(stem3vol)); +} +void HotcueControl::setStem4vol(int stem4vol) { + m_hotcueStem4vol->set(static_cast(stem4vol)); +} + void HotcueControl::setStatus(HotcueControl::Status status) { m_pHotcueStatus->forceSet(static_cast(status)); } diff --git a/src/engine/controls/cuecontrol.h b/src/engine/controls/cuecontrol.h index c3bb82b0ec8..225fa72bea5 100644 --- a/src/engine/controls/cuecontrol.h +++ b/src/engine/controls/cuecontrol.h @@ -99,6 +99,11 @@ class HotcueControl : public QObject { void setColor(mixxx::RgbColor::optional_t newColor); mixxx::RgbColor::optional_t getColor() const; + void setStem1vol(int stem1vol); + void setStem2vol(int stem2vol); + void setStem3vol(int stem3vol); + void setStem4vol(int stem4vol); + /// Used for caching the preview state of this hotcue control /// for the case the cue is deleted during preview. mixxx::CueType getPreviewingType() const { @@ -167,6 +172,10 @@ class HotcueControl : public QObject { std::unique_ptr m_pHotcueStatus; std::unique_ptr m_hotcueType; std::unique_ptr m_hotcueColor; + std::unique_ptr m_hotcueStem1vol; + std::unique_ptr m_hotcueStem2vol; + std::unique_ptr m_hotcueStem3vol; + std::unique_ptr m_hotcueStem4vol; // Hotcue button controls std::unique_ptr m_hotcueSet; std::unique_ptr m_hotcueSetCue; diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 68ba74dc41f..02e53bf6ed4 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -54,6 +54,25 @@ const QString kAppGroup = QStringLiteral("[App]"); } // anonymous namespace +// EveOSC + +enum class DefOscBodyType { + STRINGBODY, + INTBODY, + DOUBLEBODY, + FLOATBODY +}; + +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, @@ -173,6 +192,22 @@ EngineBuffer::EngineBuffer(const QString& group, m_pTrackSamples = new ControlObject(ConfigKey(m_group, "track_samples")); m_pTrackSampleRate = new ControlObject(ConfigKey(m_group, "track_samplerate")); + m_pTrackType = new ControlObject(ConfigKey(m_group, "track_type")); + m_pTrackTypeLength = new ControlObject(ConfigKey(m_group, "track_type_length")); + m_pTrackArtistLength = new ControlObject(ConfigKey(m_group, "track_artist_length")); + m_pTrackArtist_1 = new ControlObject(ConfigKey(m_group, "track_artist_1")); + m_pTrackArtist_2 = new ControlObject(ConfigKey(m_group, "track_artist_2")); + m_pTrackArtist_3 = new ControlObject(ConfigKey(m_group, "track_artist_3")); + m_pTrackArtist_4 = new ControlObject(ConfigKey(m_group, "track_artist_4")); + m_pTrackArtist_5 = new ControlObject(ConfigKey(m_group, "track_artist_5")); + + m_pTrackTitleLength = new ControlObject(ConfigKey(m_group, "track_title_length")); + m_pTrackTitle_1 = new ControlObject(ConfigKey(m_group, "track_title_1")); + m_pTrackTitle_2 = new ControlObject(ConfigKey(m_group, "track_title_2")); + m_pTrackTitle_3 = new ControlObject(ConfigKey(m_group, "track_title_3")); + m_pTrackTitle_4 = new ControlObject(ConfigKey(m_group, "track_title_4")); + m_pTrackTitle_5 = new ControlObject(ConfigKey(m_group, "track_title_5")); + m_pKeylock = new ControlPushButton(ConfigKey(m_group, "keylock"), true); m_pKeylock->setButtonMode(mixxx::control::ButtonMode::Toggle); @@ -312,6 +347,22 @@ EngineBuffer::~EngineBuffer() { delete m_pTrackSamples; delete m_pTrackSampleRate; + delete m_pTrackType; + delete m_pTrackTypeLength; + delete m_pTrackArtistLength; + delete m_pTrackArtist_1; + delete m_pTrackArtist_2; + delete m_pTrackArtist_3; + delete m_pTrackArtist_4; + delete m_pTrackArtist_5; + + delete m_pTrackTitleLength; + delete m_pTrackTitle_1; + delete m_pTrackTitle_2; + delete m_pTrackTitle_3; + delete m_pTrackTitle_4; + delete m_pTrackTitle_5; + delete m_pScaleLinear; delete m_pScaleST; #ifdef __RUBBERBAND__ @@ -563,6 +614,157 @@ 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 + + // Eve start + // Type + QString TrackInfoType = pTrack->getType(); + QString TrackInfoTypeTest = TrackInfoType; + int TrackInfoTypeTestLength = TrackInfoTypeTest.length(); + if (TrackInfoTypeTestLength > 5) { + TrackInfoType = TrackInfoTypeTest.mid(0, 5); + }; + m_pTrackTypeLength->set(TrackInfoTypeTestLength); + + int CharType[5]; + for (int i = 1; i <= 5; i++) { + CharType[i - 1] = 0; + } + + for (int i = 1; i <= TrackInfoType.length(); i++) { + if ((TrackInfoType.at(i - 1).toLatin1()) < 0) { + CharType[i - 1] = ((TrackInfoType.at(i - 1).toLatin1()) + 300); + } else { + CharType[i - 1] = (TrackInfoType.at(i - 1).toLatin1()); + }; + } + + double TrackTypePart = 0.0; + TrackTypePart = (1.0 * CharType[0] * 1000000000000) + + (1.0 * CharType[1] * 1000000000) + (1.0 * CharType[2] * 1000000) + + (1.0 * CharType[3] * 1000) + (1.0 * CharType[4] * 1); + m_pTrackType->set(TrackTypePart); + + // Title + QString TrackInfoTitle = pTrack->getTitle(); + QString TrackInfoTitleTest = TrackInfoTitle; + int TrackInfoTitleTestLength = TrackInfoTitleTest.length(); + if (TrackInfoTitleTestLength > 200) { + TrackInfoTitle = TrackInfoTitleTest.mid(0, 200); + }; + m_pTrackTitleLength->set(TrackInfoTitleTestLength); + + int CharTitle[200]; + for (int i = 1; i <= 200; i++) { + CharTitle[i - 1] = 0; + } + + // for (int i = 1; i <= TrackInfoTitle.length(); i++) { + for (int i = 1; i <= 25; i++) { + if ((TrackInfoTitle.at(i - 1).toLatin1()) < 0) { + CharTitle[i - 1] = ((TrackInfoTitle.at(i - 1).toLatin1()) + 300); + } else { + CharTitle[i - 1] = (TrackInfoTitle.at(i - 1).toLatin1()); + }; + } + + // Artist + QString TrackInfoArtist = pTrack->getArtist(); + QString TrackInfoArtistTest = TrackInfoArtist; + int TrackInfoArtistTestLength = TrackInfoArtistTest.length(); + if (TrackInfoArtistTestLength > 200) { + TrackInfoArtist = TrackInfoArtist.mid(0, 200); + }; + m_pTrackArtistLength->set(TrackInfoArtistTestLength); + + int CharArtist[200]; + for (int i = 1; i <= 200; i++) { + CharArtist[i - 1] = 0; + } + + // for (int i = 1; i <= TrackInfoArtist.length(); i++) { + for (int i = 1; i <= 25; i++) { + if ((TrackInfoArtist.at(i - 1).toLatin1()) < 0) { + CharArtist[i - 1] = ((TrackInfoArtist.at(i - 1).toLatin1()) + 300); + } else { + CharArtist[i - 1] = (TrackInfoArtist.at(i - 1).toLatin1()); + }; + } + + double TrackTitlePart_1 = 0.0; + double TrackTitlePart_2 = 0.0; + double TrackTitlePart_3 = 0.0; + double TrackTitlePart_4 = 0.0; + double TrackTitlePart_5 = 0.0; + + TrackTitlePart_1 = (1.0 * CharTitle[0] * 1000000000000) + + (1.0 * CharTitle[1] * 1000000000) + (1.0 * CharTitle[2] * 1000000) + + (1.0 * CharTitle[3] * 1000) + (1.0 * CharTitle[4] * 1); + TrackTitlePart_2 = (1.0 * CharTitle[5] * 1000000000000) + + (1.0 * CharTitle[6] * 1000000000) + (1.0 * CharTitle[7] * 1000000) + + (1.0 * CharTitle[8] * 1000) + (1.0 * CharTitle[9] * 1); + TrackTitlePart_3 = (1.0 * CharTitle[10] * 1000000000000) + + (1.0 * CharTitle[11] * 1000000000) + + (1.0 * CharTitle[12] * 1000000) + (1.0 * CharTitle[13] * 1000) + + (1.0 * CharTitle[14] * 1); + TrackTitlePart_4 = (1.0 * CharTitle[15] * 1000000000000) + + (1.0 * CharTitle[16] * 1000000000) + + (1.0 * CharTitle[17] * 1000000) + (1.0 * CharTitle[18] * 1000) + + (1.0 * CharTitle[19] * 1); + TrackTitlePart_5 = (1.0 * CharTitle[20] * 1000000000000) + + (1.0 * CharTitle[21] * 1000000000) + + (1.0 * CharTitle[22] * 1000000) + (1.0 * CharTitle[23] * 1000) + + (1.0 * CharTitle[24] * 1); + + m_pTrackTitle_1->set(TrackTitlePart_1); + m_pTrackTitle_2->set(TrackTitlePart_2); + m_pTrackTitle_3->set(TrackTitlePart_3); + m_pTrackTitle_4->set(TrackTitlePart_4); + m_pTrackTitle_5->set(TrackTitlePart_5); + + double TrackArtistPart_1 = 0.0; + double TrackArtistPart_2 = 0.0; + double TrackArtistPart_3 = 0.0; + double TrackArtistPart_4 = 0.0; + double TrackArtistPart_5 = 0.0; + + TrackArtistPart_1 = (1.0 * CharArtist[0] * 1000000000000) + + (1.0 * CharArtist[1] * 1000000000) + + (1.0 * CharArtist[2] * 1000000) + (1.0 * CharArtist[3] * 1000) + + (1.0 * CharArtist[4] * 1); + TrackArtistPart_2 = (1.0 * CharArtist[5] * 1000000000000) + + (1.0 * CharArtist[6] * 1000000000) + + (1.0 * CharArtist[7] * 1000000) + (1.0 * CharArtist[8] * 1000) + + (1.0 * CharArtist[9] * 1); + TrackArtistPart_3 = (1.0 * CharArtist[10] * 1000000000000) + + (1.0 * CharArtist[11] * 1000000000) + + (1.0 * CharArtist[12] * 1000000) + (1.0 * CharArtist[13] * 1000) + + (1.0 * CharArtist[14] * 1); + TrackArtistPart_4 = (1.0 * CharArtist[15] * 1000000000000) + + (1.0 * CharArtist[16] * 1000000000) + + (1.0 * CharArtist[17] * 1000000) + (1.0 * CharArtist[18] * 1000) + + (1.0 * CharArtist[19] * 1); + TrackArtistPart_5 = (1.0 * CharArtist[20] * 1000000000000) + + (1.0 * CharArtist[21] * 1000000000) + + (1.0 * CharArtist[22] * 1000000) + (1.0 * CharArtist[23] * 1000) + + (1.0 * CharArtist[24] * 1); + + m_pTrackArtist_1->set(TrackArtistPart_1); + m_pTrackArtist_2->set(TrackArtistPart_2); + m_pTrackArtist_3->set(TrackArtistPart_3); + m_pTrackArtist_4->set(TrackArtistPart_4); + m_pTrackArtist_5->set(TrackArtistPart_5); + // Reset slip mode m_pSlipButton->set(0); m_bSlipEnabledProcessing = false; @@ -632,6 +834,28 @@ void EngineBuffer::ejectTrack() { m_pTrackSampleRate->set(0); m_pTrackLoaded->forceSet(0); + // EveOSC begin + + if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) { + OscNoTrackLoadedInGroup(m_pConfig, getGroup()); + } + + m_pTrackType->set(0); + m_pTrackTypeLength->set(0); + m_pTrackArtistLength->set(0); + m_pTrackArtist_1->set(0); + m_pTrackArtist_2->set(0); + m_pTrackArtist_3->set(0); + m_pTrackArtist_4->set(0); + m_pTrackArtist_5->set(0); + + m_pTrackTitleLength->set(0); + m_pTrackTitle_1->set(0); + m_pTrackTitle_2->set(0); + m_pTrackTitle_3->set(0); + m_pTrackTitle_4->set(0); + m_pTrackTitle_5->set(0); + m_playButton->set(0.0); m_playposSlider->set(0); m_pCueControl->resetIndicators(); diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index b0ef812686f..a69260e37e3 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -394,6 +394,26 @@ class EngineBuffer : public EngineObject { ControlObject* m_pTrackSamples; ControlObject* m_pTrackSampleRate; + // TrackType + ControlObject* m_pTrackType; + ControlObject* m_pTrackTypeLength; + + // TrackArtist + ControlObject* m_pTrackArtistLength; + ControlObject* m_pTrackArtist_1; + ControlObject* m_pTrackArtist_2; + ControlObject* m_pTrackArtist_3; + ControlObject* m_pTrackArtist_4; + ControlObject* m_pTrackArtist_5; + + // TrackTitle + ControlObject* m_pTrackTitleLength; + ControlObject* m_pTrackTitle_1; + ControlObject* m_pTrackTitle_2; + ControlObject* m_pTrackTitle_3; + ControlObject* m_pTrackTitle_4; + ControlObject* m_pTrackTitle_5; + ControlPushButton* m_playButton; ControlPushButton* m_playStartButton; ControlPushButton* m_stopStartButton; diff --git a/src/library/dao/cuedao.cpp b/src/library/dao/cuedao.cpp index 5533382e3dd..0b66e044532 100644 --- a/src/library/dao/cuedao.cpp +++ b/src/library/dao/cuedao.cpp @@ -46,6 +46,10 @@ CuePointer cueFromRow(const QSqlRecord& row) { int hotcue = row.value(row.indexOf("hotcue")).toInt(); QString label = labelFromQVariant(row.value(row.indexOf("label"))); mixxx::RgbColor::optional_t color = mixxx::RgbColor::fromQVariant(row.value(row.indexOf("color"))); + int stem1vol = row.value(row.indexOf("stem1vol")).toInt(); + int stem2vol = row.value(row.indexOf("stem2vol")).toInt(); + int stem3vol = row.value(row.indexOf("stem3vol")).toInt(); + int stem4vol = row.value(row.indexOf("stem4vol")).toInt(); VERIFY_OR_DEBUG_ASSERT(color) { return CuePointer(); } @@ -67,7 +71,11 @@ CuePointer cueFromRow(const QSqlRecord& row) { lengthFrames, hotcue, label, - *color)); + *color, + stem1vol, + stem2vol, + stem3vol, + stem4vol)); return pCue; } @@ -158,22 +166,28 @@ bool CueDAO::saveCue(TrackId trackId, Cue* cue) const { if (cue->getId().isValid()) { // Update cue query.prepare(QStringLiteral("UPDATE " CUE_TABLE " SET " - "track_id=:track_id," - "type=:type," - "position=:position," - "length=:length," - "hotcue=:hotcue," - "label=:label," - "color=:color" - " WHERE id=:id")); + "track_id=:track_id," + "type=:type," + "position=:position," + "length=:length," + "hotcue=:hotcue," + "label=:label," + "color=:color," + "stem1vol=:stem1vol," + "stem2vol=:stem2vol," + "stem3vol=:stem3vol," + "stem4vol=:stem4vol" + " WHERE id=:id")); query.bindValue(":id", cue->getId().toVariant()); } else { // New cue query.prepare( QStringLiteral("INSERT INTO " CUE_TABLE " (track_id, type, position, length, hotcue, " - "label, color) VALUES (:track_id, :type, " - ":position, :length, :hotcue, :label, :color)")); + "label, color, stem1vol, stem2vol, stem3vol, " + "stem4vol) VALUES (:track_id, :type, " + ":position, :length, :hotcue, :label, :color, " + ":stem1vol, :stem2vol, :stem3vol, :stem4vol)")); } // Bind values and execute query @@ -183,7 +197,11 @@ bool CueDAO::saveCue(TrackId trackId, Cue* cue) const { query.bindValue(":length", cue->getLengthFrames() * mixxx::kEngineChannelOutputCount); query.bindValue(":hotcue", cue->getHotCue()); query.bindValue(":label", labelToQVariant(cue->getLabel())); - query.bindValue(":color", mixxx::RgbColor::toQVariant(cue->getColor())); + query.bindValue(":color", mixxx::RgbColor::toQVariant(cue->getColor())), + query.bindValue(":stem1vol", static_cast(cue->getStem1vol())), + query.bindValue(":stem2vol", static_cast(cue->getStem2vol())), + query.bindValue(":stem3vol", static_cast(cue->getStem3vol())), + query.bindValue(":stem4vol", static_cast(cue->getStem4vol())); if (!query.exec()) { LOG_FAILED_QUERY(query); return false; diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 1b8162c7fe9..0402ff638c1 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) { } @@ -726,6 +732,51 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack, // Update the PlayerInfo class that is used in EngineBroadcast to replace // the metadata of a stream PlayerInfo::instance().setTrackInfo(getGroup(), m_pLoadedTrack); + QString trackInfoArtist = " "; + QString trackInfoTitle = " "; + QString DeckStatusTxtLine2 = " "; + QString DeckStatusTxtLine3 = " "; + QString DeckStatusTxtLine4 = " "; + QTime tempStatusTime = QTime::currentTime(); + QString DeckStatusTime = tempStatusTime.toString("hh:mm:ss"); + + if (pNewTrack) { + // QString trackInfo = pNewTrack->getInfo(); + trackInfoArtist = pNewTrack->getArtist(); + trackInfoTitle = pNewTrack->getTitle(); + trackInfoArtist.replace("\"", "''"); + trackInfoTitle.replace("\"", "''"); + DeckStatusTxtLine2 = "Artist : \"" + trackInfoArtist + "\","; + DeckStatusTxtLine3 = "Title : \"" + trackInfoTitle + "\","; + DeckStatusTxtLine4 = "Time : \"" + DeckStatusTime + "\","; + + } else { + DeckStatusTxtLine2 = "Artist : \" \","; + DeckStatusTxtLine3 = "Title : \" \","; + DeckStatusTxtLine4 = "Time : \"" + DeckStatusTime + "\","; + } + QString trackInfoDeck = getGroup(); + trackInfoDeck.replace("[Channel", ""); + trackInfoDeck.replace("]", ""); + QString DeckStatusFilePath = m_pConfig->getSettingsPath(); + DeckStatusFilePath.replace("Roaming", "Local"); + DeckStatusFilePath.replace("\\", "/"); + QString DeckStatusFileLocation = + DeckStatusFilePath + "/controllers/Status" + getGroup() + ".js"; + // Different file for each Deck / Sampler + QString DeckStatusTxtLine1 = "var TrackDeck" + trackInfoDeck + " = { "; + QString DeckStatusTxtLine5 = "};"; + QFile DeckStatusFile(DeckStatusFileLocation); + DeckStatusFile.remove(); + DeckStatusFile.open(QIODevice::ReadWrite | QIODevice::Append); + // DeckStatusFile.open(QIODevice::ReadWrite | QIODevice::Append); + QTextStream DeckStatusTxt(&DeckStatusFile); + DeckStatusTxt << DeckStatusTxtLine1 << "\n"; + DeckStatusTxt << DeckStatusTxtLine2 << "\n"; + DeckStatusTxt << DeckStatusTxtLine3 << "\n"; + DeckStatusTxt << DeckStatusTxtLine4 << "\n"; + DeckStatusTxt << DeckStatusTxtLine5 << "\n"; + DeckStatusFile.close(); } TrackPointer BaseTrackPlayerImpl::getLoadedTrack() const { @@ -924,6 +975,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 end } EngineDeck* BaseTrackPlayerImpl::getEngineDeck() const { diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp index c5b44237bf9..df3ef08f225 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 @@ -763,6 +771,19 @@ void MixxxMainWindow::slotUpdateWindowTitle(TrackPointer pTrack) { QString trackInfo = pTrack->getInfo(); if (!trackInfo.isEmpty()) { appTitle = QString("%1 | %2").arg(trackInfo, appTitle); + // writing the artist & title of the playing track + // not only to the windowtitle but also to a file + // location and name for nowplayingfile + QString StatusNowPlayingFilePath = m_pCoreServices->getSettings()->getSettingsPath(); + QString StatusNowPlayingFileLocation = StatusNowPlayingFilePath + "/NowPlaying.txt"; + QFile StatusNowPlayingFile(StatusNowPlayingFileLocation); + // remove previous nowplayingfile + StatusNowPlayingFile.remove(); + StatusNowPlayingFile.open(QIODevice::ReadWrite); + QTextStream StatusNowPlayingTxt(&StatusNowPlayingFile); + // write Artist - Trackname to nowplayingfile + StatusNowPlayingTxt << QString("%1").arg(trackInfo) << "\n"; + StatusNowPlayingFile.close(); } filePath = pTrack->getLocation(); } @@ -1445,3 +1466,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..323e40181b6 --- /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..4057f0fcf0e --- /dev/null +++ b/src/osc/oscreceiver.h @@ -0,0 +1,42 @@ +#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 sendOscSyncTriggers(UserSettingsPointer m_pConfig); +// void processOscMessage(UserSettingsPointer m_pConfig, float oscInVal, +// const QString& oscInAddress) { void +// OscGetParameterRequest(UserSettingsPointer m_pConfig, const QString& +// oscGroup, const QString& oscKey, float oscValue); void +// OscGetValueRequest(UserSettingsPointer m_pConfig, const QString& oscGroup, +// const QString& oscKey, float oscValue); void +// OscSetRequest(UserSettingsPointer m_pConfig, const QString& oscGroup, +// const QString& oscKey, float oscValue); +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 + + + + diff --git a/src/track/cue.cpp b/src/track/cue.cpp index 28fe9cca19c..38ab76d53d3 100644 --- a/src/track/cue.cpp +++ b/src/track/cue.cpp @@ -50,14 +50,22 @@ Cue::Cue( mixxx::audio::FrameDiff_t length, int hotCue, const QString& label, - mixxx::RgbColor color) + mixxx::RgbColor color, + int stem1vol, + int stem2vol, + int stem3vol, + int stem4vol) : m_bDirty(false), // clear flag after loading from database m_dbId(id), m_type(type), m_startPosition(position), m_iHotCue(hotCue), m_label(label), - m_color(color) { + m_color(color), + m_stem1vol(stem1vol), + m_stem2vol(stem2vol), + m_stem3vol(stem3vol), + m_stem4vol(stem4vol) { DEBUG_ASSERT(m_dbId.isValid()); if (length != 0) { if (position.isValid()) { @@ -84,7 +92,11 @@ Cue::Cue( sampleRate)), m_iHotCue(cueInfo.getHotCueIndex().value_or(kNoHotCue)), m_label(cueInfo.getLabel()), - m_color(cueInfo.getColor().value_or(mixxx::PredefinedColorPalettes::kDefaultCueColor)) { + m_color(cueInfo.getColor().value_or(mixxx::PredefinedColorPalettes::kDefaultCueColor)), + m_stem1vol(cueInfo.getStem1vol().value_or(kNoHotCue)), + m_stem2vol(cueInfo.getStem2vol().value_or(kNoHotCue)), + m_stem3vol(cueInfo.getStem3vol().value_or(kNoHotCue)), + m_stem4vol(cueInfo.getStem4vol().value_or(kNoHotCue)) { DEBUG_ASSERT(!m_dbId.isValid()); } @@ -94,13 +106,21 @@ Cue::Cue( int hotCueIndex, mixxx::audio::FramePos startPosition, mixxx::audio::FramePos endPosition, - mixxx::RgbColor color) + mixxx::RgbColor color, + int stem1vol, + int stem2vol, + int stem3vol, + int stem4vol) : m_bDirty(true), // not yet in database, needs to be saved m_type(type), m_startPosition(startPosition), m_endPosition(endPosition), m_iHotCue(hotCueIndex), - m_color(color) { + m_color(color), + m_stem1vol(stem1vol), + m_stem2vol(stem2vol), + m_stem3vol(stem3vol), + m_stem4vol(stem4vol) { DEBUG_ASSERT(m_iHotCue == kNoHotCue || m_iHotCue >= mixxx::kFirstHotCueIndex); DEBUG_ASSERT(m_startPosition.isValid() || m_endPosition.isValid()); DEBUG_ASSERT(!m_dbId.isValid()); @@ -115,7 +135,11 @@ mixxx::CueInfo Cue::getCueInfo( positionFramesToMillis(m_endPosition, sampleRate), m_iHotCue == kNoHotCue ? std::nullopt : std::make_optional(m_iHotCue), m_label, - m_color); + m_color, + m_stem1vol == kNoHotCue ? std::nullopt : std::make_optional(m_stem1vol), + m_stem2vol == kNoHotCue ? std::nullopt : std::make_optional(m_stem2vol), + m_stem3vol == kNoHotCue ? std::nullopt : std::make_optional(m_stem3vol), + m_stem4vol == kNoHotCue ? std::nullopt : std::make_optional(m_stem4vol)); } DbId Cue::getId() const { @@ -226,6 +250,70 @@ int Cue::getHotCue() const { return m_iHotCue; } +int Cue::getStem1vol() const { + const auto lock = lockMutex(&m_mutex); + return m_stem1vol; +} + +int Cue::getStem2vol() const { + const auto lock = lockMutex(&m_mutex); + return m_stem2vol; +} + +int Cue::getStem3vol() const { + const auto lock = lockMutex(&m_mutex); + return m_stem3vol; +} + +int Cue::getStem4vol() const { + const auto lock = lockMutex(&m_mutex); + return m_stem4vol; +} + +void Cue::setStem1vol(int stem1vol) { + auto lock = lockMutex(&m_mutex); + if (m_stem1vol == stem1vol) { + return; + } + // m_stem1vol = stem1vol; + m_bDirty = true; + lock.unlock(); + emit updated(); +} + +void Cue::setStem2vol(int stem2vol) { + auto lock = lockMutex(&m_mutex); + if (m_stem2vol == stem2vol) { + return; + } + // m_stem2vol = stem2vol; + m_bDirty = true; + lock.unlock(); + emit updated(); +} + +void Cue::setStem3vol(int stem3vol) { + auto lock = lockMutex(&m_mutex); + if (m_stem3vol == stem3vol) { + return; + } + // m_stem3vol = stem3vol; + m_bDirty = true; + lock.unlock(); + emit updated(); +} + +void Cue::setStem4vol(int stem4vol) { + auto lock = lockMutex(&m_mutex); + if (m_stem4vol == stem4vol) { + return; + } + // m_stem4vol = stem4vol; + m_bDirty = true; + lock.unlock(); + emit updated(); +} + QString Cue::getLabel() const { const auto lock = lockMutex(&m_mutex); return m_label; diff --git a/src/track/cue.h b/src/track/cue.h index c0474e76ae9..9c81b011ed6 100644 --- a/src/track/cue.h +++ b/src/track/cue.h @@ -46,7 +46,11 @@ class Cue : public QObject { mixxx::audio::FrameDiff_t length, int hotCue, const QString& label, - mixxx::RgbColor color); + mixxx::RgbColor color, + int stem1vol, + int stem2vol, + int stem3vol, + int stem4vol); /// Initialize new cue points Cue( @@ -54,7 +58,11 @@ class Cue : public QObject { int hotCueIndex, mixxx::audio::FramePos startPosition, mixxx::audio::FramePos endPosition, - mixxx::RgbColor color); + mixxx::RgbColor color, + int stem1vol, + int stem2vol, + int stem3vol, + int stem4vol); ~Cue() override = default; @@ -75,9 +83,17 @@ class Cue : public QObject { mixxx::audio::FrameDiff_t getLengthFrames() const; int getHotCue() const; + int getStem1vol() const; + int getStem2vol() const; + int getStem3vol() const; + int getStem4vol() const; QString getLabel() const; void setLabel(const QString& label); + void setStem1vol(int stem1vol); + void setStem2vol(int stem2vol); + void setStem3vol(int stem3vol); + void setStem4vol(int stem4vol); mixxx::RgbColor getColor() const; void setColor(mixxx::RgbColor color); @@ -107,6 +123,10 @@ class Cue : public QObject { const int m_iHotCue; QString m_label; mixxx::RgbColor m_color; + const int m_stem1vol; + const int m_stem2vol; + const int m_stem3vol; + const int m_stem4vol; friend class Track; friend class CueDAO; diff --git a/src/track/cueinfo.cpp b/src/track/cueinfo.cpp index 20d7ea11a0b..c5fa8bd5781 100644 --- a/src/track/cueinfo.cpp +++ b/src/track/cueinfo.cpp @@ -38,6 +38,10 @@ CueInfo::CueInfo() m_endPositionMillis(std::nullopt), m_hotCueIndex(std::nullopt), m_color(std::nullopt), + m_stem1vol(std::nullopt), + m_stem2vol(std::nullopt), + m_stem3vol(std::nullopt), + m_stem4vol(std::nullopt), m_flags(CueFlag::None) { } @@ -48,6 +52,10 @@ CueInfo::CueInfo( const std::optional& hotCueIndex, QString label, const mixxx::RgbColor::optional_t& color, + const std::optional& stem1vol, + const std::optional& stem2vol, + const std::optional& stem3vol, + const std::optional& stem4vol, CueFlags flags) : m_type(type), m_startPositionMillis(startPositionMillis), @@ -55,6 +63,10 @@ CueInfo::CueInfo( m_hotCueIndex(hotCueIndex), m_label(std::move(label)), m_color(color), + m_stem1vol(stem1vol), + m_stem2vol(stem2vol), + m_stem3vol(stem3vol), + m_stem4vol(stem4vol), m_flags(flags) { assertEndPosition(type, endPositionMillis); } @@ -92,6 +104,38 @@ void CueInfo::setHotCueIndex(int hotCueIndex) { m_hotCueIndex = hotCueIndex; } +std::optional CueInfo::getStem1vol() const { + return m_stem1vol; +} + +std::optional CueInfo::getStem2vol() const { + return m_stem2vol; +} + +std::optional CueInfo::getStem3vol() const { + return m_stem3vol; +} + +std::optional CueInfo::getStem4vol() const { + return m_stem4vol; +} + +void CueInfo::setStem1vol(int stem1vol) { + m_stem1vol = stem1vol; +} + +void CueInfo::setStem2vol(int stem2vol) { + m_stem2vol = stem2vol; +} + +void CueInfo::setStem3vol(int stem3vol) { + m_stem3vol = stem3vol; +} + +void CueInfo::setStem4vol(int stem4vol) { + m_stem4vol = stem4vol; +} + QString CueInfo::getLabel() const { return m_label; } @@ -116,7 +160,11 @@ bool operator==( lhs.getEndPositionMillis() == rhs.getEndPositionMillis() && lhs.getHotCueIndex() == rhs.getHotCueIndex() && lhs.getLabel() == rhs.getLabel() && - lhs.getColor() == rhs.getColor(); + lhs.getColor() == rhs.getColor() && + lhs.getStem1vol() == rhs.getStem1vol() && + lhs.getStem2vol() == rhs.getStem2vol() && + lhs.getStem3vol() == rhs.getStem3vol() && + lhs.getStem4vol() == rhs.getStem4vol(); } QDebug operator<<(QDebug debug, const CueType& cueType) { @@ -161,6 +209,10 @@ QDebug operator<<(QDebug debug, const CueInfo& cueInfo) { << ", index=" << cueInfo.getHotCueIndex() << ", label=" << cueInfo.getLabel() << ", color=" << cueInfo.getColor() + << ", stem1vol=" << cueInfo.getStem1vol() + << ", stem2vol=" << cueInfo.getStem2vol() + << ", stem3vol=" << cueInfo.getStem3vol() + << ", stem4vol=" << cueInfo.getStem4vol() << ", flags=" << cueInfo.flags() << "]"; return debug; diff --git a/src/track/cueinfo.h b/src/track/cueinfo.h index c73eadcc1d0..456ca9c28f1 100644 --- a/src/track/cueinfo.h +++ b/src/track/cueinfo.h @@ -42,8 +42,11 @@ class CueInfo { const std::optional& hotCueIndex, QString label, const RgbColor::optional_t& color, + const std::optional& stem1vol, + const std::optional& stem2vol, + const std::optional& stem3vol, + const std::optional& stem4vol, CueFlags flags = CueFlag::None); - CueType getType() const; void setType(CueType type); @@ -58,6 +61,15 @@ class CueInfo { std::optional getHotCueIndex() const; void setHotCueIndex(int hotCueIndex); + std::optional getStem1vol() const; + std::optional getStem2vol() const; + std::optional getStem3vol() const; + std::optional getStem4vol() const; + void setStem1vol(int stem1vol); + void setStem2vol(int stem2vol); + void setStem3vol(int stem3vol); + void setStem4vol(int stem4vol); + QString getLabel() const; void setLabel( const QString& label = QString()); @@ -90,6 +102,10 @@ class CueInfo { std::optional m_hotCueIndex; QString m_label; RgbColor::optional_t m_color; + std::optional m_stem1vol; + std::optional m_stem2vol; + std::optional m_stem3vol; + std::optional m_stem4vol; CueFlags m_flags; }; diff --git a/src/track/serato/markers.cpp b/src/track/serato/markers.cpp index 8c12a24ec7c..88cc6b812d8 100644 --- a/src/track/serato/markers.cpp +++ b/src/track/serato/markers.cpp @@ -236,6 +236,10 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parseID3(const QByteArray& data) { hasEndPosition, endPosition, color, + 100, + 100, + 100, + 100, type, isLocked)); kLogger.trace() << "SeratoMarkersEntry (ID3)" << *pEntry; @@ -256,6 +260,10 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parseMP4(const QByteArray& data) { quint8 colorGreen; quint8 colorBlue; quint8 type; + int stem1vol; + int stem2vol; + int stem3vol; + int stem4vol; bool isLocked; QDataStream stream(data); @@ -315,6 +323,10 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parseMP4(const QByteArray& data) { type == static_cast(TypeId::Loop), endPosition, color, + stem1vol, + stem2vol, + stem3vol, + stem4vol, type, isLocked)); kLogger.trace() << "SeratoMarkersEntry (MP4)" << *pEntry; @@ -654,6 +666,10 @@ QList SeratoMarkers::getCues() const { cueIndex, QString(), pEntry->getColor().toDisplayedColor(), + 100, + 100, + 100, + 100, CueFlag::None); cueInfos.append(cueInfo); } @@ -676,6 +692,10 @@ QList SeratoMarkers::getCues() const { loopIndex, QString(), std::nullopt, + 100, + 100, + 100, + 100, pEntry->isLocked() ? CueFlag::Locked : CueFlag::None); cueInfos.append(loopInfo); // TODO: Add support for the "locked" attribute @@ -738,6 +758,10 @@ void SeratoMarkers::setCues(const QList& cueInfos) { false, 0, SeratoStoredHotcueColor::fromDisplayedColor(cueInfo.getColor()), + 100, + 100, + 100, + 100, static_cast(SeratoMarkersEntry::TypeId::Cue), false); } else { @@ -747,6 +771,10 @@ void SeratoMarkers::setCues(const QList& cueInfos) { false, 0, SeratoStoredHotcueColor(SeratoStoredColor::kFixedUnsetColor), + 100, + 100, + 100, + 100, static_cast(SeratoMarkersEntry::TypeId::Unknown), false); } @@ -768,6 +796,10 @@ void SeratoMarkers::setCues(const QList& cueInfos) { // import the blue-ish default color in the code above, but // it will not be used by Serato. SeratoStoredHotcueColor(SeratoStoredColor::kFixedLoopColor), + 100, + 100, + 100, + 100, static_cast(SeratoMarkersEntry::TypeId::Loop), cueInfo.isLocked()); } else { @@ -777,6 +809,10 @@ void SeratoMarkers::setCues(const QList& cueInfos) { false, 0, SeratoStoredHotcueColor(SeratoStoredColor::kFixedUnsetColor), + 100, + 100, + 100, + 100, // In contrast to cues, unset saved loop have the same type // ID as set ones. static_cast(SeratoMarkersEntry::TypeId::Loop), diff --git a/src/track/serato/markers.h b/src/track/serato/markers.h index dc7fae078ae..dc478630018 100644 --- a/src/track/serato/markers.h +++ b/src/track/serato/markers.h @@ -35,6 +35,10 @@ class SeratoMarkersEntry { bool hasEndPosition, int endPosition, SeratoStoredHotcueColor color, + int stem1vol, + int stem2vol, + int stem3vol, + int stem4vol, quint8 type, bool isLocked) : m_color(color), @@ -100,10 +104,13 @@ class SeratoMarkersEntry { SeratoStoredHotcueColor m_color; bool m_hasStartPosition; bool m_hasEndPosition; - ; bool m_isLocked; quint32 m_startPosition; quint32 m_endPosition; + int stem1vol; + int stem2vol; + int stem3vol; + int stem4vol; quint8 m_type; }; diff --git a/src/track/serato/markers2.cpp b/src/track/serato/markers2.cpp index 554b8f3a382..378d54b9d5a 100644 --- a/src/track/serato/markers2.cpp +++ b/src/track/serato/markers2.cpp @@ -615,6 +615,10 @@ QList SeratoMarkers2::getCues() const { pCueEntry->getIndex(), pCueEntry->getLabel(), pCueEntry->getColor().toDisplayedColor(), + 100, + 100, + 100, + 100, CueFlag::None); cueInfos.append(cueInfo); } @@ -638,6 +642,10 @@ QList SeratoMarkers2::getCues() const { pLoopEntry->getIndex(), pLoopEntry->getLabel(), std::nullopt, // Serato's Loops don't have a color + 100, + 100, + 100, + 100, pLoopEntry->isLocked() ? CueFlag::Locked : CueFlag::None); // TODO: Add support for "locked" loops cueInfos.append(loopInfo); diff --git a/src/track/track.cpp b/src/track/track.cpp index 52986fb6701..094c8077131 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -955,7 +955,11 @@ void Track::setMainCuePosition(mixxx::audio::FramePos position) { Cue::kNoHotCue, position, mixxx::audio::kInvalidFramePos, - mixxx::PredefinedColorPalettes::kDefaultCueColor)); + mixxx::PredefinedColorPalettes::kDefaultCueColor, + 100, + 100, + 100, + 100)); // While this method could be called from any thread, // associated Cue objects should always live on the // same thread as their host, namely this->thread(). @@ -1008,7 +1012,11 @@ CuePointer Track::createAndAddCue( int hotCueIndex, mixxx::audio::FramePos startPosition, mixxx::audio::FramePos endPosition, - mixxx::RgbColor color) { + mixxx::RgbColor color, + int stem1vol, + int stem2vol, + int stem3vol, + int stem4vol) { VERIFY_OR_DEBUG_ASSERT(hotCueIndex == Cue::kNoHotCue || hotCueIndex >= mixxx::kFirstHotCueIndex) { return CuePointer{}; @@ -1021,7 +1029,11 @@ CuePointer Track::createAndAddCue( hotCueIndex, startPosition, endPosition, - color)); + color, + stem1vol, + stem2vol, + stem3vol, + stem4vol)); // While this method could be called from any thread, // associated Cue objects should always live on the // same thread as their host, namely this->thread(). diff --git a/src/track/track.h b/src/track/track.h index f053b6b2166..975d314b063 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -305,20 +305,32 @@ class Track : public QObject { int hotCueIndex, mixxx::audio::FramePos startPosition, mixxx::audio::FramePos endPosition, - mixxx::RgbColor color = mixxx::PredefinedColorPalettes::kDefaultCueColor); + mixxx::RgbColor color = mixxx::PredefinedColorPalettes::kDefaultCueColor, + int stem1vol = 100, + int stem2vol = 100, + int stem3vol = 100, + int stem4vol = 100); CuePointer createAndAddCue( mixxx::CueType type, int hotCueIndex, double startPositionSamples, double endPositionSamples, - mixxx::RgbColor color = mixxx::PredefinedColorPalettes::kDefaultCueColor) { + mixxx::RgbColor color = mixxx::PredefinedColorPalettes::kDefaultCueColor, + int stem1vol = 100, + int stem2vol = 100, + int stem3vol = 100, + int stem4vol = 100) { return createAndAddCue(type, hotCueIndex, mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( startPositionSamples), mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid( endPositionSamples), - color); + color, + stem1vol, + stem2vol, + stem3vol, + stem4vol); } CuePointer findCueByType(mixxx::CueType type) const; // NOTE: Cannot be used for hotcues. CuePointer findCueById(DbId id) const;