diff --git a/CMakeLists.txt b/CMakeLists.txt index cbe8e8e7..e0df9d7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,9 +36,9 @@ endif() # `project()` command. `project()` sets up some helpful variables that describe source/binary # directories, and the current project version. This is a standard CMake command. -project(SonoBus VERSION 1.6.1) +project(SonoBus VERSION 1.9.0) -set(BUILDVERSION 76) +set(BUILDVERSION 174) # If you've installed JUCE somehow (via a package manager, or directly using the CMake install @@ -69,11 +69,35 @@ set_property(GLOBAL PROPERTY USE_FOLDERS YES) # Bug in Xcode? https://gitlab.kitware.com/cmake/cmake/-/issues/21383 set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Targets") -# Create a /Modules directory in the IDE with the JUCE Module code -option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Show all module sources in IDE projects" ON) + + +# include AOO (which includes opus) +set(AOO_BUILD_SHARED_LIBRARY OFF CACHE INTERNAL "" FORCE) +set(AOO_BUILD_PD_EXTERNAL OFF CACHE INTERNAL "" FORCE) +set(AOO_BUILD_SC_EXTENSION OFF CACHE INTERNAL "" FORCE) +set(AOO_BUILD_TEST_SUITE OFF CACHE INTERNAL "" FORCE) +set(AOO_CODEC_OPUS ON CACHE INTERNAL "" FORCE) +set(AOO_SYSTEM_OPUS ON CACHE INTERNAL "" FORCE) + +if (APPLE) + add_definitions(-DAOO_OPUS_MULTISTREAM_H="../../../../mac/include/opus/opus_multistream.h") +elseif (WIN32) + add_definitions(-DAOO_OPUS_MULTISTREAM_H="../../../../windows/include/opus/opus_multistream.h") +endif() + +add_subdirectory(deps/aoo EXCLUDE_FROM_ALL ) + +# need to get these out of the AOO build so we can add them to our our own build defs +get_directory_property(AOODIR_COMPILE_DEFS DIRECTORY deps/aoo COMPILE_DEFINITIONS) + +#message(STATUS "AOODIR_DEFS: ${AOODIR_COMPILE_DEFS}") + # include JUCE +# Create a /Modules directory in the IDE with the JUCE Module code +option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Show all module sources in IDE projects" ON) + add_subdirectory(deps/juce EXCLUDE_FROM_ALL ) @@ -180,7 +204,8 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr set (HEADER_INCLUDES - deps/aoo/lib + deps/aoo + deps/aoo/include deps/aoo/deps ) @@ -325,6 +350,8 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr Source/mtdm.h Source/zitaRev.h ) + +#[[ set(AOOSourceFiles deps/aoo/lib/src/SLIP.hpp deps/aoo/lib/src/client.cpp @@ -371,10 +398,11 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr deps/aoo/deps/oscpack/osc/OscReceivedElements.h deps/aoo/deps/oscpack/osc/OscTypes.h ) - +]] + target_sources("${target_name}" PRIVATE ${SourceFiles} - ${AOOSourceFiles} + # ${AOOSourceFiles} ) # No, we don't want our source buried in extra nested folders @@ -382,7 +410,8 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr # The source tree should uhhh, still look like the source tree, yo source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Source PREFIX "" FILES ${SourceFiles}) - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/deps/aoo PREFIX "aoo" FILES ${AOOSourceFiles}) + + #source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/deps/aoo PREFIX "aoo" FILES ${AOOSourceFiles}) # Move the INTERFACE auto-created JUCE library stuff into its own folder source_group("JUCE Library Code" REGULAR_EXPRESSION "juce_") @@ -445,7 +474,9 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr JUCE_LOAD_CURL_SYMBOLS_LAZILY=1 FF_AUDIO_ALLOW_ALLOCATIONS_IN_MEASURE_BLOCK=0 SONOBUS_BUILD_VERSION="${VERSION}" - ${PLAT_COMPILE_DEFS} ) + ${PLAT_COMPILE_DEFS} + ${AOODIR_COMPILE_DEFS} + ) juce_add_binary_data("${target_name}_SBData" SOURCES Source/wordmaker.g @@ -594,14 +625,14 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr ff_meters ${target_name}_SBData - + AOO_STATIC opus PUBLIC juce::juce_recommended_config_flags juce::juce_recommended_lto_flags # juce::juce_recommended_warning_flags ) - + endfunction() # most of the targets diff --git a/Source/ConnectView.cpp b/Source/ConnectView.cpp index 89d3e0e4..8327b56b 100644 --- a/Source/ConnectView.cpp +++ b/Source/ConnectView.cpp @@ -1002,7 +1002,7 @@ void ConnectView::buttonClicked (Button* buttonThatWasClicked) } if (host.isNotEmpty() && port != 0) { - if (processor.connectRemotePeer(host, port, "", "", processor.getValueTreeState().getParameter(SonobusAudioProcessor::paramMainRecvMute)->getValue() == 0)) { + if (processor.connectRemotePeer(host, port, kAooIdInvalid, "", "", kAooIdInvalid, processor.getValueTreeState().getParameter(SonobusAudioProcessor::paramMainRecvMute)->getValue() == 0)) { setVisible(false); if (auto * callout = dynamic_cast(directConnectCalloutBox.get())) { callout->dismiss(); @@ -1744,7 +1744,9 @@ void ConnectView::PublicGroupsListModel::groupSelected(int rowNumber) parent->processor.leaveServerGroup(parent->processor.getCurrentJoinedGroup()); - parent->processor.joinServerGroup(parent->currConnectionInfo.groupName, parent->currConnectionInfo.groupPassword, isPublic); + parent->processor.joinServerGroup(parent->currConnectionInfo.groupName, parent->currConnectionInfo.groupPassword, + parent->currConnectionInfo.userName, parent->currConnectionInfo.userPassword, + isPublic); parent->processor.setWatchPublicGroups(false); } diff --git a/Source/PeersContainerView.cpp b/Source/PeersContainerView.cpp index 18f67050..659b2198 100644 --- a/Source/PeersContainerView.cpp +++ b/Source/PeersContainerView.cpp @@ -732,13 +732,18 @@ void PeersContainerView::resetPendingUsers() mPendingUsers.clear(); } -void PeersContainerView::peerPendingJoin(String & group, String & user) +void PeersContainerView::peerPendingJoin(String & group, String & user, AooId groupId, AooId userId) { - mPendingUsers[user] = PendingUserInfo(group, user); + auto pui = PendingUserInfo(group, user); + pui.groupId = groupId; + pui.userId = userId; + + mPendingUsers[user] = pui; + rebuildPeerViews(); } -void PeersContainerView::peerFailedJoin(String & group, String & user) +void PeersContainerView::peerFailedJoin(String & group, String & user, AooId groupId, AooId userId) { auto found = mPendingUsers.find(user); if (found != mPendingUsers.end()) { @@ -747,19 +752,21 @@ void PeersContainerView::peerFailedJoin(String & group, String & user) } } -void PeersContainerView::peerBlockedJoin(String & group, String & user, String & address, int port) +void PeersContainerView::peerBlockedJoin(String & group, String & user, String & address, int port, AooId groupId, AooId userId) { auto found = mPendingUsers.find(user); if (found != mPendingUsers.end()) { found->second.failed = true; found->second.blocked = true; found->second.address = address; + found->second.userId = userId; + found->second.groupId = groupId; found->second.port = port; updatePeerViews(); } } -void PeersContainerView::peerLeftGroup(String & group, String & user) +void PeersContainerView::peerLeftGroup(String & group, String & user, AooId groupId, AooId userId) { // check if it was pending and remove it auto found = mPendingUsers.find(user); @@ -1610,7 +1617,6 @@ void PeersContainerView::updatePeerViews(int specific) bool recvactive = processor.getRemotePeerRecvActive(i); bool recvallow = processor.getRemotePeerRecvAllow(i); - bool latactive = processor.isRemotePeerLatencyTestActive(i); bool safetymuted = processor.getRemotePeerSafetyMuted(i); bool blocked = processor.getRemotePeerBlockedUs(i); @@ -1726,8 +1732,6 @@ void PeersContainerView::updatePeerViews(int specific) pvf->latencyLabel->setText(latlab, dontSendNotification); } - pvf->latActiveButton->setToggleState(latactive, dontSendNotification); - bool initCompleted = false; int autobufmode = (int)processor.getRemotePeerAutoresizeBufferMode(i, initCompleted); @@ -1771,21 +1775,6 @@ void PeersContainerView::updatePeerViews(int specific) pvf->addrLabel->setAlpha(connected ? 1.0 : 0.8); pvf->statusLabel->setAlpha(connected ? 1.0 : disalpha); - - if (latinfo.legacy) { - if (pvf->stopLatencyTestTimestampMs > 0.0 && nowstampms > pvf->stopLatencyTestTimestampMs - && !pvf->latActiveButton->isMouseButtonDown()) { - - // only stop if it has actually gotten a real latency - if (latinfo.isreal) { - stopLatencyTest(i); - - String messagestr = generateLatencyMessage(latinfo); - showPopTip(messagestr, 5000, pvf->latActiveButton.get(), 300); - - } - } - } } int i=0; @@ -1825,44 +1814,6 @@ void PeersContainerView::updatePeerViews(int specific) lastUpdateTimestampMs = nowstampms; } -void PeersContainerView::startLatencyTest(int di) -{ - if (di >= mPeerViews.size()) return; - - PeerViewInfo * pvf = mPeerViews.getUnchecked(di); - - int i = mPeerUpdateOrdering[di]; - - pvf->stopLatencyTestTimestampMs = Time::getMillisecondCounter(); // make it stop after the first one //+ 1500; - pvf->wasRecvActiveAtLatencyTest = processor.getRemotePeerRecvActive(i); - pvf->wasSendActiveAtLatencyTest = processor.getRemotePeerSendActive(i); - - pvf->latencyLabel->setText("****", dontSendNotification); - - processor.startRemotePeerLatencyTest(i); -} - -void PeersContainerView::stopLatencyTest(int di) -{ - if (di >= mPeerViews.size()) return; - PeerViewInfo * pvf = mPeerViews.getUnchecked(di); - - int i = mPeerUpdateOrdering[di]; - - processor.stopRemotePeerLatencyTest(i); - - pvf->stopLatencyTestTimestampMs = 0; - - SonobusAudioProcessor::LatencyInfo latinfo; - processor.getRemotePeerLatencyInfo(i, latinfo); - - if (latinfo.legacy && !latinfo.isreal) { - pvf->latencyLabel->setText(TRANS("PRESS"), dontSendNotification); - } else { - //pvf->latencyLabel->setText(String::formatted("%d ms", (int)lrintf(latinfo.totalRoundtripMs)) + (latinfo.estimated ? "*" : "") , dontSendNotification); - updatePeerViews(i); - } -} String PeersContainerView::generateLatencyMessage(const SonobusAudioProcessor::LatencyInfo &latinfo) { @@ -2006,24 +1957,11 @@ void PeersContainerView::buttonClicked (Button* buttonThatWasClicked) SonobusAudioProcessor::LatencyInfo latinfo; processor.getRemotePeerLatencyInfo(i, latinfo); - if (latinfo.legacy) { - pvf->latActiveButton->setToggleState(!pvf->latActiveButton->getToggleState(), dontSendNotification); - if (pvf->latActiveButton->getToggleState()) { - startLatencyTest(di); - //showPopTip(TRANS("Measuring actual round-trip latency"), 4000, pvf->latActiveButton.get(), 140); - } else { - stopLatencyTest(di); - } - } - else { - String messagestr = generateLatencyMessage(latinfo); - - showPopTip(messagestr, 8000, pvf->latActiveButton.get(), 300); - pvf->latActiveButton->setToggleState(false, dontSendNotification); - - + String messagestr = generateLatencyMessage(latinfo); + + showPopTip(messagestr, 8000, pvf->latActiveButton.get(), 300); + pvf->latActiveButton->setToggleState(false, dontSendNotification); - } return; } @@ -2116,7 +2054,7 @@ void PeersContainerView::buttonClicked (Button* buttonThatWasClicked) else if (ppvf->unblockButton.get() == buttonThatWasClicked) { processor.removeBlockedAddress(pinfo.second.address); - processor.connectRemotePeer(pinfo.second.address, pinfo.second.port, pinfo.second.user, pinfo.second.group); + processor.connectRemotePeer(pinfo.second.address, pinfo.second.port, pinfo.second.userId, pinfo.second.user, pinfo.second.group, pinfo.second.groupId); mPendingUsers.erase(pinfo.first); rebuildPeerViews(); diff --git a/Source/PeersContainerView.h b/Source/PeersContainerView.h index 08017a91..b281e8d7 100644 --- a/Source/PeersContainerView.h +++ b/Source/PeersContainerView.h @@ -211,10 +211,10 @@ public MultiTimer int getPeerViewCount() const { return mPeerViews.size(); } void resetPendingUsers(); - void peerPendingJoin(String & group, String & user); - void peerFailedJoin(String & group, String & user); - void peerBlockedJoin(String & group, String & user, String & address, int port); - void peerLeftGroup(String & group, String & user); + void peerPendingJoin(String & group, String & user, AooId groupId, AooId userId); + void peerFailedJoin(String & group, String & user, AooId groupId, AooId userId); + void peerBlockedJoin(String & group, String & user, String & address, int port, AooId groupId, AooId userId); + void peerLeftGroup(String & group, String & user, AooId groupId, AooId userId); int getPendingPeerCount() const { return (int)mPendingUsers.size(); } void rebuildPeerViews(); @@ -247,9 +247,6 @@ public MultiTimer protected: - void startLatencyTest(int i); - void stopLatencyTest(int i); - void configLevelSlider(Slider * slider); void configLabel(Label *label, int ltype); void configKnobSlider(Slider * slider); @@ -286,6 +283,8 @@ public MultiTimer PendingUserInfo(const String & group_="", const String & user_="") : group(group_), user(user_) {} String group; String user; + AooId groupId = kAooIdInvalid; + AooId userId = kAooIdInvalid; bool failed = false; bool blocked = false; String address; diff --git a/Source/SonoStandaloneFilterApp.cpp b/Source/SonoStandaloneFilterApp.cpp index 97d8ada4..1166857c 100644 --- a/Source/SonoStandaloneFilterApp.cpp +++ b/Source/SonoStandaloneFilterApp.cpp @@ -558,7 +558,9 @@ class SonobusStandaloneFilterApp : public JUCEApplication, public Timer sonoproc->setWatchPublicGroups(false); - sonoproc->joinServerGroup(cmdlineConnInfo.groupName, cmdlineConnInfo.groupPassword, cmdlineConnInfo.groupIsPublic); + sonoproc->joinServerGroup(cmdlineConnInfo.groupName, cmdlineConnInfo.groupPassword, + cmdlineConnInfo.userName, cmdlineConnInfo.userPassword, + cmdlineConnInfo.groupIsPublic); } } diff --git a/Source/SonoStandaloneFilterWindow.h b/Source/SonoStandaloneFilterWindow.h index 5beaef32..249fd5c1 100644 --- a/Source/SonoStandaloneFilterWindow.h +++ b/Source/SonoStandaloneFilterWindow.h @@ -562,7 +562,7 @@ class StandalonePluginHolder : private AudioIODeviceCallback, String lastRecentsSetupFolder; std::unique_ptr options; - StringArray lastMidiDevices; + Array lastMidiDevices; private: @@ -777,7 +777,7 @@ class StandalonePluginHolder : private AudioIODeviceCallback, const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions) { deviceManager.addAudioCallback (&maxSizeEnforcer); - deviceManager.addMidiInputCallback ({}, &player); + deviceManager.addMidiInputDeviceCallback ({}, &player); reloadAudioDeviceState (enableAudioInput, preferredDefaultDeviceName, preferredSetupOptions); } @@ -786,24 +786,24 @@ class StandalonePluginHolder : private AudioIODeviceCallback, { saveAudioDeviceState(); - deviceManager.removeMidiInputCallback ({}, &player); + deviceManager.removeMidiInputDeviceCallback ({}, &player); deviceManager.removeAudioCallback (&maxSizeEnforcer); } void timerCallback() override { - auto newMidiDevices = MidiInput::getDevices(); + auto newMidiDevices = MidiInput::getAvailableDevices(); if (newMidiDevices != lastMidiDevices) { for (auto& oldDevice : lastMidiDevices) if (! newMidiDevices.contains (oldDevice)) - deviceManager.setMidiInputEnabled (oldDevice, false); + deviceManager.setMidiInputDeviceEnabled (oldDevice.identifier, false); for (auto& newDevice : newMidiDevices) if (! lastMidiDevices.contains (newDevice)) - deviceManager.setMidiInputEnabled (newDevice, true); + deviceManager.setMidiInputDeviceEnabled (newDevice.identifier, true); } } diff --git a/Source/SonobusPluginEditor.cpp b/Source/SonobusPluginEditor.cpp index d5837afd..784a791c 100644 --- a/Source/SonobusPluginEditor.cpp +++ b/Source/SonobusPluginEditor.cpp @@ -1607,53 +1607,68 @@ void SonobusAudioProcessorEditor::aooClientPublicGroupDeleted(SonobusAudioProces } -void SonobusAudioProcessorEditor::aooClientPeerJoined(SonobusAudioProcessor *comp, const String & group, const String & user) +void SonobusAudioProcessorEditor::aooClientPeerJoined(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) { DBG("Client peer '" << user << "' joined group '" << group << "'"); { - const ScopedLock sl (clientStateLock); - clientEvents.add(ClientEvent(ClientEvent::PeerJoinEvent, group, true, "", user)); + const ScopedLock sl (clientStateLock); + auto cev = ClientEvent(ClientEvent::PeerJoinEvent, group, true, "", user); + cev.groupId = groupId; + cev.userId = userId; + clientEvents.add(cev); } triggerAsyncUpdate(); } -void SonobusAudioProcessorEditor::aooClientPeerPendingJoin(SonobusAudioProcessor *comp, const String & group, const String & user) +void SonobusAudioProcessorEditor::aooClientPeerPendingJoin(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) { DBG("Client peer '" << user << "' pending join group '" << group << "'"); { - const ScopedLock sl (clientStateLock); - clientEvents.add(ClientEvent(ClientEvent::PeerPendingJoinEvent, group, true, "", user)); + const ScopedLock sl (clientStateLock); + auto cev = ClientEvent(ClientEvent::PeerPendingJoinEvent, group, true, "", user); + cev.groupId = groupId; + cev.userId = userId; + clientEvents.add(cev); } triggerAsyncUpdate(); } -void SonobusAudioProcessorEditor::aooClientPeerJoinFailed(SonobusAudioProcessor *comp, const String & group, const String & user) +void SonobusAudioProcessorEditor::aooClientPeerJoinFailed(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) { DBG("Client peer '" << user << "' FAILed to join group '" << group << "'"); { - const ScopedLock sl (clientStateLock); - clientEvents.add(ClientEvent(ClientEvent::PeerFailedJoinEvent, group, true, "", user)); + const ScopedLock sl (clientStateLock); + auto cev = ClientEvent(ClientEvent::PeerFailedJoinEvent, group, true, "", user); + cev.groupId = groupId; + cev.userId = userId; + clientEvents.add(cev); } triggerAsyncUpdate(); } -void SonobusAudioProcessorEditor::aooClientPeerJoinBlocked(SonobusAudioProcessor *comp, const String & group, const String & user, const String & address, int port) +void SonobusAudioProcessorEditor::aooClientPeerJoinBlocked(SonobusAudioProcessor *comp, const String & group, const String & user, const String & address, int port, AooId groupId, AooId userId) { DBG("Client peer '" << user << "' with address: " << address << " : " << port << " BLOCKED from joining group '" << group << "'"); { const ScopedLock sl (clientStateLock); - clientEvents.add(ClientEvent(ClientEvent::PeerBlockedJoinEvent, group, true, address, user, port)); + auto cev = ClientEvent(ClientEvent::PeerBlockedJoinEvent, group, true, address, user, port); + cev.groupId = groupId; + cev.userId = userId; + clientEvents.add(cev); } triggerAsyncUpdate(); } -void SonobusAudioProcessorEditor::aooClientPeerLeft(SonobusAudioProcessor *comp, const String & group, const String & user) +void SonobusAudioProcessorEditor::aooClientPeerLeft(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) { DBG("Client peer '" << user << "' left group '" << group << "'"); { - const ScopedLock sl (clientStateLock); - clientEvents.add(ClientEvent(ClientEvent::PeerLeaveEvent, group, true, "", user)); + const ScopedLock sl (clientStateLock); + auto cev = ClientEvent(ClientEvent::PeerLeaveEvent, group, true, "", user); + cev.groupId = groupId; + cev.userId = userId; + clientEvents.add(cev); } triggerAsyncUpdate(); @@ -3809,7 +3824,9 @@ void SonobusAudioProcessorEditor::handleAsyncUpdate() processor.setWatchPublicGroups(false); - processor.joinServerGroup(currConnectionInfo.groupName, currConnectionInfo.groupPassword, currConnectionInfo.groupIsPublic); + processor.joinServerGroup(currConnectionInfo.groupName, currConnectionInfo.groupPassword, + currConnectionInfo.userName, currConnectionInfo.userPassword, + currConnectionInfo.groupIsPublic); } else { // we've connected but have not specified group, assume we want to see public groups @@ -3938,19 +3955,19 @@ void SonobusAudioProcessorEditor::handleAsyncUpdate() mChatView->addNewChatMessage(SBChatEvent(SBChatEvent::SystemType, ev.group, ev.user, "", "", mesg)); } - mPeerContainer->peerLeftGroup(ev.group, ev.user); + mPeerContainer->peerLeftGroup(ev.group, ev.user, ev.groupId, ev.userId); updatePeerState(true); updateState(false); } else if (ev.type == ClientEvent::PeerPendingJoinEvent) { - mPeerContainer->peerPendingJoin(ev.group, ev.user); + mPeerContainer->peerPendingJoin(ev.group, ev.user, ev.groupId, ev.userId); } else if (ev.type == ClientEvent::PeerFailedJoinEvent) { - mPeerContainer->peerFailedJoin(ev.group, ev.user); + mPeerContainer->peerFailedJoin(ev.group, ev.user, ev.groupId, ev.userId); } else if (ev.type == ClientEvent::PeerBlockedJoinEvent) { - mPeerContainer->peerBlockedJoin(ev.group, ev.user, ev.message, (int) lrint(ev.floatVal)); + mPeerContainer->peerBlockedJoin(ev.group, ev.user, ev.message, (int) lrint(ev.floatVal), ev.groupId, ev.userId); } else if (ev.type == ClientEvent::PeerRequestedLatencyMatchEvent) { diff --git a/Source/SonobusPluginEditor.h b/Source/SonobusPluginEditor.h index 56cda39b..c6e98146 100644 --- a/Source/SonobusPluginEditor.h +++ b/Source/SonobusPluginEditor.h @@ -136,11 +136,11 @@ public PeersContainerView::Listener void aooClientGroupLeft(SonobusAudioProcessor *comp, bool success, const String & group, const String & errmesg="") override; void aooClientPublicGroupModified(SonobusAudioProcessor *comp, const String & group, int count, const String & errmesg="") override; void aooClientPublicGroupDeleted(SonobusAudioProcessor *comp, const String & group, const String & errmesg="") override; - void aooClientPeerJoined(SonobusAudioProcessor *comp, const String & group, const String & user) override; - void aooClientPeerPendingJoin(SonobusAudioProcessor *comp, const String & group, const String & user) override; - void aooClientPeerJoinFailed(SonobusAudioProcessor *comp, const String & group, const String & user) override; - void aooClientPeerJoinBlocked(SonobusAudioProcessor *comp, const String & group, const String & user, const String & address, int port) override; - void aooClientPeerLeft(SonobusAudioProcessor *comp, const String & group, const String & user) override; + void aooClientPeerJoined(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) override; + void aooClientPeerPendingJoin(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) override; + void aooClientPeerJoinFailed(SonobusAudioProcessor *comp, const String & group, const String & user,AooId groupId, AooId userId) override; + void aooClientPeerJoinBlocked(SonobusAudioProcessor *comp, const String & group, const String & user, const String & address, int port, AooId groupId, AooId userId) override; + void aooClientPeerLeft(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) override; void aooClientError(SonobusAudioProcessor *comp, const String & errmesg) override; void aooClientPeerChangedState(SonobusAudioProcessor *comp, const String & mesg) override; void sbChatEventReceived(SonobusAudioProcessor *comp, const SBChatEvent & mesg) override; @@ -468,6 +468,8 @@ public PeersContainerView::Listener String message; String user; String group; + AooId userId = kAooIdInvalid; + AooId groupId = kAooIdInvalid; float floatVal = 0.0f; }; Array clientEvents; diff --git a/Source/SonobusPluginProcessor.cpp b/Source/SonobusPluginProcessor.cpp index 5b08249a..0abcf608 100644 --- a/Source/SonobusPluginProcessor.cpp +++ b/Source/SonobusPluginProcessor.cpp @@ -8,10 +8,13 @@ #include "RunCumulantor.h" +#include "aoo/aoo.h" +#include "aoo/codec/aoo_pcm.h" +#include "aoo/codec/aoo_opus.h" -#include "aoo/aoo_net.h" -#include "aoo/aoo_pcm.h" -#include "aoo/aoo_opus.h" +//#include "aoo/aoo_net.hpp" +#include "common/net_utils.hpp" +#include "common/time.hpp" #include "oscpack/osc/OscOutboundPacketStream.h" #include "oscpack/osc/OscReceivedElements.h" @@ -19,6 +22,7 @@ #include "mtdm.h" #include +#include #include "LatencyMeasurer.h" #include "Metronome.h" @@ -41,6 +45,8 @@ typedef int socklen_t; #define SENDBUFSIZE_SCALAR 2.0f #define PEER_PING_INTERVAL_MS 2000.0 +#define LOCAL_SERVER_PORT 10999 + String SonobusAudioProcessor::paramInGain ("ingain"); String SonobusAudioProcessor::paramDry ("dry"); String SonobusAudioProcessor::paramInMonitorMonoPan ("inmonmonopan"); @@ -191,6 +197,7 @@ static addrinfo* getAddressInfo (bool isDatagram, const String& hostName, int po return nullptr; } +#if 0 struct SonobusAudioProcessor::EndpointState { EndpointState(String ipaddr_="", int port_=0) : ipaddr(ipaddr_), port(port_) { rawaddr.sa_family = AF_UNSPEC; @@ -226,7 +233,35 @@ struct SonobusAudioProcessor::EndpointState { struct sockaddr rawaddr; }; +#else +struct SonobusAudioProcessor::EndpointState { + EndpointState(aoo::ip_address addr) : address(addr) { + + ipaddr = address.name_unmapped(); + port = address.port(); + } + + int ownerSocket = -1; + + String ipaddr; + int port = 0; + + AooId groupid = kAooIdInvalid; + AooId userid = kAooIdInvalid; + + struct sockaddr * getRawAddr() { + return address.address_ptr(); + } + + + // runtime state + int64_t sentBytes = 0; + int64_t recvBytes = 0; + + aoo::ip_address address; +}; +#endif @@ -240,9 +275,16 @@ enum { RemoteNetTypeMobileData = 3 }; +struct ProcessorIdPair +{ + ProcessorIdPair(SonobusAudioProcessor *proc=nullptr, AooId id_=-1) : processor(proc), id(id_) {} + SonobusAudioProcessor * processor; + AooId id; +}; + struct SonobusAudioProcessor::RemotePeer { - RemotePeer(EndpointState * ep = 0, int id_=0, aoo::isink::pointer oursink_ = 0, aoo::isource::pointer oursource_ = 0) : endpoint(ep), + RemotePeer(EndpointState * ep = 0, int id_=0, AooSink::Ptr oursink_ = 0, AooSource::Ptr oursource_ = 0) : endpoint(ep), ourId(id_), oursink(std::move(oursink_)), oursource(std::move(oursource_)) { @@ -254,31 +296,20 @@ struct SonobusAudioProcessor::RemotePeer { recvStereoPan[i] = 1.0f; } } - - oursink.reset(aoo::isink::create(ourId)); - oursource.reset(aoo::isource::create(ourId)); - - // create latency sink/sources - latencysink.reset(aoo::isink::create(ourId + LATENCY_ID_OFFSET)); - latencysource.reset(aoo::isource::create(ourId + LATENCY_ID_OFFSET)); - echosink.reset(aoo::isink::create(ourId + ECHO_ID_OFFSET)); - echosource.reset(aoo::isource::create(ourId + ECHO_ID_OFFSET)); + + oursink = AooSink::create(ourId, nullptr); + oursource = AooSource::create(ourId, nullptr); } EndpointState * endpoint = 0; - int32_t ourId = AOO_ID_NONE; - int32_t remoteSinkId = AOO_ID_NONE; - int32_t remoteSourceId = AOO_ID_NONE; - aoo::isink::pointer oursink; - aoo::isource::pointer oursource; - - aoo::isink::pointer latencysink; - aoo::isource::pointer latencysource; - aoo::isink::pointer echosink; - aoo::isource::pointer echosource; - bool activeLatencyTest = false; - std::unique_ptr latencyProcessor; - std::unique_ptr latencyMeasurer; + int32_t ourId = kAooIdInvalid; + int32_t remoteSinkId = kAooIdInvalid; + int32_t remoteSourceId = kAooIdInvalid; + AooSink::Ptr oursink; + AooSource::Ptr oursource; + + ProcessorIdPair oursinkpp; + ProcessorIdPair oursourcepp; float gain = 1.0f; @@ -286,6 +317,7 @@ struct SonobusAudioProcessor::RemotePeer { float padBufferTimeMs = 0.0f; AutoNetBufferMode autosizeBufferMode = AutoNetBufferModeAutoFull; bool sendActive = false; + bool sendCommonActive = false; bool recvActive = false; bool sendAllow = true; bool recvAllow = true; @@ -308,6 +340,8 @@ struct SonobusAudioProcessor::RemotePeer { bool connected = false; String userName; String groupName; + AooId userId = kAooIdInvalid; + AooId groupId = kAooIdInvalid; int64_t dataPacketsReceived = 0; int64_t dataPacketsSent = 0; int64_t dataPacketsDropped = 0; @@ -362,23 +396,31 @@ struct SonobusAudioProcessor::RemotePeer { bool hasRemoteInfo = false; bool blockedUs = false; + struct PeerInfo { + int32_t flags = 0; + }; + PeerInfo aooPeerInfo; + std::unique_ptr fileWriter; + ReadWriteLock sinkLock; }; -static int32_t endpoint_send(void *e, const char *data, int32_t size) +static int32_t endpoint_send(void *e, const AooByte *data, int32_t size) { SonobusAudioProcessor::EndpointState * endpoint = static_cast(e); int result = -1; - if (endpoint->peer) { - result = endpoint->owner->write(*(endpoint->peer), data, size); - } else { - result = endpoint->owner->write(endpoint->ipaddr, endpoint->port, data, size); - } - + //if (endpoint->peer) { + // result = endpoint->owner->write(*(endpoint->peer), data, size); + //} else { + // result = endpoint->owner->write(endpoint->ipaddr, endpoint->port, data, size); + //} + + result = aoo::socket_sendto(endpoint->ownerSocket, data, size, endpoint->address); + if (result > 0) { // include UDP overhead endpoint->sentBytes += result + UDP_OVERHEAD_BYTES; @@ -389,26 +431,22 @@ static int32_t endpoint_send(void *e, const char *data, int32_t size) return result; } -static int32_t client_send(void *e, const char *data, int32_t size, void *raddr) +int32_t SonobusAudioProcessor::udpsend(void *user, const AooByte *msg, AooInt32 size, + const void *addr, AooAddrSize addrlen, AooFlag flags) { - SonobusAudioProcessor::EndpointState * endpoint = static_cast(e); - int result = -1; + auto x = (SonobusAudioProcessor *)user; + aoo::ip_address address((const sockaddr *)addr, addrlen); - const struct sockaddr *addr = (struct sockaddr *)raddr; - - if (addr->sa_family == AF_INET){ - result = (int) ::sendto(endpoint->owner->getRawSocketHandle(), data, (size_t)size, 0, addr, sizeof(struct sockaddr_in)); + // lookup endpoint + if (auto * es = x->findEndpoint(address)) { + return endpoint_send(es, msg, size); } - - if (result > 0) { - endpoint->sentBytes += result + UDP_OVERHEAD_BYTES; + else { + return aoo::socket_sendto(x->mUdpSocketHandle, msg, size, address); } - - return result; } - class SonobusAudioProcessor::SendThread : public juce::Thread { public: @@ -454,10 +492,8 @@ class SonobusAudioProcessor::RecvThread : public juce::Thread setPriority(Thread::Priority::highest); while (!threadShouldExit()) { - - if (_processor.mUdpSocket->waitUntilReady(true, 20) == 1) { - _processor.doReceiveData(); - } + + _processor.doReceiveData(); } DBG("Recv thread finishing"); @@ -497,9 +533,9 @@ class SonobusAudioProcessor::ServerThread : public juce::Thread void run() override { - if (_processor.mAooServer) { - _processor.mAooServer->run(); - } + //if (_processor.mAooServer) { + // _processor.mAooServer->run(); + //} DBG("Server thread finishing"); } @@ -517,7 +553,7 @@ class SonobusAudioProcessor::ClientThread : public juce::Thread void run() override { if (_processor.mAooClient) { - _processor.mAooClient->run(); + _processor.mAooClient->run(false); } DBG("Client thread finishing"); @@ -865,15 +901,14 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) // we have both an AOO source and sink - aoo_initialize(); + aoo_initialize(nullptr); const ScopedWriteLock sl (mCoreLock); //mAooSink.reset(aoo::isink::create(1)); - mAooDummySource.reset(aoo::isource::create(0)); - + mAooCommonSource = AooSource::create(0, nullptr); @@ -885,9 +920,13 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) - mUdpSocket = std::make_unique(); - mUdpSocket->setSendBufferSize(1048576); - mUdpSocket->setReceiveBufferSize(1048576); + //mUdpSocket = std::make_unique(); + //mUdpSocket->setSendBufferSize(1048576); + //mUdpSocket->setReceiveBufferSize(1048576); + + + + /* int tos_local = 0x38; // QOS realtime DSCP @@ -900,9 +939,14 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) if (udpport > 0) { int attempts = 100; while (attempts > 0) { - if (mUdpSocket->bindToPort(udpport)) { - udpport = mUdpSocket->getBoundPort(); + + int sock = aoo::socket_udp(udpport); + + //if (mUdpSocket->bindToPort(udpport)) { + // udpport = mUdpSocket->getBoundPort(); + if (sock >= 0) { DBG("Bound udp port to " << udpport); + mUdpSocketHandle = sock; break; } ++udpport; @@ -916,18 +960,44 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) } else { // system assigned - if (!mUdpSocket->bindToPort(0)) { + int sock = aoo::socket_udp(0); + + //if (!mUdpSocket->bindToPort(0)) { + if (sock < 0) { DBG("Error binding to any udp port!"); } else { - udpport = mUdpSocket->getBoundPort(); - DBG("Bound system chosen udp port to " << udpport); + //udpport = mUdpSocket->getBoundPort(); + mUdpSocketHandle = sock; } } - + if (mUdpSocketHandle >= 0) { + // increase socket buffers + const int sendbufsize = 1 << 19; // 512 Kb + const int recvbufsize = 1 << 20; // 1 MB + aoo::socket_set_sendbufsize(mUdpSocketHandle, sendbufsize); + aoo::socket_set_recvbufsize(mUdpSocketHandle, recvbufsize); + + aoo::ip_address addr; + if (aoo::socket_address(mUdpSocketHandle, addr) != 0){ + DBG("AooNode: couldn't get socket address"); + aoo::socket_close(mUdpSocketHandle); + mUdpSocketHandle = -1; + udpport = 0; + } + else { + udpport = addr.port(); + mLocalClientAddress = addr; + } + + } + mUdpLocalPort = udpport; + DBG("Bound udp port to " << mUdpLocalPort); + + //mLocalIPAddress = IPAddress::getLocalAddress(); #if JUCE_IOS @@ -959,11 +1029,20 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) } #endif - mServerEndpoint = std::make_unique(); - mServerEndpoint->owner = mUdpSocket.get(); + //mServerEndpoint = std::make_unique(); + //mServerEndpoint->owner = mUdpSocket.get(); - if (mUdpLocalPort > 0) { - mAooClient.reset(aoo::net::iclient::create(mServerEndpoint.get(), client_send, mUdpLocalPort)); + if (mUdpSocketHandle >= 0) { + + mAooClient = AooClient::create(nullptr); + auto flags = aoo::socket_family(mUdpSocketHandle) == aoo::ip_address::IPv6 ? + kAooSocketDualStack : kAooSocketIPv4; + mAooClient->setup(mUdpLocalPort, flags); + + mAooClient->setEventHandler( + [](void *user, const AooEvent *event, int32_t level) { + static_cast(user)->handleAooClientEvent(event, level); + }, this, kAooEventModeCallback); } @@ -973,6 +1052,16 @@ void SonobusAudioProcessor::initializeAoo(int udpPort) if (mAooClient) { mClientThread = std::make_unique(*this); + +#if 0 + mAooClient->addSource(mAooCommonSource.get(), mCurrentUserId); + + mAooCommonSource->setEventHandler( + [](void *user, const AooEvent *event, int32_t level){ + auto * pp = static_cast(user); + pp->handleAooSourceEvent(event, level, mCurrentUserId); + }, this, kAooEventModeCallback); +#endif } uint32_t estWorkDurationMs = 10; // just a guess @@ -1016,7 +1105,22 @@ void SonobusAudioProcessor::cleanupAoo() mEventThread->stopThread(400); if (mAooClient) { - mAooClient->disconnect(); + auto cb = [](void* x, const AooRequest *request, AooError result, + const AooResponse *response) { + auto obj = (SonobusAudioProcessor *)x; + if (result == kAooOk){ + DBG("Disconnected"); + } else { + auto reply = reinterpret_cast(response); + if (reply) { + DBG("Error disconnecting: " << reply->errorCode << " msg: " << reply->errorMessage); + } + } + + }; + + mAooClient->disconnect(cb, this); + mAooClient->quit(); mClientThread->stopThread(400); } @@ -1027,9 +1131,12 @@ void SonobusAudioProcessor::cleanupAoo() mAooClient.reset(); - mUdpSocket.reset(); + if (mUdpSocketHandle >= 0) { + aoo::socket_close(mUdpSocketHandle); + } + //mUdpSocket.reset(); - mAooDummySource.reset(); + mAooCommonSource.reset(); mRemotePeers.clear(); @@ -1045,30 +1152,51 @@ void SonobusAudioProcessor::startAooServer() { const ScopedWriteLock sl (mCoreLock); - int32_t err; - mAooServer.reset(aoo::net::iserver::create(10999, &err)); - - if (err != 0) { + AooError err; + + mAooServerWrapper = std::make_unique(*this, LOCAL_SERVER_PORT, String("")); +#if 0 + mAooServer = AooServer::create(LOCAL_SERVER_PORT, 0, &err); + + if (mAooServer) { + mAooServer->setEventHandler( + [](void *user, const AooEvent *event, int32_t level) { + static_cast(user)->handleAooServerEvent(event, level); + }, this, kAooEventModeCallback); + + } + else { DBG("Error creating Aoo Server: " << err); } +#endif } - + +#if 0 if (mAooServer) { mServerThread = std::make_unique(*this); mServerThread->startThread(); } +#endif } void SonobusAudioProcessor::stopAooServer() { + if (mAooServerWrapper) { + mAooServerWrapper.reset(); + } + +#if 0 if (mAooServer) { DBG("waiting on recv thread to die"); mAooServer->quit(); - mServerThread->stopThread(400); + if (mServerThread) { + mServerThread->stopThread(400); + } const ScopedWriteLock sl (mCoreLock); mAooServer.reset(); } +#endif } bool SonobusAudioProcessor::setCurrentUsername(const String & name) @@ -1079,7 +1207,6 @@ bool SonobusAudioProcessor::setCurrentUsername(const String & name) return true; } - bool SonobusAudioProcessor::connectToServer(const String & host, int port, const String & username, const String & passwd) { if (!mAooClient) return false; @@ -1090,19 +1217,83 @@ bool SonobusAudioProcessor::connectToServer(const String & host, int port, const } - mServerEndpoint->ipaddr = host; - mServerEndpoint->port = port; - mServerEndpoint->peer.reset(); + //mServerEndpoint->ipaddr = host; + //mServerEndpoint->port = port; + //mServerEndpoint->add.reset(); + + int token = 0; + + auto cb = [](void* x, const AooRequest *request, AooError result, + const AooResponse *response) { + auto obj = (SonobusAudioProcessor *)x; + + if (result == kAooOk) + { + auto resp = reinterpret_cast(response); + + auto client_id = resp->clientId; + + obj->mIsConnectedToServer = true; + obj->mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); + obj->mCurrentClientId = client_id; + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientConnected, obj, response->type != kAooRequestError, ""); + + } else { + auto reply = reinterpret_cast(response); + + obj->mIsConnectedToServer = false; + obj->mSessionConnectionStamp = 0.0; + obj->mCurrentClientId = kAooIdInvalid; + + DBG("Error connecting to server: " << reply->errorCode << " msg: " << reply->errorMessage); + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientConnected, obj, response->type != kAooRequestError, reply->errorMessage); + } + }; + + auto retval = mAooClient->connect(host.toRawUTF8(), port, passwd.toRawUTF8(), nullptr, cb, this); + +#if 0 + auto cb = [](void *x, AooError result, const void *data){ + auto obj = (SonobusAudioProcessor *)x; + //auto obj = request->obj; + //auto group = request->group; + //auto pwd = request->pwd; + + if (result == kAooOk){ + auto reply = (const AooNetReplyConnect *)data; + auto user_id = reply->userId; + + obj->mIsConnectedToServer = true; + obj->mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); + obj->mCurrentUserId = user_id; + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientConnected, obj, result == kAooOk, ""); + } else { + auto reply = (const AooNetReplyError *)data; + + obj->mIsConnectedToServer = false; + obj->mSessionConnectionStamp = 0.0; + obj->mCurrentUserId = kAooIdInvalid; + + DBG("Error connecting to server: " << reply->errorCode << " msg: " << reply->errorMessage); + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientConnected, obj, result == kAooOk, reply->errorMessage); + } + + }; + + int32_t retval = mAooClient->connect(host.toRawUTF8(), port, username.toRawUTF8(), passwd.toRawUTF8(), cb, this); +#endif mCurrentUsername = username; - int32_t retval = mAooClient->connect(host.toRawUTF8(), port, username.toRawUTF8(), passwd.toRawUTF8()); - - if (retval < 0) { + if (retval != kAooOk) { DBG("Error connecting to server: " << retval); } - - return retval >= 0; + + return retval == kAooOk; } bool SonobusAudioProcessor::isConnectedToServer() const @@ -1115,26 +1306,38 @@ bool SonobusAudioProcessor::isConnectedToServer() const bool SonobusAudioProcessor::disconnectFromServer() { if (!mAooClient) return false; - - mAooClient->disconnect(); - - // disconnect from everything else! - removeAllRemotePeers(); - { - const ScopedLock sl (mClientLock); + auto cb = [](void* x, const AooRequest *request, AooError result, + const AooResponse *response) { + auto obj = (SonobusAudioProcessor *)x; + if (result == kAooOk) + { - mIsConnectedToServer = false; - mSessionConnectionStamp = 0.0; + } else { + auto reply = reinterpret_cast(response); + DBG("Error disconnecting to server: " << reply->errorCode << " msg: " << reply->errorMessage); + } - mCurrentJoinedGroup.clear(); - } + // disconnect from everything else! + obj->removeAllRemotePeers(); - { - const ScopedLock sl (mPublicGroupsLock); + { + const ScopedLock sl (obj->mClientLock); - mPublicGroupInfos.clear(); - } + obj->mIsConnectedToServer = false; + obj->mSessionConnectionStamp = 0.0; + + obj->mCurrentJoinedGroup.clear(); + } + + { + const ScopedLock sl (obj->mPublicGroupsLock); + + obj->mPublicGroupInfos.clear(); + } + }; + + mAooClient->disconnect(cb, this); @@ -1209,8 +1412,9 @@ bool SonobusAudioProcessor::setWatchPublicGroups(bool flag) mWatchPublicGroups = flag; - int32_t retval = mAooClient->group_watch_public(flag); + JUCE_COMPILER_WARNING ("IMPLEMENT PUBLIC GROUPS"); + int32_t retval = 0; // mAooClient->group_watch_public(flag); const ScopedLock sl (mPublicGroupsLock); mPublicGroupInfos.clear(); @@ -1224,13 +1428,73 @@ bool SonobusAudioProcessor::setWatchPublicGroups(bool flag) } +struct GroupRequest { + SonobusAudioProcessor * obj; + String group; + bool ispublic; +}; + +bool SonobusAudioProcessor::setupCommonAooSource() +{ + const int32_t userid = mCurrentUserId; + + mAooCommonSource->setId(userid); + mAooClient->addSource(mAooCommonSource.get(), userid); + + mAooCommonSource->setEventHandler( + [](void *user, const AooEvent *event, int32_t level){ + auto * pp = static_cast(user); + AooId aid(kAooIdInvalid); + pp->mAooCommonSource->getId(aid); + pp->handleAooSourceEvent(event, level, aid); + }, this, kAooEventModeCallback); + +} -bool SonobusAudioProcessor::joinServerGroup(const String & group, const String & groupsecret, bool isPublic) +bool SonobusAudioProcessor::joinServerGroup(const String & group, const String & groupsecret, const String & username, const String & userpass, bool isPublic) { if (!mAooClient) return false; - int32_t retval = mAooClient->group_join(group.toRawUTF8(), groupsecret.toRawUTF8(), isPublic); - + auto cb = [](void* x, const AooRequest *request, AooError result, + const AooResponse* response) { + + auto grreq = (GroupRequest *)x; + auto obj = grreq->obj; + auto group = grreq->group; + std::string errmsg; + + if (result == kAooOk) { + DBG("Joined group - " << group); + auto r = (const AooResponseGroupJoin *)response; + const ScopedLock sl (obj->mClientLock); + obj->mCurrentJoinedGroup = group; //CharPointer_UTF8 (e->name); + obj->mCurrentJoinedGroupId = r->groupId; + // TODO grab more of the group info + obj->mCurrentUserId = r->userId; + + obj->setupCommonAooSource(); + + obj->mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); + } else { + //t_error_reply error { reply->error_code, reply->error_message }; + auto reply = reinterpret_cast(response); + errmsg = reply ? reply->errorMessage : ""; + DBG("Error joining group " << group << " : " << errmsg); + + } + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupJoined, obj, response->type != kAooRequestError, group, errmsg); + + delete grreq; + }; + + // need to add PUBLIC + auto retval = mAooClient->joinGroup(group.toRawUTF8(), groupsecret.toRawUTF8(), nullptr, + username.toRawUTF8(), userpass.toRawUTF8(), nullptr, nullptr, + cb, new GroupRequest { this, group, isPublic }); + + + if (retval < 0) { DBG("Error joining group " << group << " : " << retval); } @@ -1242,8 +1506,43 @@ bool SonobusAudioProcessor::leaveServerGroup(const String & group) { if (!mAooClient) return false; - int32_t retval = mAooClient->group_leave(group.toRawUTF8()); - + auto cb = [](void* x, const AooRequest *request, AooError result, + const AooResponse* response) { + auto grreq = (GroupRequest *)x; + auto obj = grreq->obj; + auto group = grreq->group; + std::string errmsg; + + if (result == kAooOk) { + DBG("Group leave - " << group); + + const ScopedLock sl (obj->mClientLock); + obj->mCurrentJoinedGroup.clear(); + obj->mCurrentJoinedGroupId = kAooIdInvalid; + obj->mCurrentUserId = kAooIdInvalid; + + obj->mAooClient->removeSource(obj->mAooCommonSource.get()); + + // assume they are all part of the group, XXX + obj->removeAllRemotePeers(); + + } else { + auto reply = reinterpret_cast(response); + errmsg = (reply ? reply->errorMessage : ""); + //t_error_reply error { reply->error_code, reply->error_message }; + DBG("Error leaving group " << group << " : " << errmsg); + + } + + obj->clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupLeft, obj, response->type != kAooRequestError, group, errmsg); + + delete grreq; + }; + + // for now we only connect to one group at a time, so just leave the current one + + int32_t retval = mAooClient->leaveGroup(mCurrentJoinedGroupId, cb, new GroupRequest { this, group, false }); + if (retval < 0) { DBG("Error leaving group " << group << " : " << retval); } @@ -1373,14 +1672,9 @@ void SonobusAudioProcessor::setRemotePeerAudioCodecFormat(int index, int formatI if (remote->oursource) { setupSourceFormat(remote, remote->oursource.get()); - remote->oursource->setup(getSampleRate(), currSamplesPerBlock, remote->sendChannels); + remote->oursource->setup(remote->sendChannels, getSampleRate(), currSamplesPerBlock, 0); //remote->oursource->setup(getSampleRate(), remote->packetsize , getTotalNumOutputChannels()); - setupSourceFormat(remote, remote->latencysource.get(), true); - remote->latencysource->setup(getSampleRate(), currSamplesPerBlock, 1); - setupSourceFormat(remote, remote->echosource.get(), true); - remote->echosource->setup(getSampleRate(), currSamplesPerBlock, 1); - remote->latencyDirty = true; } } @@ -1412,14 +1706,15 @@ bool SonobusAudioProcessor::setRequestRemotePeerSendAudioCodecFormat(int index, const ScopedReadLock sl (mCoreLock); auto remote = mRemotePeers.getUnchecked(index); - aoo_format_storage fmt; + AooFormatStorage fmt; if (formatIndex >= 0) { const AudioCodecFormatInfo & info = mAudioFormats.getReference(formatIndex); if (formatInfoToAooFormat(info, remote->recvChannels, fmt)) { - remote->oursink->request_source_codec_change(remote->endpoint, remote->remoteSourceId, fmt.header); + JUCE_COMPILER_WARNING ("IMPLEMENT SOURCE CODEC CHANGE"); + //remote->oursink->request_source_codec_change(remote->endpoint, remote->remoteSourceId, fmt.header); remote->reqRemoteSendFormatIndex = formatIndex; return true; @@ -1486,7 +1781,7 @@ void SonobusAudioProcessor::setRemotePeerSendPacketsize(int index, int psize) //remote->oursource->setup(getSampleRate(), remote->packetsize, getTotalNumInputChannels()); - remote->oursource->set_packetsize(remote->packetsize); + remote->oursource->setPacketSize(remote->packetsize); } } @@ -2149,45 +2444,69 @@ foleys::LevelMeterSource * SonobusAudioProcessor::getRemotePeerSendMeterSource(i -SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddRawEndpoint(void * rawaddr) +SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddRawEndpoint(const void * rawaddr, int addrlen) { - String ipaddr; - int port = 0 ; + aoo::ip_address addr((const struct sockaddr *)rawaddr, addrlen); - char hostip[INET6_ADDRSTRLEN]; - if (inet_ntop(AF_INET, get_in_addr((struct sockaddr *)rawaddr), hostip, sizeof(hostip)) == nullptr) { - DBG("Error converting raw addr to IP"); - return nullptr; - } else { - ipaddr = hostip; - port = ntohs(get_in_port((struct sockaddr *)rawaddr)); - return findOrAddEndpoint(ipaddr, port); - } + return findOrAddEndpoint(addr); } + SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddEndpoint(const String & host, int port) { - const ScopedLock sl (mEndpointsLock); - - EndpointState * endpoint = 0; - + aoo::ip_address addr(host.toStdString(), port, aoo::ip_address::Unspec); + + return findOrAddEndpoint(addr); +} + +SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddEndpoint(const aoo::ip_address & ipaddr) +{ + EndpointState * endpoint = findEndpoint(ipaddr); + + if (!endpoint) { + // add it as new + const ScopedLock sl (mEndpointsLock); + endpoint = mEndpoints.add(new EndpointState(ipaddr)); + endpoint->ownerSocket = mUdpSocketHandle; + DBG("Added new endpoint for " << ipaddr.name_unmapped() << ":" << ipaddr.port()); + } + return endpoint; +} + +SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findEndpoint(const aoo::ip_address & ipaddr) +{ + const ScopedLock sl (mEndpointsLock); + + EndpointState * endpoint = nullptr; + for (auto ep : mEndpoints) { - if (ep->ipaddr == host && ep->port == port) { + if (ep->address == ipaddr) { endpoint = ep; break; } } - - if (!endpoint) { - // add it as new - endpoint = mEndpoints.add(new EndpointState(host, port)); - endpoint->owner = mUdpSocket.get(); - endpoint->peer = std::make_unique(host, port); - DBG("Added new endpoint for " << host << ":" << port); + + return endpoint; +} + +SonobusAudioProcessor::EndpointState * SonobusAudioProcessor::findOrAddEndpoint(AooId groupid, AooId userid) +{ + const ScopedLock sl (mEndpointsLock); + + EndpointState * endpoint = nullptr; + if (groupid == kAooIdInvalid || userid == kAooIdInvalid) return nullptr; + + for (auto ep : mEndpoints) { + if (ep->groupid == groupid && ep->userid == userid) { + endpoint = ep; + break; + } } + return endpoint; } + void SonobusAudioProcessor::updateSafetyMuting(RemotePeer * peer) { // assumed corelock already held @@ -2222,151 +2541,65 @@ void SonobusAudioProcessor::updateSafetyMuting(RemotePeer * peer) void SonobusAudioProcessor::doReceiveData() { // receive from udp port, and parse packet - char buf[AOO_MAXPACKETSIZE]; - String senderIP; - int senderPort; - - int nbytes = mUdpSocket->read(buf, AOO_MAXPACKETSIZE, false, senderIP, senderPort); + AooByte buf[AOO_MAX_PACKET_SIZE]; + + //int nbytes = mUdpSocket->read(buf, AOO_MAXPACKETSIZE, false, senderIP, senderPort); - if (nbytes == 0) return; + aoo::ip_address addr; + int32_t addrlen = aoo::ip_address::max_length; + double timeoutsec = 0.02; // 20 ms + //int nbytes = mUdpSocket->read(buf, AOO_MAXPACKETSIZE, false, addr.address_ptr(), addrlen); + int nbytes = socket_receive(mUdpSocketHandle, buf, AOO_MAX_PACKET_SIZE, &addr, timeoutsec); + + if (nbytes == 0) return; // timeout else if (nbytes < 0) { DBG("Error receiving UDP"); return; } - + + //*addr.length_ptr() = addrlen; + // find endpoint from sender info - EndpointState * endpoint = findOrAddEndpoint(senderIP, senderPort); + EndpointState * endpoint = findOrAddEndpoint(addr); endpoint->recvBytes += nbytes + UDP_OVERHEAD_BYTES; - - // parse packet for AOO events - - int32_t type, id, dummyid; - if ((aoo_parse_pattern(buf, nbytes, &type, &id) > 0) - || (aoonet_parse_pattern(buf, nbytes, &type) > 0)) - { - { - - if (type == AOO_TYPE_SINK){ - // forward OSC packet to matching sink(s) - const ScopedReadLock sl (mCoreLock); - - for (auto & remote : mRemotePeers) { - if (!remote->oursink) continue; - - if (id == AOO_ID_NONE) { - // this is a compact data message, try them all - if (remote->oursink->handle_message(buf, nbytes, endpoint, endpoint_send)) { - remote->dataPacketsReceived += 1; - if (remote->recvAllow && !remote->recvActive) { - remote->recvActive = true; - } - if (remote->resetSafetyMuted) { - updateSafetyMuting(remote); - } - break; - } - } - - if (id == AOO_ID_WILDCARD || (remote->oursink->get_id(dummyid) && id == dummyid) ) { - if (remote->oursink->handle_message(buf, nbytes, endpoint, endpoint_send)) { - remote->dataPacketsReceived += 1; - if (remote->recvAllow && !remote->recvActive) { - remote->recvActive = true; - } - if (remote->resetSafetyMuted) { - updateSafetyMuting(remote); - } - } - - if (id != AOO_ID_WILDCARD) break; - } - - if (remote->echosink->get_id(dummyid) && id == dummyid) { - remote->echosink->handle_message(buf, nbytes, endpoint, endpoint_send); - break; - } - else if (remote->latencysink->get_id(dummyid) && id == dummyid) { - remote->latencysink->handle_message(buf, nbytes, endpoint, endpoint_send); - break; - } - - } - - } else if (type == AOO_TYPE_SOURCE){ - // forward OSC packet to matching sources(s) - const ScopedReadLock sl (mCoreLock); - - if (mAooDummySource->get_id(dummyid) && id == dummyid) { - // this is the special one that can accept blind invites - mAooDummySource->handle_message(buf, nbytes, endpoint, endpoint_send); - } - else { - for (auto & remote : mRemotePeers) { - if (!remote->oursource) continue; - if (id == AOO_ID_WILDCARD || (remote->oursource->get_id(dummyid) && id == dummyid)) { - remote->oursource->handle_message(buf, nbytes, endpoint, endpoint_send); - if (id != AOO_ID_WILDCARD) break; - } - - if (remote->echosource->get_id(dummyid) && id == dummyid) { - remote->echosource->handle_message(buf, nbytes, endpoint, endpoint_send); - break; - } - else if (remote->latencysource->get_id(dummyid) && id == dummyid) { - remote->latencysource->handle_message(buf, nbytes, endpoint, endpoint_send); - break; - } - } - } + // TODO - handle possible OSC bundles + bool aoohandled = false; - - } else if (type == AOO_TYPE_CLIENT || type == AOO_TYPE_PEER){ - // forward OSC packet to matching client + if (mAooClient) { + // AoO message + const ScopedReadLock sl (mCoreLock); - //DBG("Got AOO_CLIENT or PEER data"); + aoohandled = mAooClient->handleMessage(buf, nbytes, addr.address(), addr.length()) == kAooOk; - if (mAooClient) { - mAooClient->handle_message(buf, nbytes, endpoint->getRawAddr()); - } - - /* - for (int i = 0; i < x->x_numclients; ++i){ - if (pd_class(x->x_clients[i].c_obj) == aoo_client_class) - { - t_aoo_client *c = (t_aoo_client *)x->x_clients[i].c_obj; - aoo_client_handle_message(c, buf, nbytes, - ep, (aoo_replyfn)endpoint_send); - break; - } - } - */ - } else if (type == AOO_TYPE_SERVER){ - // ignore - DBG("Got AOO_SERVER data"); + if (aoohandled) { - if (mAooServer) { - // mAooServer->handle_message(buf, nbytes, endpoint); + if (auto * remote = findRemotePeer(endpoint, -1)) { + // todo - remote->dataPacketsReceived += 1; + //if (remote->recvAllow && !remote->recvActive) { + // remote->recvActive = true; + //} + if (remote->resetSafetyMuted) { + updateSafetyMuting(remote); } - - } else { - DBG("SonoBus bug: unknown aoo type: " << type); } + + // notify send thread + notifySendThread(); } + } - // notify send thread - notifySendThread(); + if (!aoohandled) { + if (handleOtherMessage(endpoint, buf, nbytes)) { + } + else { + // not a valid AoO OSC message + DBG("SonoBus: not a valid AOO message! : " << buf[0] << buf[1] << buf[2] << buf[3]); + } } - else if (handleOtherMessage(endpoint, buf, nbytes)) { - } - else { - // not a valid AoO OSC message - DBG("SonoBus: not a valid AOO message!"); - } - } // XXX @@ -2425,7 +2658,7 @@ enum { SONOBUS_MSGTYPE_BLOCKEDINFO }; -static int32_t sonobusOscParsePattern(const char *msg, int32_t n, int32_t & rettype) +static int32_t sonobusOscParsePattern(const AooByte *msg, int32_t n, int32_t & rettype) { int32_t offset = 0; if (n >= SONOBUS_MSG_DOMAIN_LEN @@ -2503,7 +2736,7 @@ static int32_t sonobusOscParsePattern(const char *msg, int32_t n, int32_t & rett return 0; } -bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const char *msg, int32_t n) +bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const AooByte *msg, int32_t n) { // try to parse it as an OSC /sb message int32_t type = SONOBUS_MSGTYPE_UNKNOWN; @@ -2514,7 +2747,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c } try { - osc::ReceivedPacket packet(msg, n); + osc::ReceivedPacket packet((const char*)msg, n); osc::ReceivedMessage message(packet); if (type == SONOBUS_MSGTYPE_PING) { @@ -2525,9 +2758,9 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c auto tt = (it++)->AsTimeTag(); // now prepare and send ack immediately - auto tt2 = aoo_osctime_get(); // use real system time + auto tt2 = aoo::time_tag::now(); // use real system time - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAX_PACKET_SIZE]; osc::OutboundPacketStream outmsg(buf, sizeof(buf)); try { @@ -2540,7 +2773,23 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c return false; } - endpoint_send(endpoint, outmsg.Data(), (int) outmsg.Size()); + if (mAooClient) { + + RemotePeer * peer = findRemotePeer(endpoint, -1); + if (!peer) { + DBG("Peerinfo: Could not find peer for endpoint: " << endpoint->ipaddr << " port: " << endpoint->port); + return false; + } + + // have to use group/user? + AooData msg { kAooDataOSC, (AooByte*)outmsg.Data(), (AooSize) outmsg.Size() }; + + mAooClient->sendMessage(peer->groupId, peer->userId, msg, 0, 0); + + //mAooClient->sendPeerMessage( {kAooDataTypeOSC, (AooByte*)outmsg.Data(), (AooInt32) outmsg.Size() }, endpoint->address.address(), endpoint->address.length(), 0); + } else { + endpoint_send(endpoint, (AooByte*)outmsg.Data(), (int) outmsg.Size()); + } DBG("Received ping from " << endpoint->ipaddr << ":" << endpoint->port << " stamp: " << tt); @@ -2552,7 +2801,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c auto it = message.ArgumentsBegin(); auto tt = (it++)->AsTimeTag(); auto tt2 = (it++)->AsTimeTag(); - auto tt3 = aoo_osctime_get(); // use real system time + auto tt3 = aoo::time_tag::now(); // use real system time handlePingEvent(endpoint, tt, tt2, tt3); // jlc @@ -2582,7 +2831,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c // find remote peer RemotePeer * peer = findRemotePeer(endpoint, -1); if (!peer) { - DBG("Could not find peer for endpoint"); + DBG("Peerinfo: Could not find peer for endpoint: " << endpoint->ipaddr << " port: " << endpoint->port); return false; } @@ -2620,7 +2869,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c // find remote peer RemotePeer * peer = findRemotePeer(endpoint, sourceid); if (!peer) { - DBG("Could not find peer for endpoint: " << endpoint->ipaddr << "src: " << sourceid); + DBG("layoutinfo: Could not find peer for endpoint: " << endpoint->ipaddr << " src: " << sourceid); } else { peer->recvdChanLayout = true; @@ -2664,12 +2913,12 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c auto latinfo = getAllLatInfo(); - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAX_PACKET_SIZE]; osc::OutboundPacketStream outmsg(buf, sizeof(buf)); String jsonstr = JSON::toString(latinfo, true, 6); - if (jsonstr.getNumBytesAsUTF8() > AOO_MAXPACKETSIZE - 100) { + if (jsonstr.getNumBytesAsUTF8() > AOO_MAX_PACKET_SIZE - 100) { DBG("Info too big for packet!"); return false; } @@ -2685,7 +2934,7 @@ bool SonobusAudioProcessor::handleOtherMessage(EndpointState * endpoint, const c return false; } - endpoint_send(endpoint, outmsg.Data(), (int) outmsg.Size()); + endpoint_send(endpoint, (AooByte*)outmsg.Data(), (int) outmsg.Size()); DBG("Received REQLAT from " << endpoint->ipaddr << ":" << endpoint->port); } @@ -2776,7 +3025,7 @@ bool SonobusAudioProcessor::sendChatEvent(const SBChatEvent & event) // /sb/chat s:groupname s:from s:targets s:tags s:message // if peerindex < 0 - send to all peers - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAX_PACKET_SIZE]; osc::OutboundPacketStream msg(buf, sizeof(buf)); try { @@ -2806,7 +3055,10 @@ bool SonobusAudioProcessor::sendChatEvent(const SBChatEvent & event) continue; DBG("Sending chat message to " << i); - this->sendPeerMessage(peer, msg.Data(), (int32_t) msg.Size()); + // TODO have to use group/user!? + //mAooClient->sendMessage(group, user, {kAooDataTypeOSC, (AooByte*)outmsg.Data(), (AooInt32) outmsg.Size() }, 0, 0); + + this->sendPeerMessage(peer, (AooByte*) msg.Data(), (int32_t) msg.Size()); } @@ -2863,7 +3115,7 @@ juce::var SonobusAudioProcessor::getAllLatInfo() void SonobusAudioProcessor::sendBlockedInfoMessage(EndpointState *endpoint, bool blocked) { - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAX_PACKET_SIZE]; osc::OutboundPacketStream outmsg(buf, sizeof(buf)); try { @@ -2879,13 +3131,13 @@ void SonobusAudioProcessor::sendBlockedInfoMessage(EndpointState *endpoint, bool return; } - endpoint_send(endpoint, outmsg.Data(), (int) outmsg.Size()); + endpoint_send(endpoint, (AooByte*) outmsg.Data(), (int) outmsg.Size()); } void SonobusAudioProcessor::sendReqLatInfoToAll() { - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAX_PACKET_SIZE]; osc::OutboundPacketStream msg(buf, sizeof(buf)); try { @@ -2904,7 +3156,7 @@ void SonobusAudioProcessor::sendReqLatInfoToAll() auto * peer = mRemotePeers.getUnchecked(i); DBG("Sending reqlat message to " << i); - this->sendPeerMessage(peer, msg.Data(), (int32_t) msg.Size()); + this->sendPeerMessage(peer, (AooByte*) msg.Data(), (int32_t) msg.Size()); } } @@ -2912,7 +3164,7 @@ void SonobusAudioProcessor::sendLatencyMatchToAll(float latency) { // suggest to all that they should adjust all receiving latencies to be this value - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAX_PACKET_SIZE]; osc::OutboundPacketStream msg(buf, sizeof(buf)); try { @@ -2933,7 +3185,7 @@ void SonobusAudioProcessor::sendLatencyMatchToAll(float latency) auto * peer = mRemotePeers.getUnchecked(i); DBG("Sending suggestlat: " << latency << " message to " << i); - this->sendPeerMessage(peer, msg.Data(), (int32_t) msg.Size()); + this->sendPeerMessage(peer, (AooByte*)msg.Data(), (int32_t) msg.Size()); } } @@ -3049,7 +3301,7 @@ void SonobusAudioProcessor::sendRemotePeerInfoUpdate(int index, RemotePeer * top // nettype TODO - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAX_PACKET_SIZE]; const ScopedReadLock sl (mCoreLock); for (int i=0; i < mRemotePeers.size(); ++i) { @@ -3064,7 +3316,7 @@ void SonobusAudioProcessor::sendRemotePeerInfoUpdate(int index, RemotePeer * top String jsonstr = JSON::toString(info.get(), true, 6); - if (jsonstr.getNumBytesAsUTF8() > AOO_MAXPACKETSIZE - 100) { + if (jsonstr.getNumBytesAsUTF8() > AOO_MAX_PACKET_SIZE - 100) { DBG("Info too big for packet!"); return; } @@ -3079,8 +3331,8 @@ void SonobusAudioProcessor::sendRemotePeerInfoUpdate(int index, RemotePeer * top continue; } - DBG("Sending peerinfo message to " << i); - this->sendPeerMessage(peer, msg.Data(), (int32_t) msg.Size()); + DBG("Sending peerinfo message to peer id " << peer->userId); + this->sendPeerMessage(peer, (AooByte*)msg.Data(), (int32_t) msg.Size()); if (index == i || topeer == peer) break; } @@ -3088,12 +3340,20 @@ void SonobusAudioProcessor::sendRemotePeerInfoUpdate(int index, RemotePeer * top } -int32_t SonobusAudioProcessor::sendPeerMessage(RemotePeer * peer, const char *msg, int32_t n) +int32_t SonobusAudioProcessor::sendPeerMessage(RemotePeer * peer, const AooByte *msg, int32_t n) { - return endpoint_send(peer->endpoint, msg, n); + if (mAooClient) { + mAooClient->sendMessage(peer->groupId, peer->userId, {kAooDataOSC, msg, (AooSize)n }, 0, 0); + + //mAooClient->sendPeerMessage(msg, n, peer->endpoint->address.address(), peer->endpoint->address.length(), 0); + } else { + return endpoint_send(peer->endpoint, msg, n); + } } + + void SonobusAudioProcessor::doSendData() { // just try to send for everybody @@ -3105,6 +3365,24 @@ void SonobusAudioProcessor::doSendData() auto nowtimems = Time::getMillisecondCounterHiRes(); + + if (mAooClient) { + mAooClient->send(udpsend, this); + } + + + for (auto & remote : mRemotePeers) { + if ( nowtimems > (remote->lastSendPingTimeMs + PEER_PING_INTERVAL_MS) ) { + sendPingEvent(remote); + remote->lastSendPingTimeMs = nowtimems; + if (!remote->haveSentFirstPeerInfo) { + sendRemotePeerInfoUpdate(-1, remote); + remote->haveSentFirstPeerInfo = true; + } + } + } + +#if 0 while (didsomething) { //mAooSource->send(); didsomething = 0; @@ -3127,13 +3405,6 @@ void SonobusAudioProcessor::doSendData() didsomething |= remote->oursink->send(); } - if (remote->latencysource) { - didsomething |= remote->latencysource->send(); - didsomething |= remote->latencysink->send(); - didsomething |= remote->echosource->send(); - didsomething |= remote->echosink->send(); - } - if ( nowtimems > (remote->lastSendPingTimeMs + PEER_PING_INTERVAL_MS) ) { sendPingEvent(remote); remote->lastSendPingTimeMs = nowtimems; @@ -3144,6 +3415,8 @@ void SonobusAudioProcessor::doSendData() } } } +#endif + if (mPendingUnmute.get() && mPendingUnmuteAtStamp < Time::getMillisecondCounter() ) { DBG("UNMUTING ALL"); @@ -3172,13 +3445,8 @@ void SonobusAudioProcessor::doSendData() } -struct ProcessorIdPair -{ - ProcessorIdPair(SonobusAudioProcessor *proc, int32_t id_) : processor(proc), id(id_) {} - SonobusAudioProcessor * processor; - int32_t id; -}; +#if 0 static int32_t gHandleSourceEvents(void * user, const aoo_event ** events, int32_t n) { ProcessorIdPair * pp = static_cast (user); @@ -3202,468 +3470,351 @@ static int32_t gHandleClientEvents(void * user, const aoo_event ** events, int32 ProcessorIdPair * pp = static_cast (user); return pp->processor->handleClientEvents(events, n); } +#endif -void SonobusAudioProcessor::handleEvents() +int32_t SonobusAudioProcessor::handleAooServerEvent(const AooEvent *event, int32_t level) { - const ScopedReadLock sl (mCoreLock); - int32_t dummy = 0; - - if (mAooServer /*&& mAooServer->events_available()*/) { - ProcessorIdPair pp(this, dummy); - mAooServer->handle_events(gHandleServerEvents, &pp); - } - - if (mAooClient && mAooClient->events_available()) { - ProcessorIdPair pp(this, dummy); - mAooClient->handle_events(gHandleClientEvents, &pp); - } + switch (event->type){ + case kAooEventClientLogin: + { + auto e = (const AooEventClientLogin *)event; - - if (mAooDummySource->events_available() > 0) { - mAooDummySource->get_id(dummy); - ProcessorIdPair pp(this, dummy); - mAooDummySource->handle_events(gHandleSourceEvents, &pp); - } + DBG("Server - Client login: " << e->id); - for (auto & remote : mRemotePeers) { - if (remote->oursource) { - remote->oursource->get_id(dummy); - ProcessorIdPair pp(this, dummy); - remote->oursource->handle_events(gHandleSourceEvents, &pp); + break; } - if (remote->oursink) { - remote->oursink->get_id(dummy); - ProcessorIdPair pp(this, dummy); - remote->oursink->handle_events(gHandleSinkEvents, &pp); + case kAooRequestGroupJoin: + { + auto e = (const AooRequestGroupJoin *)event; + + DBG("Server - Group Joined: " << e->groupName << " by user: " << e->userName << "id:" << e->userId); + + break; } + case kAooRequestGroupLeave: + { + auto e = (const AooRequestGroupLeave *)event; - - if (remote->latencysink) { - remote->latencysink->get_id(dummy); - ProcessorIdPair pp(this, dummy); - remote->latencysink->handle_events(gHandleSinkEvents, &pp); + DBG("Server - Group Left: " << e->group ); + + break; } - if (remote->echosink) { - remote->echosink->get_id(dummy); - ProcessorIdPair pp(this, dummy); - remote->echosink->handle_events(gHandleSinkEvents, &pp); + case kAooEventGroupAdd: + { + auto e = (const AooEventGroupAdd *)event; + DBG("Server - Group Added: " << e->id << " : " << e->name); + break; } - - if (remote->latencysource) { - remote->latencysource->get_id(dummy); - ProcessorIdPair pp(this, dummy); - remote->latencysource->handle_events(gHandleSourceEvents, &pp); + case kAooEventGroupRemove: + { + auto e = (const AooEventGroupRemove *)event; + DBG("Server - Group Remove: " << e->id); + break; } - if (remote->echosource) { - remote->echosource->get_id(dummy); - ProcessorIdPair pp(this, dummy); - remote->echosource->handle_events(gHandleSourceEvents, &pp); + case kAooEventError: + { + auto e = (const AooEventError *)event; + DBG("Server error: " << e->errorMessage); + break; } - - } - -} - -void SonobusAudioProcessor::sendPingEvent(RemotePeer * peer) -{ - - auto tt = aoo_osctime_get(); - - char buf[AOO_MAXPACKETSIZE]; - osc::OutboundPacketStream outmsg(buf, sizeof(buf)); - - try { - outmsg << osc::BeginMessage(SONOBUS_FULLMSG_PING) - << osc::TimeTag(tt) - << osc::EndMessage; - } - catch (const osc::Exception& e){ - DBG("exception in ping message construction: " << e.what()); - return; - } - - sendPeerMessage(peer, outmsg.Data(), (int32_t) outmsg.Size()); - - DBG("Sent ping to peer: " << peer->endpoint->ipaddr); -} - - -void SonobusAudioProcessor::handlePingEvent(EndpointState * endpoint, uint64_t tt1, uint64_t tt2, uint64_t tt3) -{ - double diff1 = aoo_osctime_duration(tt1, tt2) * 1000.0; - double diff2 = aoo_osctime_duration(tt2, tt3) * 1000.0; - double rtt = aoo_osctime_duration(tt1, tt3) * 1000.0; - - const ScopedReadLock sl (mCoreLock); - - auto * peer = findRemotePeer(endpoint, -1); - if (!peer) return; - - // smooth it - peer->pingTime = rtt; // * 0.5; - if (rtt < 600.0 ) { - peer->smoothPingTime.Z *= 0.5f; - peer->smoothPingTime.push(peer->pingTime); - } - - DBG("ping recvd from " << peer->endpoint->ipaddr << ":" << peer->endpoint->port << " -- " << diff1 << " " << diff2 << " " << rtt << " smooth: " << peer->smoothPingTime.xbar << " stdev: " <smoothPingTime.s2xx); - - - if (!peer->hasRealLatency) { - peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); + default: + DBG("Got unknown server event: " << event->type); + break; } - peer->gotNewStylePing = true; + return kAooOk; } - -int32_t SonobusAudioProcessor::handleSourceEvents(const aoo_event ** events, int32_t n, int32_t sourceId) +int32_t SonobusAudioProcessor::handleAooClientEvent(const AooEvent *event, int32_t level) { - for (int i = 0; i < n; ++i){ - switch (events[i]->type){ - case AOO_PING_EVENT: + switch (event->type){ + case kAooEventPeerMessage: { - aoo_ping_event *e = (aoo_ping_event *)events[i]; - double diff1 = aoo_osctime_duration(e->tt1, e->tt2) * 1000.0; - double diff2 = aoo_osctime_duration(e->tt2, e->tt3) * 1000.0; - double rtt = aoo_osctime_duration(e->tt1, e->tt3) * 1000.0; - - EndpointState * es = (EndpointState *)e->endpoint; + auto e = (const AooEventPeerMessage *)event; + // lookup by group/user id - RemotePeer * peer = findRemotePeer(es, sourceId); - if (peer && !peer->gotNewStylePing) { - const ScopedReadLock sl (mCoreLock); - - // smooth it - peer->pingTime = rtt; // * 0.5; - if (rtt < 600.0 ) { - peer->smoothPingTime.Z *= 0.5f; - peer->smoothPingTime.push(peer->pingTime); - } - - DBG("ping to source " << sourceId << " recvd from " << es->ipaddr << ":" << es->port << " -- " << diff1 << " " << diff2 << " " << rtt << " smooth: " << peer->smoothPingTime.xbar << " stdev: " <smoothPingTime.s2xx); + //aoo::ip_address address((const sockaddr *)e->address, e->addrlen); + + EndpointState * es = (EndpointState *) findOrAddEndpoint(e->groupId, e->userId); + if (es) { + handleOtherMessage(es, e->data.data, e->data.size); + } else { + DBG("Error finding endpoint for " << e->groupId << " : " << e->userId); + } + + break; + } + + + case kAooRequestDisconnect: + { + // don't remove all peers? + //removeAllRemotePeers(); + + mIsConnectedToServer = false; + mSessionConnectionStamp = 0.0; + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientDisconnected, this, true, ""); + + break; + } +#if 0 + case AOONET_CLIENT_GROUP_JOIN_EVENT: + { + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + if (e->result > 0){ + DBG("Joined group - " << e->name); + const ScopedLock sl (mClientLock); + mCurrentJoinedGroup = CharPointer_UTF8 (e->name); + mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); - if (!peer->hasRealLatency) { - peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); - } + + } else { + DBG("Couldn't join group " << e->name << " - " << e->errormsg); } + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupJoined, this, e->result > 0, CharPointer_UTF8 (e->name), e->errormsg); break; } - case AOO_INVITE_EVENT: + case AOONET_CLIENT_GROUP_LEAVE_EVENT: { - aoo_sink_event *e = (aoo_sink_event *)events[i]; - - // accepts invites - if (true){ - EndpointState * es = (EndpointState *)e->endpoint; - // handle dummy source specially + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + if (e->result > 0){ - int32_t dummyid; + DBG("Group leave - " << e->name); + + const ScopedLock sl (mClientLock); + mCurrentJoinedGroup.clear(); + + // assume they are all part of the group, XXX + removeAllRemotePeers(); + + //aoo_node_remove_group(x->x_node, gensym(e->name)); - if (mAooDummySource->get_id(dummyid) && dummyid == sourceId) { - // dummy source is special, and creates a remote peer - - RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->id); - if (peer) { - // we already have a peer for this, interesting - DBG("Already had remote peer for " << es->ipaddr << ":" << es->port << " ourId: " << peer->ourId); - } else if (!mIsConnectedToServer) { - peer = doAddRemotePeerIfNecessary(es); - } - else { - // connected to server, don't just respond to anyone - peer = findRemotePeer(es, -1); - if (!peer) { - DBG("Not reacting to invite from a peer not known in the group"); - break; - } - - DBG("Got invite from peer in the group"); - } - - const ScopedReadLock sl (mCoreLock); - - peer->remoteSinkId = e->id; - - // add their sink - peer->oursource->add_sink(es, peer->remoteSinkId, endpoint_send); - peer->oursource->set_sinkoption(es, peer->remoteSinkId, aoo_opt_protocol_flags, &e->flags, sizeof(int32_t)); - - if (peer->sendAllow) { - peer->oursource->start(); - peer->sendActive = true; - } else { - peer->oursource->stop(); - peer->sendActive = false; - } - - DBG("Was invited by remote peer " << es->ipaddr << ":" << es->port << " sourceId: " << peer->remoteSinkId << " ourId: " << peer->ourId); - - // now try to invite them back at their port , with the same ID, they - // should have a source waiting for us with the same id - - DBG("Inviting them back to our sink"); - peer->remoteSourceId = peer->remoteSinkId; - peer->oursink->invite_source(es, peer->remoteSourceId, endpoint_send); - - // now remove dummy handshake one - mAooDummySource->remove_sink(es, dummyid); - - } - else { - // invited - DBG("Invite received to our source: " << sourceId << " from " << es->ipaddr << ":" << es->port << " " << e->id); - - RemotePeer * peer = findRemotePeer(es, sourceId); - if (peer) { - - peer->remoteSinkId = e->id; - - peer->oursource->add_sink(es, peer->remoteSinkId, endpoint_send); - peer->oursource->set_sinkoption(es, peer->remoteSinkId, aoo_opt_protocol_flags, &e->flags, sizeof(int32_t)); - - if (peer->sendAllow) { - peer->oursource->start(); - - peer->sendActive = true; - DBG("Starting to send, we allow it"); - } else { - peer->sendActive = false; - peer->oursource->stop(); - } - - peer->connected = true; - - updateRemotePeerUserFormat(-1, peer); - sendRemotePeerInfoUpdate(-1, peer); - - DBG("Finishing peer connection for " << es->ipaddr << ":" << es->port << " " << peer->remoteSinkId); - - } - else { - // find by echo id - if (auto * echopeer = findRemotePeerByEchoId(es, sourceId)) { - echopeer->echosource->add_sink(es, e->id, endpoint_send); - echopeer->echosource->start(); - DBG("Invite to echo source adding sink " << e->id); - } - else if (auto * latpeer = findRemotePeerByLatencyId(es, sourceId)) { - echopeer->latencysource->add_sink(es, e->id, endpoint_send); - echopeer->latencysource->start(); - DBG("Invite to our latency source adding sink " << e->id); - } - else { - // not one of our sources - DBG("No source " << sourceId << " invited, how is this possible?"); - } - - } - } } else { - DBG("Invite received"); + DBG("Couldn't leave group " << e->name << " - " << e->errormsg); } - + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupLeft, this, e->result > 0, CharPointer_UTF8 (e->name), e->errormsg); + break; } - case AOO_UNINVITE_EVENT: + case AOONET_CLIENT_GROUP_PUBLIC_ADD_EVENT: { - aoo_sink_event *e = (aoo_sink_event *)events[i]; - - // accepts uninvites - if (true){ - EndpointState * es = (EndpointState *)e->endpoint; - int32_t dummyid; - - - RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->id); - - if (peer) { - int ourid = AOO_ID_NONE; - { - const ScopedReadLock sl (mCoreLock); - ourid = peer->ourId; - peer->oursource->remove_all(); - //peer->oursink->uninvite_all(); // ?? - //peer->connected = false; - peer->sendActive = false; - peer->dataPacketsSent = 0; - - if (!peer->recvActive) { - peer->connected = false; - } - } - - //doRemoveRemotePeerIfNecessary(es, ourid); - - DBG("Uninvited, removed remote peer " << es->ipaddr << ":" << es->port); - - } - else if (auto * echopeer = findRemotePeerByEchoId(es, sourceId)) { - echopeer->echosource->remove_sink(es, e->id); - echopeer->echosource->stop(); - DBG("UnInvite to echo source adding sink " << e->id); - } - else if (auto * latpeer = findRemotePeerByLatencyId(es, sourceId)) { - echopeer->latencysource->remove_sink(es, e->id); - echopeer->latencysource->stop(); - DBG("UnInvite to latency source adding sink " << e->id); - } - - else { - DBG("Uninvite received to unknown"); - } - - } else { - DBG("Uninvite received"); + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + DBG("Public group add/changed - " << e->name << " count: " << e->result); + { + const ScopedLock sl (mPublicGroupsLock); + String group = CharPointer_UTF8 (e->name); + AooPublicGroupInfo & ginfo = mPublicGroupInfos[group]; + ginfo.groupName = group; + ginfo.activeCount = e->result; + ginfo.timestamp = Time::getCurrentTime().toMilliseconds(); } - + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPublicGroupModified, this, CharPointer_UTF8 (e->name), e->result, e->errormsg); break; } - case AOO_CHANGECODEC_EVENT: + case AOONET_CLIENT_GROUP_PUBLIC_DEL_EVENT: { - aoo_source_event *e = (aoo_source_event *)events[i]; - DBG("Change codec received from sink " << e->id); + aoonet_client_group_event *e = (aoonet_client_group_event *)event; + DBG("Public group deleted - " << e->name); + { + const ScopedLock sl (mPublicGroupsLock); + String group = CharPointer_UTF8 (e->name); + mPublicGroupInfos.erase(group); + } - EndpointState * es = (EndpointState *)e->endpoint; - RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->id); + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPublicGroupDeleted, this, CharPointer_UTF8 (e->name), e->errormsg); + break; + } +#endif - if (peer) { - // now we need to set our latency and echo source to match our main source's format - aoo_format_storage fmt; - if (peer->oursource->get_format(fmt) > 0) { - peer->latencysource->set_format(fmt.header); - peer->echosource->set_format(fmt.header); - - AudioCodecFormatCodec codec = String(fmt.header.codec) == AOO_CODEC_OPUS ? CodecOpus : CodecPCM; - if (codec == CodecOpus) { - aoo_format_opus *ofmt = (aoo_format_opus *)&fmt; - int retindex = findFormatIndex(codec, ofmt->bitrate / ofmt->header.nchannels, 0); - if (retindex >= 0) { - peer->formatIndex = retindex; // new sending format index - } - } - else if (codec == CodecPCM) { - aoo_format_pcm *pfmt = (aoo_format_pcm *)&fmt; - int bdepth = pfmt->bitdepth == AOO_PCM_FLOAT32 ? 4 : pfmt->bitdepth == AOO_PCM_INT24 ? 3 : pfmt->bitdepth == AOO_PCM_FLOAT64 ? 8 : 2; - int retindex = findFormatIndex(codec, 0, bdepth); - if (retindex >= 0) { - peer->formatIndex = retindex; // new sending format index - } - } - } + + case kAooEventPeerHandshake: + { + auto e = (const AooEventPeer *)event; + + DBG("Peer attempting to join group " << e->groupName << " - user " << e->userName); + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerPendingJoin, this, CharPointer_UTF8 (e->groupName), CharPointer_UTF8 (e->userName), e->groupId, e->userId); + + break; + } + + case kAooEventPeerJoin: + { + auto e = (const AooEventPeerJoin *)event; + + DBG("Peer joined group " << e->groupName << " - user " << e->userName << " userId: " << e->userId); + if (mAutoconnectGroupPeers) { + connectRemotePeerRaw(e->address.data, e->address.size, e->userId, CharPointer_UTF8 (e->userName), CharPointer_UTF8 (e->groupName), e->groupId, !mMainRecvMute.get()); + } + + //aoo_node_add_peer(x->x_node, gensym(e->group), gensym(e->user), + // (const struct sockaddr *)e->address, e->length); + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerJoined, this, CharPointer_UTF8 (e->groupName), CharPointer_UTF8 (e->userName), e->groupId, e->userId); + + + break; + } + case kAooEventPeerTimeout: + { + auto e = (const AooEventPeerTimeout *)event; + + DBG("Peer failed to join group " << e->groupName << " - user " << e->userName); + + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerJoinFailed, this, CharPointer_UTF8 (e->groupName), CharPointer_UTF8 (e->userName), e->groupId, e->userId); + + break; + } + case kAooEventPeerLeave: + { + auto e = (const AooEventPeerLeave *)event; + + DBG("Peer leave group " << e->groupName << " - user " << e->userName); + + EndpointState * endpoint = findOrAddRawEndpoint(e->address.data, e->address.size); + if (endpoint) { - + removeAllRemotePeersWithEndpoint(endpoint); } + + //aoo_node_remove_peer(x->x_node, gensym(e->group), gensym(e->user)); + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerLeft, this, CharPointer_UTF8 (e->groupName), CharPointer_UTF8 (e->userName), e->groupId, e->userId); + + break; } - default: + case kAooEventError: + { + auto e = (const AooEventError *)event; + DBG("client error: " << e->errorMessage); + clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientError, this, e->errorMessage); break; } + case kAooEventPeerPing: + { + auto e = (const AooEventPeerPing *)event; + DBG("Peer ping " << e->group << " - user " << e->user); + break; + } + + default: + DBG("Got unknown client event: " << event->type); + break; } - return 1; - + + return kAooOk; } -int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32_t n, int32_t sinkId) + +int32_t SonobusAudioProcessor::handleAooSinkEvent(const AooEvent *event, int32_t level, int32 sinkId) { - for (int i = 0; i < n; ++i){ - switch (events[i]->type){ - case AOO_SOURCE_ADD_EVENT: + switch (event->type){ + case kAooEventSourceAdd: { - aoo_source_event *e = (aoo_source_event *)events[i]; - EndpointState * es = (EndpointState *)e->endpoint; - + auto e = (AooEventEndpoint *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + RemotePeer * peer = findRemotePeer(es, sinkId); if (peer) { // someone has added us, thus accepting our invitation int32_t dummyid; - - if (mAooDummySource->get_id(dummyid) && dummyid == e->id ) { + +#if 0 + if (mAooDummySource->getId(dummyid) == kAooOk && dummyid == e->endpoint.id ) { // ignoring dummy add DBG("Got dummy handshake add from " << es->ipaddr << ":" << es->port); } - else { - DBG("Added source " << es->ipaddr << ":" << es->port << " " << e->id << " to our " << sinkId); - peer->remoteSourceId = e->id; + else +#endif + { + DBG("Added source " << es->ipaddr << ":" << es->port << " " << e->endpoint.id << " to our " << sinkId); + peer->remoteSourceId = e->endpoint.id; + + //AooEndpoint bogusep = { e->endpoint.address, e->endpoint.addrlen, 0 }; + //peer->oursink->uninviteSource(bogusep); // get rid of existing bogus one - peer->oursink->uninvite_source(es, 0, endpoint_send); // get rid of existing bogus one - if (peer->recvAllow) { - peer->oursink->invite_source(es, peer->remoteSourceId, endpoint_send); + peer->oursink->inviteSource(e->endpoint, nullptr); //peer->recvActive = true; } else { DBG("we aren't accepting recv right now, politely decline it"); - peer->oursink->uninvite_source(es, peer->remoteSourceId, endpoint_send); - peer->recvActive = false; + peer->oursink->uninviteSource(e->endpoint); + peer->recvActive = false; } peer->connected = true; - } // do invite here? + } else { - DBG("Added source to unknown " << e->id); + DBG("Added source to unknown " << e->endpoint.id); } // add remote source //doAddRemoteSourceIfNecessary(es, e->id); - - + + break; } - case AOO_SOURCE_FORMAT_EVENT: + case kAooEventFormatChange: { - aoo_source_event *e = (aoo_source_event *)events[i]; - EndpointState * es = (EndpointState *)e->endpoint; + auto e = (const AooEventFormatChange *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + + const ScopedReadLock sl (mCoreLock); - const ScopedReadLock sl (mCoreLock); - RemotePeer * peer = findRemotePeer(es, sinkId); if (peer) { - aoo_format_storage f; - if (peer->oursink->get_source_format(e->endpoint, e->id, f) > 0) { - DBG("Got source format event from " << es->ipaddr << ":" << es->port << " " << e->id << " channels: " << f.header.nchannels); - peer->recvMeterSource.resize(f.header.nchannels, meterRmsWindow); - + AooFormatStorage f; + if (peer->oursink->getSourceFormat(e->endpoint, f) == kAooOk) { + DBG("Got source format event from " << es->ipaddr << ":" << es->port << " " << e->endpoint.id << " channels: " << f.header.numChannels); + peer->recvMeterSource.resize(f.header.numChannels, meterRmsWindow); + // check for layout bool gotuserformat = false; char userfmtdata[1024]; - int32_t retsize = peer->oursink->get_sourceoption(e->endpoint, e->id, aoo_opt_userformat, userfmtdata, sizeof(userfmtdata)); - if (retsize > 0) { - ValueTree tree = ValueTree::readFromData (userfmtdata, retsize); - - if (tree.isValid()) { - applyLayoutFormatToPeer(peer, tree); - gotuserformat = true; - } - else { - DBG("Error parsing userformat"); - } - } - else { - DBG("No userformat: " << retsize); - } - - if (peer->recvChannels != f.header.nchannels) { - + JUCE_COMPILER_WARNING ("TODO: USERFORMAT"); + /* + int32_t retsize = peer->oursink->get_sourceoption(e->endpoint, e->id, aoo_opt_userformat, userfmtdata, sizeof(userfmtdata)); + if (retsize > 0) { + ValueTree tree = ValueTree::readFromData (userfmtdata, retsize); + + if (tree.isValid()) { + applyLayoutFormatToPeer(peer, tree); + gotuserformat = true; + } + else { + DBG("Error parsing userformat"); + } + } + else { + DBG("No userformat: " << retsize); + } + */ + + if (peer->recvChannels != f.header.numChannels) { + { const ScopedWriteLock sl (peer->sinkLock); - - peer->recvChannels = std::min(MAX_PANNERS, f.header.nchannels); - + + peer->recvChannels = std::min(MAX_PANNERS, f.header.numChannels); + // set up this sink with new channel count - + int sinkchan = std::max(getMainBusNumOutputChannels(), peer->recvChannels); - - peer->oursink->setup(getSampleRate(), currSamplesPerBlock, sinkchan); + + peer->oursink->setup(sinkchan, getSampleRate(), currSamplesPerBlock, 0); } peer->recvMeterSource.resize (peer->recvChannels, meterRmsWindow); - + // for now if > 2, all on own changroup (by default) - + if (!gotuserformat && !peer->recvdChanLayout) { if (peer->recvChannels > 2) { if (!peer->modifiedChanGroups) { @@ -3677,75 +3828,76 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 else { peer->chanGroups[0].params.numChannels = peer->recvChannels; peer->numChanGroups = 1; - + if (peer->recvChannels == 1) { peer->viewExpanded = false; } } } - + if (peer->recvChannels == 1) { peer->viewExpanded = false; } - + /* - if (peer->recvChannels == 1) { - // center pan - peer->recvPan[0] = 0.0f; - } else if (peer->recvChannels == 2) { - // Left/Right - peer->recvStereoPan[0] = -1.0f; - peer->recvStereoPan[1] = 1.0f; - } else if (peer->recvChannels > 2) { - peer->recvStereoPan[0] = -1.0f; - peer->recvStereoPan[1] = 1.0f; - for (int i=2; i < peer->recvChannels; ++i) { - peer->recvPan[i] = 0.0f; - } - } + if (peer->recvChannels == 1) { + // center pan + peer->recvPan[0] = 0.0f; + } else if (peer->recvChannels == 2) { + // Left/Right + peer->recvStereoPan[0] = -1.0f; + peer->recvStereoPan[1] = 1.0f; + } else if (peer->recvChannels > 2) { + peer->recvStereoPan[0] = -1.0f; + peer->recvStereoPan[1] = 1.0f; + for (int i=2; i < peer->recvChannels; ++i) { + peer->recvPan[i] = 0.0f; + } + } */ } - - - AudioCodecFormatCodec codec = String(f.header.codec) == AOO_CODEC_OPUS ? CodecOpus : CodecPCM; + + + AudioCodecFormatCodec codec = String(f.header.codecName) == kAooCodecOpus ? CodecOpus : CodecPCM; if (codec == CodecOpus) { - aoo_format_opus *fmt = (aoo_format_opus *)&f; - peer->recvFormat = AudioCodecFormatInfo(fmt->bitrate/fmt->header.nchannels, fmt->complexity, fmt->signal_type); + AooFormatOpus *fmt = (AooFormatOpus *)&f; + // unknown parts, + //peer->recvFormat = AudioCodecFormatInfo(fmt->bitrate/fmt->header.nchannels, fmt->complexity, fmt->signalType); //peer->recvFormatIndex = findFormatIndex(codec, fmt->bitrate / fmt->header.nchannels, 0); } else { - aoo_format_pcm *fmt = (aoo_format_pcm *)&f; - int bitdepth = fmt->bitdepth == AOO_PCM_INT16 ? 2 : fmt->bitdepth == AOO_PCM_INT24 ? 3 : fmt->bitdepth == AOO_PCM_FLOAT32 ? 4 : fmt->bitdepth == AOO_PCM_FLOAT64 ? 8 : 2; + AooFormatPcm *fmt = (AooFormatPcm *)&f; + int bitdepth = fmt->bitDepth == kAooPcmInt16 ? 2 : fmt->bitDepth == kAooPcmInt24 ? 3 : fmt->bitDepth == kAooPcmFloat32 ? 4 : fmt->bitDepth == kAooPcmFloat64 ? 8 : 2; peer->recvFormat = AudioCodecFormatInfo(bitdepth); //peer->recvFormatIndex = findFormatIndex(codec, 0, fmt->bitdepth); } clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerChangedState, this, "format"); - } + } } - else { - DBG("format event to unknown " << e->id); + else { + DBG("format event to unknown " << e->endpoint.id); } break; } - case AOO_SOURCE_STATE_EVENT: + case kAooEventStreamState: { - aoo_source_state_event *e = (aoo_source_state_event *)events[i]; - EndpointState * es = (EndpointState *)e->endpoint; - + auto e = (AooEventStreamState *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + DBG("Got source state event from " << es->ipaddr << ":" << es->port << " -- " << e->state); - - const ScopedReadLock sl (mCoreLock); - + + const ScopedReadLock sl (mCoreLock); + RemotePeer * peer = findRemotePeer(es, sinkId); if (peer) { - peer->recvActive = peer->recvAllow && e->state > 0; + peer->recvActive = peer->recvAllow && e->state == kAooStreamStateActive; if (!peer->recvActive && !peer->sendActive) { peer->connected = false; } else { - peer->connected = true; + peer->connected = true; } } @@ -3753,29 +3905,30 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 break; } - case AOO_BLOCK_LOST_EVENT: + case kAooEventBufferUnderrun: + //case AOO_BLOCK_LOST_EVENT: { - aoo_block_lost_event *e = (aoo_block_lost_event *)events[i]; - - EndpointState * es = (EndpointState *)e->endpoint; - - DBG("Got source block lost event from " << es->ipaddr << ":" << es->port << " " << e->id << " -- " << e->count); - - const ScopedReadLock sl (mCoreLock); + auto *e = (AooEventBufferUnderrun *)event; + + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + + DBG("Got source underrun event from " << es->ipaddr << ":" << es->port << " " << e->endpoint.id); + + const ScopedReadLock sl (mCoreLock); RemotePeer * peer = findRemotePeer(es, sinkId); if (peer) { - peer->dataPacketsDropped += e->count; + peer->dataPacketsDropped += 1; // e->count; if (peer->autosizeBufferMode != AutoNetBufferModeOff) { // see if our drop rate exceeds threshold, and increase buffersize if so double nowtime = Time::getMillisecondCounterHiRes(); const float dropratethresh = peer->autosizeBufferMode == AutoNetBufferModeInitAuto ? 1.0f : mAutoresizeDropRateThresh; const float adjustlimit = 0.5f; // don't adjust more often than once every 0.5 seconds - + bool autoinitdone = peer->autosizeBufferMode == AutoNetBufferModeInitAuto && peer->autoNetbufInitCompleted; if (peer->lastDroptime > 0 && !autoinitdone) { - double deltatime = (nowtime - peer->lastDroptime) * 1e-3; + double deltatime = (nowtime - peer->lastDroptime) * 1e-3; if (deltatime > adjustlimit) { //float droprate = (peer->dataPacketsDropped - peer->lastDropCount) / deltatime; float droprate = 1.0f / deltatime; // treat any drops as one instance @@ -3783,35 +3936,33 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 float adjms = 1000.0f * currSamplesPerBlock / getSampleRate(); peer->buffertimeMs += adjms; peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); - peer->oursink->set_buffersize(peer->buffertimeMs); - peer->echosink->set_buffersize(peer->buffertimeMs); - peer->latencysink->set_buffersize(peer->buffertimeMs); + peer->oursink->setLatency(peer->buffertimeMs * 1e-3); peer->latencyDirty = true; peer->fillRatioSlow.reset(); peer->fillRatio.reset(); - + DBG("AUTO-Increasing buffer time by " << adjms << " ms to " << (int)peer->buffertimeMs << " droprate: " << droprate); - + if (peer->hasRealLatency) { peer->totalEstLatency = peer->totalLatency + (peer->buffertimeMs - peer->bufferTimeAtRealLatency); } - + if (peer->autosizeBufferMode == AutoNetBufferModeAutoFull) { - + const float timesincedecrthresh = 2.0; if (peer->lastNetBufDecrTime > 0 && (nowtime - peer->lastNetBufDecrTime)*1e-3 < timesincedecrthresh ) { peer->netBufAutoBaseline = peer->buffertimeMs; DBG("Got drop within short time thresh, setting minimum baseline for future decr to " << peer->netBufAutoBaseline); } } - + sendRemotePeerInfoUpdate(-1, peer); // send to this peer - + } - + float realdroprate = (peer->dataPacketsDropped - peer->lastDropCount) / deltatime; peer->fastDropRate.push(realdroprate); - + peer->lastDroptime = nowtime; peer->lastDropCount = peer->dataPacketsDropped; } @@ -3822,12 +3973,12 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 float droprate = (peer->dataPacketsDropped - peer->lastDropCount) / deltatime; peer->fastDropRate.push(droprate); } - + peer->lastDroptime = nowtime; peer->lastDropCount = peer->dataPacketsDropped; } - + //peer->lastNetBufDecrTime = 0; // reset auto-decr } @@ -3835,67 +3986,57 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 break; } - case AOO_BLOCK_REORDERED_EVENT: + case kAooEventBlockDrop: { - aoo_block_reordered_event *e = (aoo_block_reordered_event *)events[i]; - - EndpointState * es = (EndpointState *)e->endpoint; - - DBG("Got source block reordered event from " << es->ipaddr << ":" << es->port << " " << e->id << " -- " << e->count); - + auto *e = (AooEventBlockDrop *)event; + + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + + DBG("Got source block dropped event from " << es->ipaddr << ":" << es->port << " " << e->endpoint.id << " -- " << e->count); + break; } - case AOO_BLOCK_RESENT_EVENT: + case kAooEventBlockResend: { - aoo_block_resent_event *e = (aoo_block_resent_event *)events[i]; - EndpointState * es = (EndpointState *)e->endpoint; - - DBG("Got source block resent event from " << es->ipaddr << ":" << es->port << " " << e->id << " -- " << e->count); - const ScopedReadLock sl (mCoreLock); + auto *e = (AooEventBlockResend *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + + DBG("Got source block resent event from " << es->ipaddr << ":" << es->port << " " << e->endpoint.id << " -- " << e->count); + const ScopedReadLock sl (mCoreLock); RemotePeer * peer = findRemotePeer(es, sinkId); if (peer) { peer->dataPacketsResent += e->count; } - - break; - } - case AOO_BLOCK_GAP_EVENT: - { - aoo_block_gap_event *e = (aoo_block_gap_event *)events[i]; - - EndpointState * es = (EndpointState *)e->endpoint; - - DBG("Got source block gap event from " << es->ipaddr << ":" << es->port << " " << e->id << " -- " << e->count); - + break; } - case AOO_PING_EVENT: + case kAooEventSinkPing: { - aoo_ping_event *e = (aoo_ping_event *)events[i]; - EndpointState * es = (EndpointState *)e->endpoint; - - - double diff = aoo_osctime_duration(e->tt1, e->tt2) * 1000.0; - DBG("Got source block ping event from " << es->ipaddr << ":" << es->port << " " << e->id << " -- " << diff); - - - RemotePeer * peer = findRemotePeer(es, sinkId); + auto *e = (AooEventSinkPing *)event; + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + + + double diff = aoo::time_tag::duration(e->t1, e->t2) * 1000.0; + DBG("Got source block ping event from " << es->ipaddr << ":" << es->port << " " << e->endpoint.id << " -- " << diff); + + + RemotePeer * peer = findRemotePeer(es, sinkId); if (peer) { const ScopedReadLock sl (mCoreLock); - + double nowtime = Time::getMillisecondCounterHiRes(); - + double deltadroptime = peer->lastDroptime > 0 ? (nowtime - peer->lastDroptime) * 1e-3 : (nowtime - peer->resetDroptime) * 1e-3; - + if (peer->autosizeBufferMode != AutoNetBufferModeOff) { if (!peer->autoNetbufInitCompleted) { const float nodropsthresh = 7.0; - + if (deltadroptime > nodropsthresh) { peer->autoNetbufInitCompleted = true; peer->resetSafetyMuted = false; DBG("Netbuf Initial auto time is done after no drops in " << nodropsthresh); - + // clear drop count peer->dataPacketsResent = 0; peer->dataPacketsDropped = 0; @@ -3904,22 +4045,22 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 peer->fastDropRate.resetInitVal(0.0f); //peer->lastDroptime = 0; } - + } - + } else { // manual mode peer->resetSafetyMuted = false; } - - + + if (peer->autosizeBufferMode == AutoNetBufferModeAutoFull) { // possibly adjust net buffer down, if it has been longer than threshold since last drop double nowtime = Time::getMillisecondCounterHiRes(); const float nodropsthresh = 10.0; // no drops in 10 seconds const float adjustlimit = 10; // don't adjust more often than once every 10 seconds - + if (peer->lastNetBufDecrTime > 0 && peer->buffertimeMs > peer->netBufAutoBaseline && !peer->latencyMatched) { double deltatime = (nowtime - peer->lastNetBufDecrTime) * 1e-3; double deltadroptime = (nowtime - peer->lastDroptime) * 1e-3; @@ -3929,406 +4070,425 @@ int32_t SonobusAudioProcessor::handleSinkEvents(const aoo_event ** events, int32 if (deltadroptime > nodropsthresh) { float adjms = 1000.0f * currSamplesPerBlock / getSampleRate(); peer->buffertimeMs -= adjms; - + peer->buffertimeMs = std::max(peer->buffertimeMs, peer->netBufAutoBaseline); - + peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); - peer->oursink->set_buffersize(peer->buffertimeMs); - peer->echosink->set_buffersize(peer->buffertimeMs); - peer->latencysink->set_buffersize(peer->buffertimeMs); + peer->oursink->setLatency(peer->buffertimeMs * 1e-3); peer->latencyDirty = true; - + peer->fillRatioSlow.reset(); peer->fillRatio.reset(); - + if (peer->hasRealLatency) { peer->totalEstLatency = peer->totalLatency + (peer->buffertimeMs - peer->bufferTimeAtRealLatency); } - + DBG("AUTO-Decreasing buffer time by " << adjms << " ms to " << (int) peer->buffertimeMs); - + peer->lastNetBufDecrTime = nowtime; - + sendRemotePeerInfoUpdate(-1, peer); // send to this peer - + } - + //peer->lastNetBufDropCount = peer->dataPacketsDropped; } } else { peer->lastNetBufDecrTime = nowtime; } - + + } + + if (peer->resetSafetyMuted) { + updateSafetyMuting(peer); } } - - + + break; } default: break; - } } - return 1; -} - -int32_t SonobusAudioProcessor::handleServerEvents(const aoo_event ** events, int32_t n) -{ - for (int i = 0; i < n; ++i){ - switch (events[i]->type){ - case AOONET_SERVER_USER_JOIN_EVENT: - { - aoonet_server_user_event *e = (aoonet_server_user_event *)events[i]; - - DBG("Server - User joined: " << e->name); - - break; - } - case AOONET_SERVER_USER_LEAVE_EVENT: - { - aoonet_server_user_event *e = (aoonet_server_user_event *)events[i]; - - DBG("Server - User left: " << e->name); - - - break; - } - case AOONET_SERVER_GROUP_JOIN_EVENT: - { - aoonet_server_group_event *e = (aoonet_server_group_event *)events[i]; - - DBG("Server - Group Joined: " << e->group << " by user: " << e->user); - - break; - } - case AOONET_SERVER_GROUP_LEAVE_EVENT: - { - aoonet_server_group_event *e = (aoonet_server_group_event *)events[i]; - - DBG("Server - Group Left: " << e->group << " by user: " << e->user); - - break; - } - case AOONET_SERVER_ERROR_EVENT: - { - aoonet_server_event *e = (aoonet_server_event *)events[i]; - - DBG("Server error: " << String::fromUTF8(e->errormsg)); - - break; - } - default: - DBG("Got unknown server event: " << events[i]->type); - break; - } - } - return 1; + + return kAooOk; } -int32_t SonobusAudioProcessor::handleClientEvents(const aoo_event ** events, int32_t n) +int32_t SonobusAudioProcessor::handleAooSourceEvent(const AooEvent *event, int32_t level, int32_t sourceId) { - for (int i = 0; i < n; ++i){ - switch (events[i]->type){ - case AOONET_CLIENT_CONNECT_EVENT: + switch (event->type){ + case kAooEventSourcePing: { - aoonet_client_group_event *e = (aoonet_client_group_event *)events[i]; + auto & e = * ((AooEventSourcePing*) event); + double diff1 = aoo::time_tag::duration(e.t1, e.t2) * 1000.0; + double diff2 = aoo::time_tag::duration(e.t2, e.t3) * 1000.0; + double rtt = aoo::time_tag::duration(e.t1, e.t3) * 1000.0; - if (e->result > 0){ - DBG("Connected to server!"); - mIsConnectedToServer = true; - mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); - } else { - DBG("Couldn't connect to server - " << String::fromUTF8(e->errormsg)); - mIsConnectedToServer = false; - mSessionConnectionStamp = 0.0; - } + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e.endpoint.address, e.endpoint.addrlen); - if (mPendingReconnect) { - - if (mIsConnectedToServer && mPendingReconnectInfo.groupName.isNotEmpty()) { - mPendingReconnectInfo.timestamp = Time::getCurrentTime().toMilliseconds(); - addRecentServerConnectionInfo(mPendingReconnectInfo); - setWatchPublicGroups(false); - DBG("Joining group after pending reconnect: " << mPendingReconnectInfo.groupName); - joinServerGroup(mPendingReconnectInfo.groupName, mPendingReconnectInfo.groupPassword, mPendingReconnectInfo.groupIsPublic); + RemotePeer * peer = findRemotePeer(es, sourceId); + if (peer && !peer->gotNewStylePing) { + const ScopedReadLock sl (mCoreLock); + + // smooth it + peer->pingTime = rtt; // * 0.5; + if (rtt < 600.0 ) { + peer->smoothPingTime.Z *= 0.5f; + peer->smoothPingTime.push(peer->pingTime); } - - mPendingReconnect = false; - } - if (mIsConnectedToServer && mReconnectTimer.isTimerRunning()) { - DBG("Stopping reconnect timer"); - mReconnectTimer.stopTimer(); - mRecoveringFromServerLoss = false; + DBG("ping to source " << sourceId << " recvd from " << es->ipaddr << ":" << es->port << " -- " << diff1 << " " << diff2 << " " << rtt << " smooth: " << peer->smoothPingTime.xbar << " stdev: " <smoothPingTime.s2xx); + + + if (!peer->hasRealLatency) { + peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); + } } - - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientConnected, this, e->result > 0, String::fromUTF8(e->errormsg)); - break; } - case AOONET_CLIENT_DISCONNECT_EVENT: + case kAooEventInvite: { - aoonet_client_group_event *e = (aoonet_client_group_event *)events[i]; - if (e->result == 0){ - DBG("Disconnected from server - " << String::fromUTF8(e->errormsg)); - - if (mReconnectAfterServerLoss.get() && !mReconnectTimer.isTimerRunning()) { - DBG("Starting reconnect timer"); - mRecoveringFromServerLoss = true; - mReconnectTimer.startTimer(1000); - } - } + auto *e = (AooEventInvite *)event; - mPendingReconnect = false; + // accepts invites + if (true){ + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + // handle dummy source specially - // don't remove all peers? - //removeAllRemotePeers(); - - mIsConnectedToServer = false; - mSessionConnectionStamp = 0.0; - - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientDisconnected, this, e->result > 0, String::fromUTF8(e->errormsg)); + { + // invited + aoo::ip_address epaddr((const struct sockaddr *)e->endpoint.address, e->endpoint.addrlen); + + char tmpbuf[64]; + std::string addrhex; + for (int i=0; i < e->endpoint.addrlen; ++i) { + snprintf(tmpbuf, sizeof(tmpbuf), "%x", ((uint8_t *)e->endpoint.address)[i] ); + addrhex += tmpbuf; + addrhex += " "; + } + DBG("raw addr: " << addrhex); - break; - } - case AOONET_CLIENT_GROUP_JOIN_EVENT: - { - aoonet_client_group_event *e = (aoonet_client_group_event *)events[i]; - if (e->result > 0){ - DBG("Joined group - " << e->name); - const ScopedLock sl (mClientLock); - mCurrentJoinedGroup = CharPointer_UTF8 (e->name); + DBG("Invite received to our source: " << sourceId << " from " << epaddr.name_unmapped() << " esaddr: " << es->ipaddr << ":" << es->port << " " << e->endpoint.id); - mSessionConnectionStamp = Time::getMillisecondCounterHiRes(); + if (sourceId == mCurrentUserId) { + if (auto * peer = findRemotePeer(es, -1)) { + + peer->remoteSinkId = e->endpoint.id; + peer->connected = true; // ?? + + if (peer->sendAllow && !mMainSendMute.get()) { + DBG("Accepted invitation to send from our common to remote sink: " << peer->remoteSinkId); + mAooCommonSource->handleInvite(e->endpoint, e->token, true); + peer->sendCommonActive = true; + } + else { + mAooCommonSource->handleInvite(e->endpoint, e->token, false); + peer->sendCommonActive = false; + DBG("Not sending from our common to remote sink: " << peer->remoteSinkId); + } + + sendRemotePeerInfoUpdate(-1, peer); + } + else { + DBG("Could not find peer from endpoint alone"); + } + } + else if (auto * peer = findRemotePeer(es, sourceId)) { + + peer->remoteSinkId = e->endpoint.id; + + //peer->oursource->addSink(e->endpoint, 0); + + //peer->oursource->set_sinkoption(es, peer->remoteSinkId, aoo_opt_protocol_flags, &e->flags, sizeof(int32_t)); + + if (peer->sendAllow) { + peer->oursource->handleInvite(e->endpoint, e->token, true); + peer->sendActive = true; + DBG("Accepted invitation to send to remote sink: " << peer->remoteSinkId); + } else { + // don't accept invitation to send + peer->oursource->handleInvite(e->endpoint, e->token, false); + DBG("Not sending , Not Accepting invitation to send to remote sink"); + } + + peer->connected = true; + + updateRemotePeerUserFormat(-1, peer); + sendRemotePeerInfoUpdate(-1, peer); + + DBG("Finishing peer connection for " << es->ipaddr << ":" << es->port << " " << peer->remoteSinkId); + + } + else { + // not one of our sources + DBG("No source " << sourceId << " invited, how is this possible?"); + } + + } } else { - DBG("Couldn't join group " << e->name << " - " << String::fromUTF8(e->errormsg)); + DBG("Invite received"); } - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupJoined, this, e->result > 0, CharPointer_UTF8 (e->name), String::fromUTF8(e->errormsg)); + break; } - case AOONET_CLIENT_GROUP_LEAVE_EVENT: + case kAooEventUninvite: { - aoonet_client_group_event *e = (aoonet_client_group_event *)events[i]; - if (e->result > 0){ - - DBG("Group leave - " << e->name); + auto *e = (AooEventUninvite *)event; - const ScopedLock sl (mClientLock); - mCurrentJoinedGroup.clear(); + // accepts uninvites + if (true){ + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->endpoint.address, e->endpoint.addrlen); + int32_t dummyid; - // assume they are all part of the group, XXX - removeAllRemotePeers(); - //aoo_node_remove_group(x->x_node, gensym(e->name)); - + RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->endpoint.id); - } else { - DBG("Couldn't leave group " << e->name << " - " << String::fromUTF8(e->errormsg)); - } + if (peer) { + if (sourceId == mCurrentUserId) { + mAooCommonSource->handleUninvite(e->endpoint, e->token, true); + peer->sendCommonActive = false; + peer->dataPacketsSent = 0; + if (!peer->recvActive) { + peer->connected = false; + } + DBG("Uninvited from our common source " << es->ipaddr << ":" << es->port); + } + else + { + const ScopedReadLock sl (mCoreLock); + peer->oursource->handleUninvite(e->endpoint, e->token, true); + peer->oursource->removeAll(); - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientGroupLeft, this, e->result > 0, CharPointer_UTF8 (e->name), String::fromUTF8(e->errormsg)); + peer->sendActive = false; + peer->dataPacketsSent = 0; - break; - } - case AOONET_CLIENT_GROUP_PUBLIC_ADD_EVENT: - { - aoonet_client_group_event *e = (aoonet_client_group_event *)events[i]; - DBG("Public group add/changed - " << e->name << " count: " << e->result); - { - const ScopedLock sl (mPublicGroupsLock); - String group = CharPointer_UTF8 (e->name); - AooPublicGroupInfo & ginfo = mPublicGroupInfos[group]; - ginfo.groupName = group; - ginfo.activeCount = e->result; - ginfo.timestamp = Time::getCurrentTime().toMilliseconds(); - } + if (!peer->recvActive) { + peer->connected = false; + } + DBG("Uninvited from our remote peer source " << es->ipaddr << ":" << es->port); + } - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPublicGroupModified, this, CharPointer_UTF8 (e->name), e->result, String::fromUTF8(e->errormsg)); - break; - } - case AOONET_CLIENT_GROUP_PUBLIC_DEL_EVENT: - { - aoonet_client_group_event *e = (aoonet_client_group_event *)events[i]; - DBG("Public group deleted - " << e->name); - { - const ScopedLock sl (mPublicGroupsLock); - String group = CharPointer_UTF8 (e->name); - mPublicGroupInfos.erase(group); - } - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPublicGroupDeleted, this, CharPointer_UTF8 (e->name), String::fromUTF8(e->errormsg)); - break; - } + } + else { + DBG("Uninvite received to unknown"); + } - case AOONET_CLIENT_PEER_PREJOIN_EVENT: - { - aoonet_client_peer_event *e = (aoonet_client_peer_event *)events[i]; - - if (e->result > 0){ - DBG("Peer attempting to join group " << e->group << " - user " << e->user); - - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerPendingJoin, this, CharPointer_UTF8 (e->group), CharPointer_UTF8 (e->user)); - } else { - DBG("bug bad result on join event"); + DBG("Uninvite received"); } - + break; } - case AOONET_CLIENT_PEER_JOIN_EVENT: +#if 0 + case AOO_CHANGECODEC_EVENT: { - aoonet_client_peer_event *e = (aoonet_client_peer_event *)events[i]; + aoo_source_event *e = (aoo_source_event *)event; + DBG("Change codec received from sink " << e->id); - if (e->result > 0){ - DBG("Peer joined group " << e->group << " - user " << e->user); + EndpointState * es = (EndpointState *) findOrAddRawEndpoint(e->ep.address, e->ep.addrlen); - EndpointState * endpoint = findOrAddRawEndpoint(e->address); - if (endpoint) { - - // check if blocked - if (isAddressBlocked(endpoint->ipaddr)) { - - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerJoinBlocked, this, CharPointer_UTF8 (e->group), CharPointer_UTF8 (e->user), endpoint->ipaddr, endpoint->port); + RemotePeer * peer = findRemotePeerByRemoteSinkId(es, e->id); + + if (peer) { + // now we need to set our latency and echo source to match our main source's format + aoo_format_storage fmt; + if (peer->oursource->get_format(fmt) > 0) { - // after a short delay - Timer::callAfterDelay(400, [this, endpoint] { - sendBlockedInfoMessage(endpoint, true); - }); + AudioCodecFormatCodec codec = String(fmt.header.codec) == AOO_CODEC_OPUS ? CodecOpus : CodecPCM; + if (codec == CodecOpus) { + aoo_format_opus *ofmt = (aoo_format_opus *)&fmt; + int retindex = findFormatIndex(codec, ofmt->bitrate / ofmt->header.nchannels, 0); + if (retindex >= 0) { + peer->formatIndex = retindex; // new sending format index + } } - else { - if (mAutoconnectGroupPeers) { - connectRemotePeerRaw(e->address, CharPointer_UTF8 (e->user), CharPointer_UTF8 (e->group), !mMainRecvMute.get()); + else if (codec == CodecPCM) { + aoo_format_pcm *pfmt = (aoo_format_pcm *)&fmt; + int bdepth = pfmt->bitdepth == AOO_PCM_FLOAT32 ? 4 : pfmt->bitdepth == AOO_PCM_INT24 ? 3 : pfmt->bitdepth == AOO_PCM_FLOAT64 ? 8 : 2; + int retindex = findFormatIndex(codec, 0, bdepth); + if (retindex >= 0) { + peer->formatIndex = retindex; // new sending format index } - - //aoo_node_add_peer(x->x_node, gensym(e->group), gensym(e->user), - // (const struct sockaddr *)e->address, e->length); - - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerJoined, this, CharPointer_UTF8 (e->group), CharPointer_UTF8 (e->user)); } } - - } else { - DBG("bug bad result on join event"); } - - - break; } - case AOONET_CLIENT_PEER_JOINFAIL_EVENT: - { - aoonet_client_peer_event *e = (aoonet_client_peer_event *)events[i]; - - if (e->result > 0){ - DBG("Peer failed to join group " << e->group << " - user " << e->user); - - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerJoinFailed, this, CharPointer_UTF8 (e->group), CharPointer_UTF8 (e->user)); - - } else { - DBG("bug bad result on join event"); - } - +#endif + default: break; + } + + return kAooOk; +} + +void SonobusAudioProcessor::handleEvents() +{ + const ScopedReadLock sl (mCoreLock); + int32_t dummy = 0; + +#if 1 + if (mAooCommonSource->eventsAvailable() > 0) { + mAooCommonSource->pollEvents(); + } +#endif + + for (auto & remote : mRemotePeers) { + if (remote->oursource) { + remote->oursource->pollEvents(); + } + if (remote->oursink) { + remote->oursink->pollEvents(); } - case AOONET_CLIENT_PEER_LEAVE_EVENT: - { - aoonet_client_peer_event *e = (aoonet_client_peer_event *)events[i]; - if (e->result > 0){ + } + + +#if 0 + if (mAooServer /*&& mAooServer->events_available()*/) { + ProcessorIdPair pp(this, dummy); + mAooServer->handle_events(gHandleServerEvents, &pp); + } + + if (mAooClient && mAooClient->events_available()) { + ProcessorIdPair pp(this, dummy); + mAooClient->handle_events(gHandleClientEvents, &pp); + } + + + if (mAooDummySource->events_available() > 0) { + mAooDummySource->get_id(dummy); + ProcessorIdPair pp(this, dummy); + mAooDummySource->handle_events(gHandleSourceEvents, &pp); + } + + for (auto & remote : mRemotePeers) { + if (remote->oursource) { + remote->oursource->get_id(dummy); + ProcessorIdPair pp(this, dummy); + remote->oursource->handle_events(gHandleSourceEvents, &pp); + } + if (remote->oursink) { + remote->oursink->get_id(dummy); + ProcessorIdPair pp(this, dummy); + remote->oursink->handle_events(gHandleSinkEvents, &pp); + + } + + } +#endif +} + +void SonobusAudioProcessor::sendPingEvent(RemotePeer * peer) +{ + + auto tt = aoo::time_tag::now(); + + char buf[AOO_MAX_PACKET_SIZE]; + osc::OutboundPacketStream outmsg(buf, sizeof(buf)); + + try { + outmsg << osc::BeginMessage(SONOBUS_FULLMSG_PING) + << osc::TimeTag(tt) + << osc::EndMessage; + } + catch (const osc::Exception& e){ + DBG("exception in ping message construction: " << e.what()); + return; + } + + sendPeerMessage(peer, (AooByte*)outmsg.Data(), (int32_t) outmsg.Size()); + + DBG("Sent ping to peer: " << peer->endpoint->ipaddr); +} + + +void SonobusAudioProcessor::handlePingEvent(EndpointState * endpoint, uint64_t tt1, uint64_t tt2, uint64_t tt3) +{ + double diff1 = aoo::time_tag::duration(tt1, tt2) * 1000.0; + double diff2 = aoo::time_tag::duration(tt2, tt3) * 1000.0; + double rtt = aoo::time_tag::duration(tt1, tt3) * 1000.0; + + const ScopedReadLock sl (mCoreLock); + + auto * peer = findRemotePeer(endpoint, -1); + if (!peer) return; + + // smooth it + peer->pingTime = rtt; // * 0.5; + if (rtt < 600.0 ) { + peer->smoothPingTime.Z *= 0.5f; + peer->smoothPingTime.push(peer->pingTime); + } - DBG("Peer leave group " << e->group << " - user " << e->user); + DBG("ping recvd from " << peer->endpoint->ipaddr << ":" << peer->endpoint->port << " -- " << diff1 << " " << diff2 << " " << rtt << " smooth: " << peer->smoothPingTime.xbar << " stdev: " <smoothPingTime.s2xx); - EndpointState * endpoint = findOrAddRawEndpoint(e->address); - if (endpoint) { - - removeAllRemotePeersWithEndpoint(endpoint); - } - - //aoo_node_remove_peer(x->x_node, gensym(e->group), gensym(e->user)); - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientPeerLeft, this, CharPointer_UTF8 (e->group), CharPointer_UTF8 (e->user)); - } else { - DBG("bug bad result on leave event"); - } - break; - } - case AOONET_CLIENT_ERROR_EVENT: - { - aoonet_client_event *e = (aoonet_client_event *)events[i]; - DBG("client error: " << String::fromUTF8(e->errormsg)); - clientListeners.call(&SonobusAudioProcessor::ClientListener::aooClientError, this, String::fromUTF8(e->errormsg)); - break; - } - default: - DBG("Got unknown client event: " << events[i]->type); - break; - } + if (!peer->hasRealLatency) { + peer->totalEstLatency = peer->smoothPingTime.xbar + 2*peer->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); } - return 1; + + peer->gotNewStylePing = true; } -int SonobusAudioProcessor::connectRemotePeerRaw(void * sockaddr, const String & username, const String & groupname, bool reciprocate) + +bool SonobusAudioProcessor::connectRemotePeerRaw(const void * sockaddr, int addrlen, AooId userid, const String & username, const String & groupname, AooId groupid, bool reciprocate) { - EndpointState * endpoint = findOrAddRawEndpoint(sockaddr); + EndpointState * endpoint = findOrAddRawEndpoint(sockaddr, addrlen); if (!endpoint) { DBG("Error getting endpoint from raw address"); - return 0; + return false; } - RemotePeer * remote = doAddRemotePeerIfNecessary(endpoint, AOO_ID_NONE, username, groupname); // get new one + return connectRemotePeerInternal(endpoint, userid, username, groupname, groupid, reciprocate); +} - remote->recvAllow = !mMainRecvMute.get(); - - // special - use 0 - bool ret = remote->oursink->invite_source(endpoint, 0, endpoint_send) == 1; - - if (ret) { - DBG("Successfully invited remote peer at " << endpoint->ipaddr << ":" << endpoint->port << " - ourId " << remote->ourId); - remote->connected = true; - remote->invitedPeer = reciprocate; - //remote->recvActive = reciprocate; - if (!mMainSendMute.get()) { - remote->sendActive = true; - remote->oursource->start(); - updateRemotePeerUserFormat(-1, remote); - sendRemotePeerInfoUpdate(-1, remote); - } - else { - remote->sendActive = false; - remote->oursource->stop(); - } - sendBlockedInfoMessage(remote->endpoint, false); - - } else { - DBG("Error inviting remote peer at " << endpoint->ipaddr << ":" << endpoint->port << " - ourId " << remote->ourId); - } +bool SonobusAudioProcessor::connectRemotePeer(const String & host, int port, AooId userid, const String & username, const String & groupname, AooId groupid, bool reciprocate) +{ + EndpointState * endpoint = findOrAddEndpoint(host, port); - return ret; + return connectRemotePeerInternal(endpoint, userid, username, groupname, groupid, reciprocate); } -int SonobusAudioProcessor::connectRemotePeer(const String & host, int port, const String & username, const String & groupname, bool reciprocate) +bool SonobusAudioProcessor::connectRemotePeerInternal(EndpointState * endpoint, AooId userid, const String & username, const String & groupname, AooId groupid, bool reciprocate) { - EndpointState * endpoint = findOrAddEndpoint(host, port); + if (!endpoint) { + DBG("No endpoint passed to connectRemotePeerInternal"); + return false; + } + + RemotePeer * remote = doAddRemotePeerIfNecessary(endpoint, userid, userid, username, groupname, groupid); // get new one - RemotePeer * remote = doAddRemotePeerIfNecessary(endpoint, AOO_ID_NONE, username, groupname); // get new one + endpoint->groupid = groupid; + endpoint->userid = userid; remote->recvAllow = !mMainRecvMute.get(); - // special - use 0 - bool ret = remote->oursink->invite_source(endpoint, 0, endpoint_send) == 1; + // if we use their userid as the source id to get their common source + + // or - use our userid as source id to get their source specific to us + + AooId sourceid = userid; + + AooEndpoint aep = { endpoint->address.address_ptr(), (AooAddrSize) endpoint->address.length(), sourceid }; + remote->remoteSourceId = sourceid; + + bool ret = remote->oursink->inviteSource(aep, nullptr) == kAooOk; if (ret) { - DBG("Successfully invited remote peer at " << host << ":" << port << " - ourId " << remote->ourId); + DBG("Successfully invited remote peer at " << endpoint->address.name_unmapped() << ":" << endpoint->address.port() << " - ourId " << remote->ourId); remote->connected = true; remote->invitedPeer = reciprocate; //remote->recvActive = reciprocate; if (!mMainSendMute.get()) { remote->sendActive = true; - remote->oursource->start(); + remote->oursource->startStream(nullptr); updateRemotePeerUserFormat(-1, remote); } @@ -4336,12 +4496,13 @@ int SonobusAudioProcessor::connectRemotePeer(const String & host, int port, cons sendBlockedInfoMessage(remote->endpoint, false); } else { - DBG("Error inviting remote peer at " << host << ":" << port << " - ourId " << remote->ourId); + DBG("Error inviting remote peer at " << endpoint->address.name_unmapped() << ":" << endpoint->address.port() << " - ourId " << remote->ourId); } return ret; } + bool SonobusAudioProcessor::disconnectRemotePeer(const String & host, int port, int32_t ourId) { EndpointState * endpoint = findOrAddEndpoint(host, port); @@ -4355,20 +4516,23 @@ bool SonobusAudioProcessor::disconnectRemotePeer(const String & host, int port, if (remote) { if (remote->oursink) { DBG("uninviting all remote source " << remote->remoteSourceId); - ret = remote->oursink->uninvite_all(); + ret = remote->oursink->uninviteAll(); //ret = remote->oursink->uninvite_source(endpoint, remote->remoteSourceId, endpoint_send) == 1; } // if we auto-invited the other end's remote source, remove that as a dest sink if (remote->oursource) { DBG("removing all remote sink " << remote->remoteSinkId); - remote->oursource->remove_all(); + remote->oursource->removeAll(); //ret |= remote->oursource->remove_sink(endpoint, remote->remoteSinkId) == 1; } remote->connected = false; remote->recvActive = false; remote->sendActive = false; + + endpoint->groupid = kAooIdInvalid; + endpoint->userid = kAooIdInvalid; } } @@ -4395,13 +4559,13 @@ bool SonobusAudioProcessor::disconnectRemotePeer(int index) remote = mRemotePeers.getUnchecked(index); if (remote->oursink) { DBG("uninviting all remote source " << remote->remoteSourceId); - ret = remote->oursink->uninvite_all(); + ret = remote->oursink->uninviteAll(); } // if we auto-invited the other end's remote source, remove that as a dest sink if (remote->oursource && remote->remoteSinkId >= 0) { DBG("removing all remote sink " << remote->remoteSinkId); - remote->oursource->remove_all(); + remote->oursource->removeAll(); //ret |= remote->oursource->remove_sink(remote->endpoint, remote->remoteSinkId) == 1; } @@ -4507,6 +4671,14 @@ bool SonobusAudioProcessor::removeAllRemotePeers() { const ScopedWriteLock slw (mCoreLock); mRemotePeers.clearQuick(false); // not deleting objects here + + for (int index = 0; index < removed.size(); ++index) { + auto remote = removed.getUnchecked(index); + + mAooClient->removeSink(remote->oursink.get()); + + mAooClient->removeSource(remote->oursource.get()); + } } // reset matrix @@ -4548,6 +4720,10 @@ bool SonobusAudioProcessor::removeRemotePeer(int index, bool sendblock) { const ScopedWriteLock slw (mCoreLock); + mAooClient->removeSink(remote->oursink.get()); + + mAooClient->removeSource(remote->oursource.get()); + mRemotePeers.remove(index, false); // not deleting in scoped write lock } @@ -5017,6 +5193,12 @@ void SonobusAudioProcessor::setRemotePeerNominalSendChannelCount(int index, int updateRemotePeerSendChannels(i, remote); } } + + if (index < 0) { + setupSourceFormat(nullptr, mAooCommonSource.get()); + int mainsendchans = mSendChannels.get() <= 0 ? mActiveSendChannels : mSendChannels.get(); + mAooCommonSource->setup(mainsendchans, getSampleRate(), currSamplesPerBlock, 0); + } } int SonobusAudioProcessor::getRemotePeerActualSendChannelCount(int index) const @@ -5037,8 +5219,8 @@ void SonobusAudioProcessor::updateDynamicResampling() bool newval = mDynamicResampling.get(); for (int i=0; i < mRemotePeers.size(); ++i) { RemotePeer * remote = mRemotePeers.getUnchecked(i); - remote->oursink->set_dynamic_resampling(newval ? 1 : 0); - remote->oursource->set_dynamic_resampling(newval ? 1 : 0); + remote->oursink->setDynamicResampling(newval ? 1 : 0); + remote->oursource->setDynamicResampling(newval ? 1 : 0); } } @@ -5106,7 +5288,7 @@ void SonobusAudioProcessor::updateRemotePeerSendChannels(int index, RemotePeer * setupSourceFormat(remote, remote->oursource.get()); //setupSourceUserFormat(remote, remote->oursource.get()); - remote->oursource->setup(getSampleRate(), currSamplesPerBlock, remote->sendChannels); + remote->oursource->setup(remote->sendChannels, getSampleRate(), currSamplesPerBlock, 0); updateRemotePeerUserFormat(index); } @@ -5144,9 +5326,7 @@ void SonobusAudioProcessor::setRemotePeerBufferTime(int index, float bufferMs) RemotePeer * remote = mRemotePeers.getUnchecked(index); remote->buffertimeMs = bufferMs; remote->totalEstLatency = remote->smoothPingTime.xbar + 2*remote->buffertimeMs + (1e3*currSamplesPerBlock/getSampleRate()); - remote->oursink->set_buffersize(remote->buffertimeMs); // ms - remote->echosink->set_buffersize(remote->buffertimeMs); - remote->latencysink->set_buffersize(remote->buffertimeMs); + remote->oursink->setLatency(remote->buffertimeMs * 1e-3); remote->fillRatioSlow.reset(); remote->fillRatio.reset(); remote->netBufAutoBaseline = (1e3*currSamplesPerBlock/getSampleRate()); // at least a process block @@ -5261,10 +5441,12 @@ void SonobusAudioProcessor::setRemotePeerRecvActive(int index, bool active) #if 1 if (active) { DBG("inviting peer " << remote->ourId << " source " << remote->remoteSourceId); - remote->oursink->invite_source(remote->endpoint,remote->remoteSourceId, endpoint_send); + AooEndpoint aep = { remote->endpoint->address.address_ptr(), (AooAddrSize) remote->endpoint->address.length(), remote->remoteSourceId }; + remote->oursink->inviteSource(aep, nullptr); } else { DBG("uninviting peer " << remote->ourId << " source " << remote->remoteSourceId); - remote->oursink->uninvite_source(remote->endpoint, remote->remoteSourceId, endpoint_send); + AooEndpoint aep = { remote->endpoint->address.address_ptr(), (AooAddrSize) remote->endpoint->address.length(), remote->remoteSourceId }; + remote->oursink->uninviteSource(aep); } #endif } @@ -5468,70 +5650,6 @@ bool SonobusAudioProcessor::getRemotePeerLatencyInfo(int index, LatencyInfo & re if (index < mRemotePeers.size()) { RemotePeer * remote = mRemotePeers.getUnchecked(index); -#if 1 - if (remote->activeLatencyTest && remote->latencyMeasurer) { - if (remote->latencyMeasurer->state > 1 /*remote->latencyMeasurer->measurementCount */) { - - DBG("Latency calculated: " << remote->latencyMeasurer->latencyMs); - - remote->totalLatency = remote->latencyMeasurer->latencyMs; - remote->bufferTimeAtRealLatency = remote->buffertimeMs; - remote->hasRealLatency = true; - remote->latencyDirty = false; - remote->totalEstLatency = remote->totalLatency; - } - else { - DBG("Latency not calculated yet..."); - - } - } -#else - if (remote->activeLatencyTest && remote->latencyProcessor) { - if (remote->latencyProcessor->resolve() < 0) { - DBG("Latency Signal below threshold..."); - - } - else - { - const bool excludeBuffer = false; - - double d, dcapt, dplay, t; - bool inv = false; - bool quest = false; - t = 1000.0 / getSampleRate(); - dcapt = currSamplesPerBlock; - dplay = currSamplesPerBlock; - - if (remote->latencyProcessor->err () > 0.35f) - { - remote->latencyProcessor->invert (); - remote->latencyProcessor->resolve (); - } - - d = remote->latencyProcessor->del (); - - if (excludeBuffer) { - d -= dcapt + dplay; - } - if (remote->latencyProcessor->err () > 0.30f) { - //printf ("??? "); - quest = true; - } - else if (remote->latencyProcessor->inv ()) { - inv = true; - } - - DBG("Peer " << index << ": " << d << " frames " << d*t << " ms - " << (quest ? "???" : (inv ? "Inv" : ""))); - - remote->totalLatency = d*t; - remote->hasRealLatency = true; - remote->latencyDirty = false; - estimated = false; - return remote->totalLatency; - } - } -#endif - retinfo.pingMs = remote->smoothPingTime.xbar; if (remote->hasRemoteInfo) { @@ -5593,17 +5711,6 @@ bool SonobusAudioProcessor::getRemotePeerLatencyInfo(int index, LatencyInfo & re return false; } -bool SonobusAudioProcessor::isRemotePeerLatencyTestActive(int index) -{ - const ScopedReadLock sl (mCoreLock); - if (index < mRemotePeers.size()) { - RemotePeer * remote = mRemotePeers.getUnchecked(index); - return remote->activeLatencyTest; - } - return false; -} - - bool SonobusAudioProcessor::isAnyRemotePeerRecording() const { const ScopedReadLock sl (mCoreLock); @@ -5625,74 +5732,22 @@ bool SonobusAudioProcessor::isRemotePeerRecording(int index) const } -bool SonobusAudioProcessor::startRemotePeerLatencyTest(int index, float durationsec) -{ - const ScopedReadLock sl (mCoreLock); - if (index < mRemotePeers.size()) { - RemotePeer * remote = mRemotePeers.getUnchecked(index); - if (!remote->activeLatencyTest) { - // invite remote's echosource to send to our latency sink - - remote->latencysink->uninvite_all(); - remote->latencysink->reset(); - remote->latencysource->remove_all(); - - remote->latencysink->invite_source(remote->endpoint, remote->remoteSourceId+ECHO_ID_OFFSET, endpoint_send); - - // start our latency source sending to remote's echosink - remote->latencysource->add_sink(remote->endpoint, remote->remoteSinkId+ECHO_ID_OFFSET, endpoint_send); - remote->latencysource->start(); - -#if 1 - remote->latencyMeasurer->measurementCount = 10000; - //remote->latencyMeasurer->initializeWithThreshold(-50.0f); - remote->latencyMeasurer->overrideThreshold = 0.2f; - remote->latencyMeasurer->noiseMeasureTime = 0.2f; - remote->latencyMeasurer->toggle(true); - -#endif - remote->hasRealLatency = false; - remote->activeLatencyTest = true; - } - return true; - } - return false; -} - -bool SonobusAudioProcessor::stopRemotePeerLatencyTest(int index) -{ - const ScopedReadLock sl (mCoreLock); - if (index < mRemotePeers.size()) { - RemotePeer * remote = mRemotePeers.getUnchecked(index); - if (remote->activeLatencyTest) { - // uninvite remote's echosource - remote->latencysink->uninvite_all(); - - // stop our latency source sending to anyone - remote->latencysource->remove_all(); - remote->latencysource->stop(); - - remote->activeLatencyTest = false; - } - return true; - } - return false; -} - - void SonobusAudioProcessor::setRemotePeerSendActive(int index, bool active) { const ScopedReadLock sl (mCoreLock); if (index < mRemotePeers.size()) { RemotePeer * remote = mRemotePeers.getUnchecked(index); + AooEndpoint aend = {remote->endpoint->address.address(), (AooAddrSize)remote->endpoint->address.length(), remote->remoteSinkId}; remote->sendActive = active; if (active) { remote->sendAllow = true; // implied remote->sendAllowCache = true; // implied - remote->oursource->start(); + remote->oursource->startStream(nullptr); + mAooCommonSource->activate(aend, true); // maybe remove/add instead? XXX } else { - remote->oursource->stop(); + remote->oursource->stopStream(); + mAooCommonSource->activate(aend, false); } } } @@ -5788,41 +5843,9 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::findRemotePeerByRemo return retpeer; } -SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::findRemotePeerByEchoId(EndpointState * endpoint, int32_t echoId) -{ - const ScopedReadLock sl (mCoreLock); - - RemotePeer * retpeer = 0; - - for (auto s : mRemotePeers) { - if (s->endpoint == endpoint && s->ourId+ECHO_ID_OFFSET == echoId) { - retpeer = s; - break; - } - } - - return retpeer; -} - -SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::findRemotePeerByLatencyId(EndpointState * endpoint, int32_t latId) -{ - const ScopedReadLock sl (mCoreLock); - - RemotePeer * retpeer = 0; - - for (auto s : mRemotePeers) { - if (s->endpoint == endpoint && s->ourId+LATENCY_ID_OFFSET == latId) { - retpeer = s; - break; - } - } - - return retpeer; -} - -SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNecessary(EndpointState * endpoint, int32_t ourId, const String & username, const String & groupname) +SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNecessary(EndpointState * endpoint, int32_t ourId, AooId userid, const String & username, const String & groupname, AooId groupid) { const ScopedReadLock sl (mCoreLock); @@ -5840,7 +5863,7 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece if (doadd) { // find free id - int32_t newid = 1; + int32_t newid = userid >= 0 ? userid : 1; bool hasit = false; while (!hasit) { bool safe = true; @@ -5860,8 +5883,10 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece retpeer->userName = username; + retpeer->userId = userid; retpeer->groupName = groupname; - + retpeer->groupId = groupid; + retpeer->buffertimeMs = mBufferTime.get() * 1000.0f; retpeer->formatIndex = mDefaultAudioFormatIndex; retpeer->autosizeBufferMode = (AutoNetBufferMode) defaultAutoNetbufMode; @@ -5883,61 +5908,51 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece } retpeer->resetSafetyMuted = retpeer->buffertimeMs < 3.0f; - retpeer->blockedUs = false; - - retpeer->oursink->setup(getSampleRate(), currSamplesPerBlock, getMainBusNumOutputChannels()); - retpeer->oursink->set_buffersize(retpeer->buffertimeMs); - int32_t flags = AOO_PROTOCOL_FLAG_COMPACT_DATA; - retpeer->oursink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); + retpeer->oursinkpp.processor = this; + retpeer->oursink->getId(retpeer->oursinkpp.id); + retpeer->oursink->setEventHandler( + [](void *user, const AooEvent *event, int32_t level){ + auto * pp = static_cast(user); + pp->processor->handleAooSinkEvent(event, level, pp->id); + }, &retpeer->oursinkpp, kAooEventModePoll); - retpeer->nominalSendChannels = mSendChannels.get(); - retpeer->sendChannels = mSendChannels.get() <= 0 ? mActiveSendChannels : mSendChannels.get(); - setupSourceFormat(retpeer, retpeer->oursource.get()); - float sendbufsize = jmax(10.0, SENDBUFSIZE_SCALAR * 1000.0f * currSamplesPerBlock / getSampleRate()); - retpeer->oursource->setup(getSampleRate(), currSamplesPerBlock, retpeer->sendChannels); - retpeer->oursource->set_buffersize(sendbufsize); - retpeer->oursource->set_packetsize(retpeer->packetsize); - //setupSourceUserFormat(retpeer, retpeer->oursource.get()); + retpeer->oursourcepp.processor = this; + retpeer->oursource->getId(retpeer->oursourcepp.id); + retpeer->oursource->setEventHandler( + [](void *user, const AooEvent *event, int32_t level){ + auto * pp = static_cast(user); + pp->processor->handleAooSourceEvent(event, level, pp->id); + }, &retpeer->oursourcepp, kAooEventModePoll); - setupSourceFormat(retpeer, retpeer->latencysource.get(), true); - retpeer->latencysource->setup(getSampleRate(), currSamplesPerBlock, 1); - retpeer->latencysource->set_packetsize(retpeer->packetsize); - setupSourceFormat(retpeer, retpeer->echosource.get(), true); - retpeer->echosource->setup(getSampleRate(), currSamplesPerBlock, 1); - retpeer->echosource->set_buffersize(1000.0f * currSamplesPerBlock / getSampleRate()); - retpeer->echosource->set_packetsize(retpeer->packetsize); - retpeer->latencysink->setup(getSampleRate(), currSamplesPerBlock, 1); - retpeer->echosink->setup(getSampleRate(), currSamplesPerBlock, 1); + retpeer->blockedUs = false; - retpeer->latencysink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); - retpeer->echosink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); + retpeer->oursink->setup(getMainBusNumOutputChannels(), getSampleRate(), currSamplesPerBlock, 0); + //retpeer->oursink->setBufferSize(retpeer->buffertimeMs * 1e-3); + retpeer->oursink->setLatency(retpeer->buffertimeMs * 1e-3); - retpeer->latencysink->set_buffersize(retpeer->buffertimeMs); - retpeer->echosink->set_buffersize(retpeer->buffertimeMs); + //int32_t flags = AOO_PROTOCOL_FLAG_COMPACT_DATA; + //retpeer->oursink->set_option(aoo_opt_protocol_flags, &flags, sizeof(int32_t)); - // never dynamic resampling the latency and echo ones - retpeer->latencysink->set_dynamic_resampling(0); - retpeer->echosink->set_dynamic_resampling(0); - retpeer->latencysource->set_dynamic_resampling(0); - retpeer->echosource->set_dynamic_resampling(0); + retpeer->nominalSendChannels = mSendChannels.get(); + retpeer->sendChannels = mSendChannels.get() <= 0 ? mActiveSendChannels : mSendChannels.get(); + setupSourceFormat(retpeer, retpeer->oursource.get()); + float sendbufsize = jmax(10.0, SENDBUFSIZE_SCALAR * 1000.0f * currSamplesPerBlock / getSampleRate()); + retpeer->oursource->setup(retpeer->sendChannels, getSampleRate(), currSamplesPerBlock, 0); + retpeer->oursource->setBufferSize(sendbufsize * 1e-3); + retpeer->oursource->setPacketSize(retpeer->packetsize); + //setupSourceUserFormat(retpeer, retpeer->oursource.get()); - retpeer->oursource->set_ping_interval(2000); - retpeer->latencysource->set_ping_interval(2000); - retpeer->echosource->set_ping_interval(2000); + retpeer->oursource->setPingInterval(2); - retpeer->oursource->set_respect_codec_change_requests(1); - retpeer->latencysource->set_respect_codec_change_requests(1); - retpeer->echosource->set_respect_codec_change_requests(1); + //retpeer->oursource->set_respect_codec_change_requests(1); - //retpeer->latencyProcessor.reset(new MTDM(getSampleRate())); - retpeer->latencyMeasurer.reset(new LatencyMeasurer()); - retpeer->oursink->set_dynamic_resampling(mDynamicResampling.get() ? 1 : 0); - retpeer->oursource->set_dynamic_resampling(mDynamicResampling.get() ? 1 : 0); + retpeer->oursink->setDynamicResampling(mDynamicResampling.get() ? 1 : 0); + retpeer->oursource->setDynamicResampling(mDynamicResampling.get() ? 1 : 0); retpeer->workBuffer.setSize(2, currSamplesPerBlock, false, false, true); @@ -5965,9 +5980,23 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece // now add it, once initialized { const ScopedWriteLock slw (mCoreLock); + + mAooClient->addSink(retpeer->oursink.get(), retpeer->oursinkpp.id); + + mAooClient->addSource(retpeer->oursource.get(), retpeer->oursourcepp.id); + mRemotePeers.add(retpeer); } + + if (retpeer->sendAllow) { + retpeer->oursource->startStream(nullptr); + + retpeer->sendActive = true; + } else { + retpeer->sendActive = false; + } + //updateRemotePeerUserFormat(mRemotePeers.size()-1); } @@ -5981,12 +6010,8 @@ SonobusAudioProcessor::RemotePeer * SonobusAudioProcessor::doAddRemotePeerIfNece if (findAndLoadCacheForPeer(retpeer)) { setupSourceFormat(retpeer, retpeer->oursource.get()); - setupSourceFormat(retpeer, retpeer->latencysource.get(), true); - setupSourceFormat(retpeer, retpeer->echosource.get(), true); - retpeer->oursink->set_buffersize(retpeer->buffertimeMs); - retpeer->latencysink->set_buffersize(retpeer->buffertimeMs); - retpeer->echosink->set_buffersize(retpeer->buffertimeMs); + retpeer->oursink->setLatency(retpeer->buffertimeMs * 1e-3); for (auto i=0; i < retpeer->numChanGroups && i < MAX_CHANGROUPS; ++i) { retpeer->chanGroups[i].commitCompressorParams(); @@ -6121,6 +6146,11 @@ bool SonobusAudioProcessor::removeAllRemotePeersWithEndpoint(EndpointState * end { const ScopedWriteLock slw (mCoreLock); + + mAooClient->removeSink(s->oursink.get()); + + mAooClient->removeSource(s->oursource.get()); + removed.add(mRemotePeers.removeAndReturn(i)); } } @@ -6147,6 +6177,10 @@ bool SonobusAudioProcessor::doRemoveRemotePeerIfNecessary(EndpointState * endpoi { const ScopedWriteLock slw (mCoreLock); + mAooClient->removeSink(s->oursink.get()); + + mAooClient->removeSource(s->oursource.get()); + removed.add(mRemotePeers.removeAndReturn(i)); } break; @@ -6176,30 +6210,19 @@ bool SonobusAudioProcessor::isAnythingRoutedToPeer(int index) const //// -bool SonobusAudioProcessor::formatInfoToAooFormat(const AudioCodecFormatInfo & info, int channels, aoo_format_storage & retformat) { +bool SonobusAudioProcessor::formatInfoToAooFormat(const AudioCodecFormatInfo & info, int channels, AooFormatStorage & retformat) { if (info.codec == CodecPCM) { - aoo_format_pcm *fmt = (aoo_format_pcm *)&retformat; - fmt->header.codec = AOO_CODEC_PCM; - fmt->header.blocksize = currSamplesPerBlock >= info.min_preferred_blocksize ? currSamplesPerBlock : info.min_preferred_blocksize; - fmt->header.samplerate = getSampleRate(); - fmt->header.nchannels = channels; - fmt->bitdepth = info.bitdepth == 2 ? AOO_PCM_INT16 : info.bitdepth == 3 ? AOO_PCM_INT24 : info.bitdepth == 4 ? AOO_PCM_FLOAT32 : info.bitdepth == 8 ? AOO_PCM_FLOAT64 : AOO_PCM_INT16; + AooFormatPcm *fmt = (AooFormatPcm *)&retformat; + + AooFormatPcm_init(fmt, channels, getSampleRate(), currSamplesPerBlock >= info.min_preferred_blocksize ? currSamplesPerBlock : info.min_preferred_blocksize, info.bitdepth == 2 ? kAooPcmInt16 : info.bitdepth == 3 ? kAooPcmInt24 : info.bitdepth == 4 ? kAooPcmFloat32 : info.bitdepth == 8 ? kAooPcmFloat64 : kAooPcmInt16); return true; } else if (info.codec == CodecOpus) { - aoo_format_opus *fmt = (aoo_format_opus *)&retformat; - fmt->header.codec = AOO_CODEC_OPUS; - fmt->header.blocksize = currSamplesPerBlock >= info.min_preferred_blocksize ? currSamplesPerBlock : info.min_preferred_blocksize; - fmt->header.samplerate = getSampleRate(); - fmt->header.nchannels = channels; - fmt->bitrate = info.bitrate * fmt->header.nchannels; - fmt->complexity = info.complexity; - fmt->signal_type = info.signal_type; - fmt->application_type = OPUS_APPLICATION_RESTRICTED_LOWDELAY; - //fmt->application_type = OPUS_APPLICATION_AUDIO; - + AooFormatOpus *fmt = (AooFormatOpus *)&retformat; + AooFormatOpus_init(fmt, channels, getSampleRate(), currSamplesPerBlock >= info.min_preferred_blocksize ? currSamplesPerBlock : info.min_preferred_blocksize, OPUS_APPLICATION_RESTRICTED_LOWDELAY); + return true; } @@ -6207,18 +6230,25 @@ bool SonobusAudioProcessor::formatInfoToAooFormat(const AudioCodecFormatInfo & i } -void SonobusAudioProcessor::setupSourceFormat(SonobusAudioProcessor::RemotePeer * peer, aoo::isource * source, bool latencymode) +void SonobusAudioProcessor::setupSourceFormat(SonobusAudioProcessor::RemotePeer * peer, AooSource * source, bool latencymode) { // have choice and parameters int formatIndex = (!peer || peer->formatIndex < 0) ? mDefaultAudioFormatIndex : peer->formatIndex; if (formatIndex < 0 || formatIndex >= mAudioFormats.size()) formatIndex = 4; //emergency default const AudioCodecFormatInfo & info = mAudioFormats.getReference(formatIndex); - aoo_format_storage f; - int channels = latencymode ? 1 : peer ? peer->sendChannels : getMainBusNumInputChannels(); + AooFormatStorage f; + int channels = latencymode ? 1 : peer ? peer->sendChannels : mSendChannels.get() <= 0 ? mActiveSendChannels : mSendChannels.get(); if (formatInfoToAooFormat(info, channels, f)) { - source->set_format(f.header); + source->setFormat(f.header); + + if (info.codec == CodecOpus) { + // set these this other way + AooSource_setOpusComplexity(source, 0, info.complexity); + AooSource_setOpusSignalType(source, 0, info.signal_type); + AooSource_setOpusBitrate(source, 0, info.bitrate); + } } } @@ -6268,7 +6298,7 @@ ValueTree SonobusAudioProcessor::getSendUserFormatLayoutTree() } -void SonobusAudioProcessor::setupSourceUserFormat(RemotePeer * peer, aoo::isource * source) +void SonobusAudioProcessor::setupSourceUserFormat(RemotePeer * peer, AooSource * source) { // get userformat from send info ValueTree fmttree = getSendUserFormatLayoutTree(); @@ -6280,7 +6310,8 @@ void SonobusAudioProcessor::setupSourceUserFormat(RemotePeer * peer, aoo::isourc fmttree.writeToStream(stream); - source->set_userformat(destData.getData(), (int32_t) destData.getSize()); + JUCE_COMPILER_WARNING ("TODO: USERFORMAT"); + // source->set_userformat(destData.getData(), (int32_t) destData.getSize()); } void SonobusAudioProcessor::updateRemotePeerUserFormat(int index, RemotePeer * onlypeer) @@ -6295,10 +6326,10 @@ void SonobusAudioProcessor::updateRemotePeerUserFormat(int index, RemotePeer * o fmttree.writeToStream(stream); - char buf[AOO_MAXPACKETSIZE]; + char buf[AOO_MAX_PACKET_SIZE]; - if (destData.getSize() > AOO_MAXPACKETSIZE - 100) { + if (destData.getSize() > AOO_MAX_PACKET_SIZE - 100) { DBG("Info too big for packet!"); return; } @@ -6322,8 +6353,8 @@ void SonobusAudioProcessor::updateRemotePeerUserFormat(int index, RemotePeer * o continue; } - DBG("Sending channellayout message to " << i); - this->sendPeerMessage(peer, msg.Data(), (int32_t) msg.Size()); + DBG("Sending channellayout message to " << peer->userId); + this->sendPeerMessage(peer, (AooByte*)msg.Data(), (int32_t) msg.Size()); if (onlypeer && onlypeer == peer) break; if (index >= 0 && index == i) break; @@ -6843,9 +6874,10 @@ void SonobusAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBloc mTransportSource.prepareToPlay(currSamplesPerBlock, getSampleRate()); //mAooSource->set_format(fmt->header); - setupSourceFormat(0, mAooDummySource.get()); - mAooDummySource->setup(sampleRate, samplesPerBlock, getTotalNumInputChannels()); - + setupSourceFormat(0, mAooCommonSource.get()); + int mainsendchans = mSendChannels.get() <= 0 ? mActiveSendChannels : mSendChannels.get(); + mAooCommonSource->setup( mainsendchans, sampleRate, samplesPerBlock, 0); + mAooCommonSource->startStream(nullptr); if (lastInputChannels == 0 || lastOutputChannels == 0 || mInputChannelGroupCount == 0) { @@ -6968,6 +7000,10 @@ void SonobusAudioProcessor::setupSourceFormatsForAll() int inchannels = mActiveSendChannels; // getTotalNumInputChannels(); // getMainBusNumInputChannels(); int outchannels = getMainBusNumOutputChannels(); + int mainsendchans = mSendChannels.get() <= 0 ? mActiveSendChannels : mSendChannels.get(); + mAooCommonSource->setup(mainsendchans, sampleRate, currSamplesPerBlock, 0); + setupSourceFormat(nullptr, mAooCommonSource.get()); + int i=0; for (auto s : mRemotePeers) { if (s->workBuffer.getNumSamples() < currSamplesPerBlock) { @@ -6983,36 +7019,15 @@ void SonobusAudioProcessor::setupSourceFormatsForAll() setupSourceFormat(s, s->oursource.get()); //setupSourceUserFormat(s, s->oursource.get()); - s->oursource->setup(sampleRate, currSamplesPerBlock, s->sendChannels); // todo use inchannels maybe? + s->oursource->setup(s->sendChannels, sampleRate, currSamplesPerBlock, 0); // todo use inchannels maybe? float sendbufsize = jmax(10.0, SENDBUFSIZE_SCALAR * 1000.0f * currSamplesPerBlock / getSampleRate()); - s->oursource->set_buffersize(sendbufsize); + s->oursource->setBufferSize(sendbufsize * 1e-3); } if (s->oursink) { const ScopedWriteLock sl (s->sinkLock); int sinkchan = jmax(outchannels, s->recvChannels); - s->oursink->setup(sampleRate, currSamplesPerBlock, sinkchan); - } - - if (s->latencysource) { - setupSourceFormat(s, s->latencysource.get(), true); - s->latencysource->setup(getSampleRate(), currSamplesPerBlock, 1); - setupSourceFormat(s, s->echosource.get(), true); - s->echosource->setup(getSampleRate(), currSamplesPerBlock, 1); - float sendbufsize = jmax(10.0, SENDBUFSIZE_SCALAR * 1000.0f * currSamplesPerBlock / getSampleRate()); - s->echosource->set_buffersize(sendbufsize); - - s->netBufAutoBaseline = (1e3*currSamplesPerBlock/getSampleRate()); // at least a process block - - { - const ScopedWriteLock sl (s->sinkLock); - - s->latencysink->setup(sampleRate, currSamplesPerBlock, 1); - s->echosink->setup(sampleRate, currSamplesPerBlock, 1); - } - - //s->latencyProcessor.reset(new MTDM(sampleRate)); - //s->latencyMeasurer.reset(new LatencyMeasurer()); + s->oursink->setup(sinkchan, sampleRate, currSamplesPerBlock, 0); } s->recvMeterSource.resize (s->recvChannels, meterRmsWindow); @@ -7186,6 +7201,10 @@ void SonobusAudioProcessor::ensureBuffers(int numSamples) RemotePeer * remote = mRemotePeers.getUnchecked(i); updateRemotePeerSendChannels(i, remote); } + + setupSourceFormat(nullptr, mAooCommonSource.get()); + int mainsendchans = mSendChannels.get() <= 0 ? mActiveSendChannels : mSendChannels.get(); + mAooCommonSource->setup(mainsendchans, getSampleRate(), currSamplesPerBlock, 0); } mTempBufferSamples = jmax(mTempBufferSamples, numSamples); @@ -7321,7 +7340,7 @@ void SonobusAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer // interleaved by keeping the same state. - uint64_t t = aoo_osctime_get(); + uint64_t t = aoo::time_tag::now(); // meter input pre everything inputMeterSource.measureBlock (buffer, 0, numSamples); @@ -7661,7 +7680,7 @@ void SonobusAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer { const ScopedReadLock sl (mCoreLock); - //mAooSource->process( buffer.getArrayOfReadPointers(), numSamples, t); + mAooCommonSource->process( (AooSample**)sendWorkBuffer.getArrayOfReadPointers(), numSamples, t); for (auto & remote : mRemotePeers) { @@ -7693,14 +7712,16 @@ void SonobusAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer remote->workBuffer.clear(0, numSamples); // calculate fill ratio before processing the sink - float retratio = 0.0f; - if (remote->oursink->get_sourceoption(remote->endpoint, remote->remoteSourceId, aoo_opt_buffer_fill_ratio, &retratio, sizeof(retratio)) > 0) { + double retratio = 0.0f; + AooEndpoint aep = { remote->endpoint->address.address_ptr(), (AooAddrSize) remote->endpoint->address.length(), remote->remoteSourceId }; +#if 0 + if (remote->oursink->getBufferFillRatio(aep, retratio) == kAooOk) { remote->fillRatio.Z *= 0.95; remote->fillRatio.push(retratio); remote->fillRatioSlow.Z *= 0.99; remote->fillRatioSlow.push(retratio); } - +#endif { // get audio data coming in from outside into tempbuf @@ -7715,7 +7736,7 @@ void SonobusAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer remote->workBuffer.clear(0, numSamples); - remote->oursink->process((float **)remote->workBuffer.getArrayOfWritePointers(), numSamples, t); + remote->oursink->process((float **)remote->workBuffer.getArrayOfWritePointers(), numSamples, t, nullptr, nullptr); } @@ -7885,46 +7906,11 @@ void SonobusAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer } - remote->oursource->process((const float **)workBuffer.getArrayOfReadPointers(), numSamples, t); + remote->oursource->process((AooSample**)workBuffer.getArrayOfReadPointers(), numSamples, t); //remote->sendMeterSource.measureBlock (workBuffer); - // now process echo and latency stuff - - workBuffer.clear(0, 0, numSamples); - if (remote->echosink->process((float **)workBuffer.getArrayOfWritePointers(), numSamples, t)) { - //DBG("received something from our ECHO sink"); - remote->echosource->process((const float **)workBuffer.getArrayOfReadPointers(), numSamples, t); - } - - - if (remote->activeLatencyTest && remote->latencyMeasurer) { - workBuffer.clear(0, 0, numSamples); - if (remote->latencysink->process((float **)workBuffer.getArrayOfWritePointers(), numSamples, t)) { - //DBG("received something from our latency sink"); - } - - // hear latency measure stuff (recv into right channel) - if (hearlatencytest) { - tempBuffer.addFrom(mainBusOutputChannels > 1 ? 1 : 0, 0, workBuffer, 0, 0, numSamples); - } - -#if 1 - remote->latencyMeasurer->processInput(workBuffer.getWritePointer(0), (int)lrint(getSampleRate()), numSamples); - remote->latencyMeasurer->processOutput(workBuffer.getWritePointer(0)); -#else - remote->latencyProcessor->process(numSamples, workBuffer.getWritePointer(0), workBuffer.getWritePointer(0)); -#endif - - // hear latency measure stuff (send into left channel) - if (hearlatencytest) { - tempBuffer.addFrom(0, 0, workBuffer, 0, 0, numSamples); - } - - - remote->latencysource->process((const float **)workBuffer.getArrayOfReadPointers(), numSamples, t); - } } ++i; @@ -9610,6 +9596,121 @@ double SonobusAudioProcessor::getMonitoringDelayTimeFromAvgPeerLatency(float sca //setMonitoringDelayTimeMs(deltimems); } +// called in NRT thread +AooServerWrapper::AooServerWrapper(SonobusAudioProcessor & proc, int port, const String & password) + : processor_(proc), port_(port) +{ + try { + // setup UDP server + udpserver_.start(port, + [this](auto&&... args) { handleUdpReceive(args...); }); + } catch (const std::exception& e){ + DBG("Error binding UDP: " << e.what()); + return; + } + + try { + // setup TCP server + tcpserver_.start(port, + [this](auto&&... args) { return handleAccept(args...); }, + [this](auto&&... args) { return handleReceive(args...); }); + } catch (const std::exception& e){ + DBG("Error binding TCP: " << e.what()); + udpserver_.stop(); + return; + } + + // success + server_ = ::AooServer::create(nullptr); + if (password.isNotEmpty()) { + server_->setPassword(password.toRawUTF8()); + } + LOG_VERBOSE("AooServer: listening on port " << port); + // first set event handler! + server_->setEventHandler([](void *x, const AooEvent *e, AooThreadLevel level) { + static_cast(x)->handleEvent(e, level); + }, this, kAooEventModeCallback); + // then start network threads + udpthread_ = std::thread([this](){ + udpserver_.run(-1); + }); + tcpthread_ = std::thread([this](){ + tcpserver_.run(); + }); +} + +AooServerWrapper::~AooServerWrapper(){ + if (server_) { + udpserver_.stop(); + if (udpthread_.joinable()) { + udpthread_.join(); + } + + tcpserver_.stop(); + if (tcpthread_.joinable()) { + tcpthread_.join(); + } + } +} + +void AooServerWrapper::handleEvent(const AooEvent *event, AooThreadLevel level){ + + processor_.handleAooServerEvent(event, level); +} + +AooId AooServerWrapper::handleAccept(int e, const aoo::ip_address& addr) { + if (e == 0) { + // reply function + auto replyfn = [](void *x, AooId client, + const AooByte *data, AooSize size) -> AooInt32 { + return static_cast(x)->send(client, data, size); + }; + AooId client; + server_->addClient(replyfn, &tcpserver_, &client); // doesn't fail + return client; + } else { + DBG("AooServer: accept() failed: " << aoo::socket_strerror(e)); + // TODO handle error? + return kAooIdInvalid; + } +} + +void AooServerWrapper::handleReceive(int e, AooId client, const aoo::ip_address& addr, const AooByte *data, AooSize size) { + if (e == 0 && size > 0) { + if (server_->handleClientMessage(client, data, size) != kAooOk) { + server_->removeClient(client); + tcpserver_.close(client); + } + } else { + // remove client! + server_->removeClient(client); + if (e == 0) { + DBG("AooServer: client " << client << " disconnected"); + } else { + DBG("AooServer: TCP error in client " + << client << ": " << aoo::socket_strerror(e)); + } + } +} + +void AooServerWrapper::handleUdpReceive(int e, const aoo::ip_address& addr, + const AooByte *data, AooSize size) { + if (e == 0) { + // reply function + auto replyfn = [](void *x, const AooByte *data, AooInt32 size, + const void *address, AooAddrSize addrlen, AooFlag) -> AooInt32 { + aoo::ip_address addr((const struct sockaddr *)address, addrlen); + return static_cast(x)->send(addr, data, size); + }; + server_->handleUdpMessage(data, size, addr.address(), addr.length(), + replyfn, &udpserver_); + } else { + DBG("AooServer: UDP error: " << aoo::socket_strerror(e)); + // TODO handle error? + } +} + + //============================================================================== diff --git a/Source/SonobusPluginProcessor.h b/Source/SonobusPluginProcessor.h index dfa4e0dc..8923a30d 100644 --- a/Source/SonobusPluginProcessor.h +++ b/Source/SonobusPluginProcessor.h @@ -7,8 +7,16 @@ #include "JuceHeader.h" -#include "aoo/aoo.hpp" -#include "aoo/aoo_net.hpp" +#include "aoo/aoo.h" +#include "aoo/aoo_client.hpp" +#include "aoo/aoo_server.hpp" +#include "aoo/aoo_sink.hpp" +#include "aoo/aoo_source.hpp" +#include "aoo/aoo_source.hpp" +#include "common/udp_server.hpp" +#include "common/tcp_server.hpp" + +#include "common/net_utils.hpp" #include #include @@ -35,6 +43,9 @@ class Metronome; #define DEFAULT_SERVER_HOST "aoo.sonobus.net" +class SonobusAudioProcessor; + + struct AooServerConnectionInfo { AooServerConnectionInfo() {} @@ -63,6 +74,34 @@ struct AooPublicGroupInfo int64 timestamp = 0; // milliseconds since 1970 }; +class AooServerWrapper { +public: + AooServerWrapper(SonobusAudioProcessor & proc, int port, const String & password=""); + ~AooServerWrapper(); + + bool isGood() const { return good; } + +private: + SonobusAudioProcessor & processor_; + int port_; + bool good = false; + + ::AooServer::Ptr server_; + aoo::udp_server udpserver_; + aoo::tcp_server tcpserver_; + std::thread udpthread_; + std::thread tcpthread_; + + void handleEvent(const AooEvent *event, AooThreadLevel level); + + AooId handleAccept(int e, const aoo::ip_address& addr); + + void handleReceive(int e, AooId client, const aoo::ip_address& addr, const AooByte *data, AooSize size); + void handleUdpReceive(int e, const aoo::ip_address& addr, + const AooByte *data, AooSize size); +}; + + struct SBChatEvent { @@ -279,10 +318,11 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue struct RemoteSource; struct RemotePeer; - int32_t handleSourceEvents(const aoo_event ** events, int32_t n, int32_t sourceId); - int32_t handleSinkEvents(const aoo_event ** events, int32_t n, int32_t sinkId); - int32_t handleServerEvents(const aoo_event ** events, int32_t n); - int32_t handleClientEvents(const aoo_event ** events, int32_t n); + int32_t handleAooServerEvent(const AooEvent *event, int32_t level); + int32_t handleAooClientEvent(const AooEvent *event, int32_t level); + int32_t handleAooSinkEvent(const AooEvent *event, int32_t level, int32_t sinkId); + int32_t handleAooSourceEvent(const AooEvent *event, int32_t level, int32_t sourceId); + // server stuff void startAooServer(); @@ -302,7 +342,7 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void setAutoconnectToGroupPeers(bool flag); bool getAutoconnectToGroupPeers() const { return mAutoconnectGroupPeers; } - bool joinServerGroup(const String & group, const String & groupsecret = "", bool isPublic=false); + bool joinServerGroup(const String & group, const String & groupsecret, const String & username, const String & userpass, bool isPublic=false); bool leaveServerGroup(const String & group); String getCurrentJoinedGroup() const ; bool setWatchPublicGroups(bool flag); @@ -318,10 +358,16 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue bool setCurrentUsername(const String & name); String getCurrentUsername() const { return mCurrentUsername; } + AooId getCurrentUserId() const { return mCurrentUserId; } + // peer stuff - + + EndpointState * findEndpoint(const aoo::ip_address & ipaddr); + + EndpointState * findOrAddEndpoint(const aoo::ip_address & ipaddr); EndpointState * findOrAddEndpoint(const String & host, int port); - EndpointState * findOrAddRawEndpoint(void * rawaddr); + EndpointState * findOrAddRawEndpoint(const void * rawaddr, int addrlen); + EndpointState * findOrAddEndpoint(AooId groupid, AooId userid); int getUdpLocalPort() const { return mUdpLocalPort; } IPAddress getLocalIPAddress() const { return mLocalIPAddress; } @@ -329,7 +375,7 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue int getSendChannels() const { return mSendChannels.get(); } - int connectRemotePeer(const String & host, int port, const String & username = "", const String & groupname = "", bool reciprocate=true); + bool connectRemotePeer(const String & host, int port, AooId userid=kAooIdInvalid, const String & username = "", const String & groupname = "", AooId groupid=kAooIdInvalid, bool reciprocate=true); bool disconnectRemotePeer(const String & host, int port, int32_t sourceId); bool disconnectRemotePeer(int index); bool removeRemotePeer(int index, bool sendblock=false); @@ -463,10 +509,6 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue }; bool getRemotePeerLatencyInfo(int index, LatencyInfo & retinfo) const; - - bool startRemotePeerLatencyTest(int index, float durationsec = 1.0); - bool stopRemotePeerLatencyTest(int index); - bool isRemotePeerLatencyTestActive(int index); bool isAnyRemotePeerRecording() const; @@ -658,11 +700,11 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue virtual void aooClientGroupLeft(SonobusAudioProcessor *comp, bool success, const String & group, const String & errmesg="") {} virtual void aooClientPublicGroupModified(SonobusAudioProcessor *comp, const String & group, int count, const String & errmesg="") {} virtual void aooClientPublicGroupDeleted(SonobusAudioProcessor *comp, const String & group, const String & errmesg="") {} - virtual void aooClientPeerPendingJoin(SonobusAudioProcessor *comp, const String & group, const String & user) {} - virtual void aooClientPeerJoined(SonobusAudioProcessor *comp, const String & group, const String & user) {} - virtual void aooClientPeerJoinFailed(SonobusAudioProcessor *comp, const String & group, const String & user) {} - virtual void aooClientPeerJoinBlocked(SonobusAudioProcessor *comp, const String & group, const String & user, const String & address, int port) {} - virtual void aooClientPeerLeft(SonobusAudioProcessor *comp, const String & group, const String & user) {} + virtual void aooClientPeerPendingJoin(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) {} + virtual void aooClientPeerJoined(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) {} + virtual void aooClientPeerJoinFailed(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) {} + virtual void aooClientPeerJoinBlocked(SonobusAudioProcessor *comp, const String & group, const String & user, const String & address, int port, AooId groupId, AooId userId) {} + virtual void aooClientPeerLeft(SonobusAudioProcessor *comp, const String & group, const String & user, AooId groupId, AooId userId) {} virtual void aooClientError(SonobusAudioProcessor *comp, const String & errmesg) {} virtual void aooClientPeerChangedState(SonobusAudioProcessor *comp, const String & mesg) {} virtual void sbChatEventReceived(SonobusAudioProcessor *comp, const SBChatEvent & chatevent) {} @@ -807,6 +849,9 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void setLanguageOverrideCode(const String & code) { mLangOverrideCode = code; } String getLanguageOverrideCode() const { return mLangOverrideCode; } + static int32_t udpsend(void *user, const AooByte *msg, AooInt32 size, + const void *addr, AooAddrSize addrlen, AooFlag flags); + private: //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SonobusAudioProcessor) @@ -844,9 +889,11 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void doSendData(); void handleEvents(); - bool handleOtherMessage(EndpointState * endpoint, const char *msg, int32_t n); + bool handleOtherMessage(EndpointState * endpoint, const AooByte *msg, int32_t n); + + int32_t sendPeerMessage(RemotePeer * peer, const AooByte *msg, int32_t n); - int32_t sendPeerMessage(RemotePeer * peer, const char *msg, int32_t n); + bool connectRemotePeerInternal(EndpointState * endpoint, AooId userid=kAooIdInvalid, const String & username = "", const String & groupname = "", AooId groupid=kAooIdInvalid, bool reciprocate=true); void handleRemotePeerInfoUpdate(RemotePeer * peer, const juce::var & infodata); void sendRemotePeerInfoUpdate(int peerindex = -1, RemotePeer * topeer = nullptr); @@ -858,18 +905,16 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void updateSafetyMuting(RemotePeer * peer); - void setupSourceFormat(RemotePeer * peer, aoo::isource * source, bool latencymode=false); - bool formatInfoToAooFormat(const AudioCodecFormatInfo & info, int channels, aoo_format_storage & retformat); + void setupSourceFormat(RemotePeer * peer, AooSource * source, bool latencymode=false); + bool formatInfoToAooFormat(const AudioCodecFormatInfo & info, int channels, AooFormatStorage & retformat); - void setupSourceUserFormat(RemotePeer * peer, aoo::isource * source); + void setupSourceUserFormat(RemotePeer * peer, AooSource * source); RemotePeer * findRemotePeer(EndpointState * endpoint, int32_t ourId); - RemotePeer * findRemotePeerByEchoId(EndpointState * endpoint, int32_t echoId); - RemotePeer * findRemotePeerByLatencyId(EndpointState * endpoint, int32_t latId); RemotePeer * findRemotePeerByRemoteSourceId(EndpointState * endpoint, int32_t sourceId); RemotePeer * findRemotePeerByRemoteSinkId(EndpointState * endpoint, int32_t sinkId); - RemotePeer * doAddRemotePeerIfNecessary(EndpointState * endpoint, int32_t ourId=AOO_ID_NONE, const String & username={}, const String & groupname={}); + RemotePeer * doAddRemotePeerIfNecessary(EndpointState * endpoint, int32_t ourId=kAooIdInvalid, AooId userid=kAooIdInvalid, const String & username={}, const String & groupname={}, AooId groupid=kAooIdInvalid); bool doRemoveRemotePeerIfNecessary(EndpointState * endpoint, int32_t ourId); bool removeAllRemotePeersWithEndpoint(EndpointState * endpoint); @@ -890,6 +935,9 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void updateRemotePeerSendChannels(int index, RemotePeer * remote); + bool setupCommonAooSource(); + + void setupSourceFormatsForAll(); ValueTree getSendUserFormatLayoutTree(); @@ -897,7 +945,7 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue void restoreLayoutFormatForPeer(RemotePeer * remote, bool resetmulti=false); - int connectRemotePeerRaw(void * sockaddr, const String & username = "", const String & groupname = "", bool reciprocate=true); + bool connectRemotePeerRaw(const void * sockaddr, int addrlen, AooId userid=kAooIdInvalid, const String & username = "", const String & groupname = "", AooId groupid=kAooIdInvalid, bool reciprocate=true); int findFormatIndex(AudioCodecFormatCodec codec, int bitrate, int bitdepth); @@ -1056,19 +1104,26 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue foleys::LevelMeterSource metMeterSource; // AOO stuff - aoo::isource::pointer mAooDummySource; + AooSource::Ptr mAooCommonSource; + + std::unique_ptr mAooServerWrapper; + AooServer::Ptr mAooServer; + - aoo::net::iserver::pointer mAooServer; - aoo::net::iclient::pointer mAooClient; + AooClient::Ptr mAooClient; std::unique_ptr mServerEndpoint; bool mAutoconnectGroupPeers = true; bool mIsConnectedToServer = false; String mCurrentJoinedGroup; + AooId mCurrentJoinedGroupId = kAooIdInvalid; + double mSessionConnectionStamp = 0.0; bool mWatchPublicGroups = false; String mCurrentUsername; + AooId mCurrentUserId = kAooIdInvalid; + AooId mCurrentClientId = kAooIdInvalid; double mPrevSampleRate = 0.0; Atomic mPendingUnmute {false}; // jlc @@ -1077,10 +1132,12 @@ class SonobusAudioProcessor : public AudioProcessor, public AudioProcessorValue // we will add sinks for any peer we invite, as part of a RemoteSource - std::unique_ptr mUdpSocket; + //std::unique_ptr mUdpSocket; + int mUdpSocketHandle = -1; int mUdpLocalPort; IPAddress mLocalIPAddress; - + aoo::ip_address mLocalClientAddress; + class SendThread; class RecvThread; class EventThread; diff --git a/deps/aoo/.git-ci/gitlab-iem.yml b/deps/aoo/.git-ci/gitlab-iem.yml index aba8fd1a..f3e00c9c 100644 --- a/deps/aoo/.git-ci/gitlab-iem.yml +++ b/deps/aoo/.git-ci/gitlab-iem.yml @@ -1,6 +1,232 @@ ---- +### global variables + variables: - SRCDIR: pd/ + PD_VERSION: 0.52-2 +# PD_PATH: only set on Windows + SC_PATH: /tmp/supercollider + +### script snippets + +.script:deps: &script_deps +# submodules + - git submodule update --init +# supercollider + - git clone https://github.com/supercollider/supercollider.git "${SC_PATH}" + - pushd "${SC_PATH}" + - git submodule update --init + - popd + +.script:cmake: &script_cmake + - prefix="$(pwd)/build/${CI_JOB_NAME}" + - rm -rf build + - cmake -H. -Bbuild -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="${OSX_ARCHITECTURES}" + -DCMAKE_INSTALL_PREFIX="$prefix/aoo" -DAOO_STATIC_LIBS=${STATIC_LIBS} -DAOO_SYSTEM_OPUS=OFF + ${PD_EXTENSION:+-DPD_EXTENSION=${PD_EXTENSION}} -DPD_DIR="${PD_PATH}" -DPD_INSTALLDIR="$prefix/pd" + -DSC_SUPERNOVA=${SUPERNOVA} -DSC_INCLUDEDIR="${SC_PATH}" -DSC_INSTALLDIR="$prefix/sc" + -DCMAKE_DEPENDS_USE_COMPILER=OFF + # NOTE: by default, CMake 3.21+ uses the compiler to obtain dependencies for makefiles ('compiler_depends.make'). + # For some reason, on the 'Hopper' runner that file would contain bogus file paths which will cause the following + # invocation of 'cmake --build ./build --target install/strip' to fail with 'multiple target patterns'. + # As a workaround, we force the legacy behavior with -DCMAKE_DEPENDS_USE_COMPILER=OFF. + +### build snippets +.script:make: &script_make + - make -C build VERBOSE=1 +.script:make_install: &script_make_install + - make -C build install/strip VERBOSE=1 + +####################################################################### +### configuration templates (to be used for snapshot and release builds) +.build:script: + stage: build + script: + - *script_make + - *script_make_install + artifacts: + name: ${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${CI_JOB_NAME} + paths: + - "build/${CI_JOB_NAME}/aoo" + - "build/${CI_JOB_NAME}/pd/aoo" + - "build/${CI_JOB_NAME}/sc/aoo" + +.base: + artifacts: + expire_in: 1 week + +.build:linux: &build_linux + extends: .build:script + image: gcc + variables: + SUPERNOVA: 1 + STATIC_LIBS: 1 + before_script: + - apt-get update && apt-get install -y --no-install-recommends make cmake git + puredata-dev puredata + - test "x${TARGETDEBARCH}" != "x" && + { export CC=${TARGETARCH}-gcc; export CXX=${TARGETARCH}-g++; } + - *script_deps + - *script_cmake + +.build:linux_i386: &build_linux_i386 + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:i386 + variables: + PD_EXTENSION: l_i386 + +.build:linux_armhf: &build_linux_armhf + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:armhf + variables: + PD_EXTENSION: l_arm + +.build:linux_arm64: &build_linux_arm64 + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:arm64 + variables: + PD_EXTENSION: l_arm64 + +.build:macos: &build_macos + extends: .build:script + tags: + - bigsur + variables: + OSX_ARCHITECTURES: "arm64;x86_64" + SUPERNOVA: 1 + PD_EXTENSION: d_fat + before_script: + - wget -q -O Pd.tgz http://msp.ucsd.edu/Software/pd-${PD_VERSION}-macosx7.mac.tar.gz + - rm -rf /Applications/Pd*.app/ + - tar -xf Pd.tgz -C /Applications/ + - rm -f Pd.tgz + - *script_deps + - *script_cmake + +.build:w32: &build_w32 + extends: .build:script + tags: + - windows + variables: + IEMCI_CONFIGURATIONS: mingw32 + SUPERNOVA: 1 + STATIC_LIBS: 1 + PD_PATH: /tmp/pd + PD_URL: http://msp.ucsd.edu/Software/pd-${PD_VERSION}-i386.msw.zip + before_script: + - pacman --noconfirm -S "${MINGW_PACKAGE_PREFIX}-cmake" + - wget -q -O Pd.zip ${PD_URL} +# install locally to avoid hassles with Windows vs. Unix file paths. + - rm -rf "${PD_PATH}"; mkdir -p "${PD_PATH}" + - unzip -q Pd.zip -d "${PD_PATH}" + - mv -v "${PD_PATH}"/*/* "${PD_PATH}" + - export CC=gcc; export CXX=g++; + - *script_deps + - *script_cmake + +.build:w64: &build_w64 + extends: .build:w32 + variables: + IEMCI_CONFIGURATIONS: mingw64 + PD_EXTENSION: m_amd64 + PD_URL: http://msp.ucsd.edu/Software/pd-${PD_VERSION}.msw.zip + +### job templates +.Linux: + <<: *build_linux +.Linux_i386: + allow_failure: true + <<: *build_linux_i386 +.Linux_ARMhf: + allow_failure: true + <<: *build_linux_armhf +.Linux_ARM64: + allow_failure: true + <<: *build_linux_arm64 +.Darwin: + <<: *build_macos +.w32: + <<: *build_w32 +.w64: + <<: *build_w64 + +####################################################################### +### create deken packages and (optionally) upload them; +### if you want to automatically upload a package, you need to +### set DEKEN_USERNAME/DEKEN_PASSWORD in the CI-project settings. +### (https://git.iem.at/help/ci/variables/README#variables) +.package: + stage: deploy + image: debian:buster + variables: + DEKEN_ROOT: "yes" + before_script: + - apt-get update && apt-get --no-install-recommends -y install deken zip + script: +# create zip files for all platform (e.g. aoo_v0.3.0_w32.zip) + - rm -f ./*.zip + - root=$(pwd) +# Pd + SuperCollider: + - for dir in ./build/*; do + for lib in pd sc; do + name=aoo_${lib}_${CI_COMMIT_REF_NAME}_$(basename "$dir").zip; + echo create $name; + (cd "$dir/$lib" && zip -r "${root}/${name}" ./aoo) + ; done + ; done +# C/C++ library: + - for dir in ./build/*; do + name=aoo_${CI_COMMIT_REF_NAME}_$(basename "$dir").zip; + echo create $name; + (cd "$dir" && zip -r "${root}/${name}" ./aoo) + ; done +# Deken: + - rm -f ./*.dek + - rm -rf ./pd/aoo + - mkdir -p ./pd/aoo +# create a single deken package containing binaries for all platforms + - for dir in ./build/*/pd/aoo; do cp -r $dir/* ./pd/aoo; done + - deken package --version="${CI_COMMIT_TAG#v}" ./pd/aoo +# upload deken package (optional) + - test -z "${CI_COMMIT_TAG}" || test -z "${DEKEN_USERNAME}" || test -z "${DEKEN_PASSWORD}" || deken upload --no-source-error ./*.dek + artifacts: + name: ${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_package + paths: + - ./*.dek + - ./*.zip + + +####################################################################### +### the actual jobs: -include: - - https://git.iem.at/pd/iem-ci/raw/master/pd-lib-builder/gitlab-iem.yml +### release jobs +Linux: + extends: + - .Linux + - .base +Linux_i386: + extends: + - .Linux_i386 + - .base +Linux_ARMhf: + extends: + - .Linux_ARMhf + - .base +Linux_ARM64: + extends: + - .Linux_ARM64 + - .base +Darwin: + extends: + - .Darwin + - .base +w32: + extends: + - .w32 + - .base +w64: + extends: + - .w64 + - .base +package: + extends: + - .package + - .base diff --git a/deps/aoo/.git-ci/requirements.apt b/deps/aoo/.git-ci/requirements.apt deleted file mode 100644 index 4801f4bf..00000000 --- a/deps/aoo/.git-ci/requirements.apt +++ /dev/null @@ -1 +0,0 @@ -libopus-dev diff --git a/deps/aoo/.git-ci/requirements.brew b/deps/aoo/.git-ci/requirements.brew deleted file mode 100644 index 3f85f81e..00000000 --- a/deps/aoo/.git-ci/requirements.brew +++ /dev/null @@ -1 +0,0 @@ -brew "opus" diff --git a/deps/aoo/.git-ci/requirements.msys2 b/deps/aoo/.git-ci/requirements.msys2 deleted file mode 100644 index 00572003..00000000 --- a/deps/aoo/.git-ci/requirements.msys2 +++ /dev/null @@ -1 +0,0 @@ -@MINGW@opus diff --git a/deps/aoo/.gitattributes b/deps/aoo/.gitattributes index 0793be4f..bb75d008 100644 --- a/deps/aoo/.gitattributes +++ b/deps/aoo/.gitattributes @@ -14,4 +14,4 @@ # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binary -*.ttf binary +*.ttf binary \ No newline at end of file diff --git a/deps/aoo/.gitignore b/deps/aoo/.gitignore index 9dc7134b..6f0e3f39 100644 --- a/deps/aoo/.gitignore +++ b/deps/aoo/.gitignore @@ -11,3 +11,6 @@ lib/src/lib/oscpack/examples** lib/src/lib/oscpack/tests** lib/src/lib/oscpack/ip** +build** +install** +doxygen** diff --git a/deps/aoo/.gitmodules b/deps/aoo/.gitmodules new file mode 100644 index 00000000..36d16ff8 --- /dev/null +++ b/deps/aoo/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/opus"] + path = deps/opus + url = https://github.com/xiph/opus.git diff --git a/deps/aoo/.gitrepo b/deps/aoo/.gitrepo index c4e8632d..90d7d13d 100644 --- a/deps/aoo/.gitrepo +++ b/deps/aoo/.gitrepo @@ -5,8 +5,8 @@ ; [subrepo] remote = https://github.com/essej/aoo.git - branch = sono - commit = e67c763c268b636d5b9ad7b5a5fc24b004890700 - parent = 04a75fd9c344e54952c646a32fd68ef319d91dfe + branch = sono20230530 + commit = afc7f4fd0f9dbcdeb0fb0bb8352cd5ee918fc8ab + parent = 7e762b5a81cbced54ef6a5efeb3e9a5d2b917db9 method = merge - cmdver = 0.4.1 + cmdver = 0.4.5 diff --git a/deps/aoo/CMakeLists.txt b/deps/aoo/CMakeLists.txt new file mode 100644 index 00000000..10e4cd8a --- /dev/null +++ b/deps/aoo/CMakeLists.txt @@ -0,0 +1,301 @@ +cmake_minimum_required (VERSION 3.1) + +message(STATUS "Project: aoo") +project(aoo) + +include(GNUInstallDirs) +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) +include(cmake/test_atomic.cmake) +include(cmake/test_linker_flag.cmake) + +# built everything (including Opus) with -fPIC +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# dependencies +include_directories(deps) + +# Opus +# NOTE: build it first, so we don't pollute it with our own settings +option(AOO_USE_CODEC_OPUS "use Opus codec" ON) +if (AOO_USE_CODEC_OPUS) + option(AOO_SYSTEM_OPUS "use system provided Opus library" OFF) + if (AOO_SYSTEM_OPUS) + set(AOO_OPUS_LDFLAGS "opus" CACHE STRING "Opus linker flags") + message(STATUS "Opus library: ${AOO_OPUS_LDFLAGS}") + else() + message(STATUS "\n*** Opus library ***\n") + + add_subdirectory("deps/opus" EXCLUDE_FROM_ALL) + + # HACK to force local opus lib (needed for MSVC) + add_library(opus_local ALIAS opus) + + set(AOO_OPUS_LDFLAGS opus_local) + endif() +endif() + +message(STATUS "\n*** Global settings ***\n") + +# oscpack +option(AOO_SYSTEM_OSCPACK "use system provided oscpack library" OFF) +if (AOO_SYSTEM_OSCPACK) + set(AOO_OSCPACK_LDFLAGS "oscpack" CACHE STRING "oscpack linker flags") +endif() + +# md5 +option(AOO_SYSTEM_MD5 "use system provided md5 library" OFF) +if (AOO_SYSTEM_MD5) + set(AOO_MD5_LDFLAGS "md5" CACHE STRING "md5 linker flags") +endif() + +if(UNIX AND NOT APPLE AND NOT MINGW) + set(LINUX TRUE) +endif() + +# some MinGW setups don't define WIN32! +if (MINGW AND NOT WIN32) + message(WARNING "Your MinGW setup does not define WIN32") + set(WIN32 TRUE) +endif() + +# check for Clang or AppleClang +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_COMPILER_IS_CLANG 1) +endif() +message(STATUS "CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) +endif() +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") + +if(LINUX AND CMAKE_COMPILER_IS_GNUCXX) + option(AOO_STATIC_LIBS "link with static libraries (libstdc++ and libgcc)" ON) +endif() +if(MINGW) + option(AOO_STATIC_LIBS "link with static libraries (libstdc++, libgcc and phread)" ON) + set(CMAKE_EXECUTABLE_SUFFIX ".exe") +endif() + +# logging +set(AOO_LOG_LEVEL "Warning" CACHE STRING "compile time log level") +set_property(CACHE AOO_LOG_LEVEL PROPERTY STRINGS "None;Error;Warning;Verbose;Debug") +message(STATUS "Log level: ${AOO_LOG_LEVEL}") +add_definitions(-DAOO_LOG_LEVEL=kAooLogLevel${AOO_LOG_LEVEL}) + +# Windows paths +if (WIN32) + # check if "Program Files (x86)" exists (64-bit Windows) and if we compile for 32-bit + set(_pf_x86 "ProgramFiles(x86)") + if (DEFINED ENV{${_pf_x86}} AND (CMAKE_SIZEOF_VOID_P EQUAL 4)) + set(PROGRAMFILES $ENV{${_pf_x86}}) + else() + set(PROGRAMFILES $ENV{PROGRAMFILES}) + endif() + set(APPDATA $ENV{APPDATA}) + set(LOCALAPPDATA $ENV{LOCALAPPDATA}) +endif() + +# use C++17 if possible, otherwise fallback to C++14 +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED OFF) + +# feature tests +if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + add_compile_options("-fvisibility=hidden") + + check_cxx_compiler_flag("-msse" HAVE_CXX_SSE) + check_cxx_compiler_flag("-msse2" HAVE_CXX_SSE2) + check_cxx_compiler_flag("-msse3" HAVE_CXX_SSE3) + check_cxx_compiler_flag("-msse4" HAVE_CXX_SSE4) + check_cxx_compiler_flag("-mfpmath=sse" HAVE_CXX_FPMATH_SSE) + + test_linker_flag("-latomic" AOO_HAVE_LIB_ATOMIC) + + # atomic double support + test_atomic(ATOMIC_DOUBLE "atomic_double.cpp") + # message(STATUS "atomic doubles supported: ${AOO_HAVE_ATOMIC_DOUBLE}") + + # atomic 64-bit integer support + test_atomic(ATOMIC_INT64 "atomic_int64.cpp") + # message(STATUS "atomic 64-bit integers supported: ${AOO_HAVE_ATOMIC_INT64}") + + # pthread_rwlock_t support + if (NOT WIN32) + if (NOT DEFINED AOO_HAVE_PTHREAD_RWLOCK) + message(STATUS "Testing support for pthread_rwlock_t") + + try_compile(RESULT_VAR + "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pthread_rwlock.cpp" + OUTPUT_VARIABLE COMPILE_OUTPUT + CXX_STANDARD 17 + LINK_LIBRARIES "pthread") + + if (RESULT_VAR) + message(STATUS "- ok") + else() + message(STATUS "- failed") + message(VERBOSE ${COMPILE_OUTPUT}) + endif() + + set(AOO_HAVE_PTHREAD_RWLOCK ${RESULT_VAR} CACHE INTERNAL "pthread_rwlock_t support") + endif() + endif() + + # warnings/errors + add_compile_options("-Wall" "-Wextra") + # disable some useless and noisy warnings + add_compile_options("-Wno-unused-parameter" "-Wno-unused-variable" "-Wno-sign-compare") + # some warnings should be errors + add_compile_options("-Werror=return-type") +endif() + +# global optimization flags +if (MSVC) + add_compile_options("/fp:fast") +else() + add_compile_options("-ffast-math" "-funroll-loops" "-fomit-frame-pointer") +endif() +if (HAVE_CXX_SSE) + add_compile_options("-msse") +endif() +if (HAVE_CXX_SSE2) + add_compile_options("-msse2") +endif() +if (HAVE_CXX_SSE3) + add_compile_options("-msse3") +endif() +# people still own old machines that don't support SSE4 +if (FALSE) + check_cxx_compiler_flag("-msse4" HAVE_CXX_SSE4) + if (HAVE_CXX_SSE4) + add_compile_options("-msse4") + endif() +endif() +if (HAVE_CXX_FPMATH_SSE) + add_compile_options("-mfpmath=sse") +endif() +option(AOO_NATIVE "optimize for this machine (not portable!)" OFF) +if (AOO_NATIVE) + add_compile_options("-march=native") +endif() + +# platform specific compile options + +# clang++: +if (CMAKE_COMPILER_IS_CLANG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") +endif() + +# MinGW +if (MINGW) + add_compile_options("-mstackrealign") +endif() + +# atomics +if (AOO_HAVE_ATOMIC_DOUBLE) + add_definitions(-DAOO_HAVE_ATOMIC_DOUBLE) +else() + message(STATUS "No built-in support for atomic doubles - " + "will be emulated in software") +endif() +if (AOO_HAVE_ATOMIC_INT64) + add_definitions(-DAOO_HAVE_ATOMIC_INT64) +else() + message(STATUS "No built-in support for atomic 64-bit integers - " + "will be emulated in software") +endif() +# link with "libatomic" if available and necessary +if (AOO_HAVE_LIB_ATOMIC AND + (NOT AOO_HAVE_ATOMIC_DOUBLE OR NOT AOO_HAVE_ATOMIC_INT64)) + list(APPEND LIBS "-latomic") +endif() +# pthread_rwlock +if (AOO_HAVE_PTHREAD_RWLOCK) + add_definitions(-DAOO_HAVE_PTHREAD_RWLOCK) +endif() + +# opus support +message(STATUS "Use Opus codec: ${AOO_USE_CODEC_OPUS}") +add_definitions(-DAOO_USE_CODEC_OPUS=$) +if (AOO_USE_CODEC_OPUS AND (NOT AOO_SYSTEM_OPUS)) + # because of a quirk in the opus source code structure, + # we can't include "opus/opus_multistream.h" + add_definitions(-DAOO_OPUS_MULTISTREAM_H=) +endif() + +# networking support +option(AOO_NET "Build with networking support" ON) +message(STATUS "Use AOO NET: ${AOO_NET}") +add_definitions(-DAOO_NET=$) + +# platform specific linker flags +if (LINUX) + list(APPEND LIBS "-pthread") + if(AOO_STATIC_LIBS) + list(APPEND LIBS "-static-libstdc++" "-static-libgcc") + endif() + set(CMAKE_BUILD_RPATH_USE_ORIGIN ON) +endif() +if (MINGW) + if (AOO_STATIC_LIBS) + list(APPEND LIBS "-static-libstdc++" "-static-libgcc" "-static -lpthread") + else() + list(APPEND LIBS "-lpthread") + endif() + if (AOO_USE_CODEC_OPUS) + list(APPEND LIBS "ssp") # for fortified functions + endif() +endif() +if (APPLE) + list(APPEND LIBS "-lpthread") +endif() + +# headers +include_directories(".") +include_directories("include") + +# AOO library +message(STATUS "\n*** AOO library ***\n") +add_subdirectory("aoo") + +# Pd external +option(AOO_BUILD_PD_EXTERNAL "build Pd external" ON) +if (AOO_BUILD_PD_EXTERNAL) + if (NOT AOO_NET) + message(FATAL_ERROR "Pd external requires AOO_NET") + endif() + message(STATUS "\n*** Pd external ***\n") + add_subdirectory("pd") +endif() + +# SC extension +option(AOO_BUILD_SC_EXTENSION "build SC extension" ON) +if (AOO_BUILD_SC_EXTENSION) + if (NOT AOO_NET) + message(FATAL_ERROR "SC extension requires AOO_NET") + endif() + message(STATUS "\n*** SC extension ***\n") + add_subdirectory("sc") +endif() + +# aooserver binary +option(AOO_BUILD_SERVER "build AOO server application" ON) +if (AOO_BUILD_SERVER) + if (NOT AOO_NET) + message(FATAL_ERROR "AOO server requires AOO_NET") + endif() + message(STATUS "\n*** AOO server ***\n") + add_subdirectory("server") +endif() + +# test suite +option(AOO_BUILD_TEST_SUITE "build test suite" ON) +if (AOO_BUILD_TEST_SUITE) + message(STATUS "\n*** Test suite ***\n") + add_subdirectory("tests") +endif() diff --git a/deps/aoo/Doxyfile b/deps/aoo/Doxyfile new file mode 100644 index 00000000..5df9c9d6 --- /dev/null +++ b/deps/aoo/Doxyfile @@ -0,0 +1,2644 @@ +# Doxyfile 1.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "AOO" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = C/C++ library for peer-to-peer audio streaming and messaging + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doxygen + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = include/aoo include/aoo/codec README.md + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = README.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /