diff --git a/ear-production-suite-plugins/lib/include/binaural_monitoring_audio_processor.hpp b/ear-production-suite-plugins/lib/include/binaural_monitoring_audio_processor.hpp index 3817499b8..aa830ab50 100644 --- a/ear-production-suite-plugins/lib/include/binaural_monitoring_audio_processor.hpp +++ b/ear-production-suite-plugins/lib/include/binaural_monitoring_audio_processor.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include <../src/dynamic_renderer.hpp> #include "variable_block_adapter.hpp" #include @@ -9,6 +10,20 @@ namespace ear { namespace plugin { +enum BearStatusStates { + NOT_ATTEMPTED = 0, + FAILED, + SUCCEEDED +}; + +struct BearStatus { + bear::Config startupConfig; + BearStatusStates startupSuccess{NOT_ATTEMPTED}; + std::string startupErrorDesc; + BearStatusStates listenerDataSetSuccess{NOT_ATTEMPTED}; + std::string listenerDataSetErrorDesc; +}; + /** * @brief Binaural monitoring plugin dsp implementation * @@ -47,6 +62,8 @@ class BinauralMonitoringAudioProcessor { BinauralMonitoringAudioProcessor& operator=( BinauralMonitoringAudioProcessor&&) = delete; + BearStatus getBearStatus() { return bearStatus; } + template void process(const InBuffer& in, OutBuffer& out) { using InTraits = BufferTraits; @@ -67,7 +84,7 @@ class BinauralMonitoringAudioProcessor { void setListenerOrientation(float quatW, float quatX, float quatY, float quatZ); - bool rendererError() { return !bearRenderer; } + bool rendererStarted() { return bearRenderer && bearStatus.startupSuccess == SUCCEEDED; } void setIsPlaying(bool state) { isPlaying = state; } bool getIsPlaying() { return isPlaying; } @@ -84,6 +101,7 @@ class BinauralMonitoringAudioProcessor { std::shared_ptr bearRenderer; // TODO - why shared? std::mutex bearListenerMutex_; bear::Listener bearListener; + BearStatus bearStatus; bool listenerQuatsDirty{false}; std::array listenerQuats{1.0, 0.0, 0.0, 0.0}; diff --git a/ear-production-suite-plugins/lib/src/binaural_monitoring_audio_processor.cpp b/ear-production-suite-plugins/lib/src/binaural_monitoring_audio_processor.cpp index 9b5384ffe..e9909371b 100644 --- a/ear-production-suite-plugins/lib/src/binaural_monitoring_audio_processor.cpp +++ b/ear-production-suite-plugins/lib/src/binaural_monitoring_audio_processor.cpp @@ -37,19 +37,25 @@ BinauralMonitoringAudioProcessor::BinauralMonitoringAudioProcessor( bearListener.set_position_cart(std::array{0.0, 0.0, 0.0}); + bearStatus.startupConfig = bearConfig; try { bearRenderer = std::make_shared( blockSize, std::max(std::max(maxObjChannels, maxDsChannels), maxHoaChannels)); + bearRenderer->set_config_blocking(bearConfig); + bearStatus.startupSuccess = BearStatusStates::SUCCEEDED; try { bearRenderer->set_listener(bearListener); + bearStatus.listenerDataSetSuccess = BearStatusStates::SUCCEEDED; } catch (std::exception &e) { + bearStatus.listenerDataSetSuccess = BearStatusStates::FAILED; + bearStatus.listenerDataSetErrorDesc = e.what(); bearRenderer.reset(); - assert(false); } } catch (std::exception &e) { + bearStatus.startupSuccess = BearStatusStates::FAILED; + bearStatus.startupErrorDesc = e.what(); bearRenderer.reset(); - assert(false); } } @@ -218,6 +224,7 @@ bool BinauralMonitoringAudioProcessor::updateChannelCounts( // Do immediate update bearRenderer->set_config_blocking(bearConfig); } + return true; } } // namespace plugin diff --git a/ear-production-suite-plugins/plugins/binaural_monitoring/CMakeLists.txt b/ear-production-suite-plugins/plugins/binaural_monitoring/CMakeLists.txt index cbc415d01..0d949db80 100644 --- a/ear-production-suite-plugins/plugins/binaural_monitoring/CMakeLists.txt +++ b/ear-production-suite-plugins/plugins/binaural_monitoring/CMakeLists.txt @@ -3,14 +3,16 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(SOURCES_BINAURAL_MONITORING - ${EPS_SHARED_DIR}/binary_data.cpp - ${EPS_SHARED_DIR}/components/look_and_feel/slider.cpp - ${EPS_SHARED_DIR}/components/ear_header.cpp - ${EPS_SHARED_DIR}/components/ear_slider_label.cpp - ${EPS_SHARED_DIR}/components/level_meter_calculator.cpp + ${EPS_SHARED_DIR}/binary_data.cpp + ${EPS_SHARED_DIR}/components/look_and_feel/slider.cpp + ${EPS_SHARED_DIR}/components/ear_combo_box.cpp + ${EPS_SHARED_DIR}/components/ear_header.cpp + ${EPS_SHARED_DIR}/components/ear_slider_label.cpp + ${EPS_SHARED_DIR}/components/level_meter_calculator.cpp src/binaural_monitoring_frontend_connector.cpp - src/binaural_monitoring_plugin_editor.cpp - src/binaural_monitoring_plugin_processor.cpp + src/binaural_monitoring_plugin_editor.cpp + src/binaural_monitoring_plugin_processor.cpp + src/bear_data_files.cpp src/orientation_osc.cpp ) @@ -25,6 +27,7 @@ set(HEADERS_BINAURAL_MONITORING ${EPS_SHARED_DIR}/binary_data.hpp ${EPS_SHARED_DIR}/components/ear_button.hpp + ${EPS_SHARED_DIR}/components/ear_combo_box.hpp ${EPS_SHARED_DIR}/components/ear_header.hpp ${EPS_SHARED_DIR}/components/ear_inverted_slider.hpp ${EPS_SHARED_DIR}/components/ear_slider.hpp @@ -47,10 +50,11 @@ set(HEADERS_BINAURAL_MONITORING src/binaural_monitoring_frontend_connector.hpp src/binaural_monitoring_plugin_editor.hpp src/binaural_monitoring_plugin_processor.hpp - src/error_overlay.hpp + src/bear_data_files.hpp src/headphone_channel_meter.hpp src/headphone_channel_meter_box.hpp src/orientation_osc.hpp + src/value_box_data_file.hpp src/value_box_orientation.hpp src/value_box_osc.hpp ) @@ -60,7 +64,7 @@ source_group("Header Files" FILES ${HEADERS_BINAURAL_MONITORING}) add_juce_vst3_plugin( ear_binaural_monitoring SOURCES ${SOURCES_BINAURAL_MONITORING} ${HEADERS_BINAURAL_MONITORING} - IDE_FOLDER ${IDE_FOLDER_PLUGINS} + IDE_FOLDER ${IDE_FOLDER_PLUGINS} CODE_SUFFIX "F0" # Speaker Monitoring suffixes start from A0 and increment. For bin, lets use F0 (Note FF is scene) DISPLAY_NAME "EAR Binaural Monitoring" DESCRIPTION "The binaural monitoring plugin" @@ -81,7 +85,7 @@ if(APPLE) add_custom_command(TARGET ear_binaural_monitoring_VST3 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${TENSORFILE_FULLPATH} "$/../Resources/${TENSORFILE_FILENAME}" ) - install(FILES ${TENSORFILE_FULLPATH} DESTINATION "${EPS_PLUGIN_INSTALL_PREFIX}ear-production-suite/EAR Binaural Monitoring.vst3/Contents/Resources") + install(FILES ${TENSORFILE_FULLPATH} DESTINATION "${EPS_PLUGIN_INSTALL_PREFIX}ear-production-suite/EAR Binaural Monitoring.vst3/Contents/Resources") else() add_custom_command(TARGET ear_binaural_monitoring_VST3 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${TENSORFILE_FULLPATH} "$/${TENSORFILE_FILENAME}" diff --git a/ear-production-suite-plugins/plugins/binaural_monitoring/src/bear_data_files.cpp b/ear-production-suite-plugins/plugins/binaural_monitoring/src/bear_data_files.cpp new file mode 100644 index 000000000..8b280089e --- /dev/null +++ b/ear-production-suite-plugins/plugins/binaural_monitoring/src/bear_data_files.cpp @@ -0,0 +1,151 @@ +#include "bear_data_files.hpp" +#include + +namespace { +bool operator==(const ear::plugin::DataFileManager::DataFile& lhs, + const ear::plugin::DataFileManager::DataFile& rhs) { + return (lhs.filename == rhs.filename) && (lhs.fullPath == rhs.fullPath) && + (lhs.isBearRelease == rhs.isBearRelease) && (lhs.label == rhs.label) && + (lhs.description == rhs.description); +} + +juce::File getBearDataFileDirectory() { + auto vstPath = juce::File::getSpecialLocation( + juce::File::SpecialLocationType::currentExecutableFile); + vstPath = vstPath.getParentDirectory(); +#ifdef __APPLE__ + vstPath = vstPath.getParentDirectory(); + vstPath = vstPath.getChildFile("Resources"); +#endif + return vstPath; +}; + +juce::File getCustomDataFileDirectory() { + auto vstPath = juce::File::getSpecialLocation( + juce::File::SpecialLocationType::currentExecutableFile); + vstPath = vstPath.getParentDirectory(); +#ifdef __APPLE__ + // vstPath is `EAR Binaural Monitoring.vst3/Contents/MacOS` - traverse up x3 to get to dir containing vst3 bundle + vstPath = vstPath.getParentDirectory(); + vstPath = vstPath.getParentDirectory(); + vstPath = vstPath.getParentDirectory(); +#endif + return vstPath; +}; + +} + +namespace ear { +namespace plugin { + +DataFileManager::DataFileManager() { + bearReleaseFiles_.add( + getBearDataFileDirectory().getChildFile(BEAR_DATA_FILE)); + updateAvailableFiles(); +} + +void DataFileManager::updateAvailableFiles() { + // Start a new vec and copy existing shared_ptrs of unchanged files + // This retains shared_ptrs unlike clear() and rebuild + std::vector> dfs; + // Lookup tfs where we expect to find custom files + auto files = getCustomDataFileDirectory().findChildFiles( + juce::File::TypesOfFileToFind::findFiles, + false, "*.tf"); + // add our expected released files + for(auto const& bearReleaseFile : bearReleaseFiles_) { + if (bearReleaseFile.existsAsFile()) { + // note that in win, the released bear dir is the same as the custom + // dir, so we might have already found this with findChildFiles + files.addIfNotAlreadyThere(bearReleaseFile); + } + } + for (const auto& file : files) { + auto newDf = std::make_shared(); + newDf->fullPath = file; + newDf->filename = file.getFileName(); + try { + auto md = bear::DataFileMetadata::read_from_file( + file.getFullPathName().toStdString()); + if (md.has_metadata()) { + newDf->label = md.get_label(); + newDf->description = md.get_description(); + newDf->isBearRelease = md.is_released(); + } + } catch (std::exception) { + } + // Use the existing struct where possible (exists and is identical) + // -- this retains the same inst and shared_ptr + auto existingDf = getDataFileInfo(file.getFileName()); + if (existingDf && *existingDf == *newDf) { + dfs.push_back(existingDf); + } else { + dfs.push_back(newDf); + } + } + availableDataFiles_ = dfs; +} + +std::shared_ptr +DataFileManager::getSelectedDataFileInfo() { + return selectedDataFile_; +} + +std::vector> +DataFileManager::getAvailableDataFiles() { + return availableDataFiles_; +} + +int DataFileManager::getAvailableDataFilesCount() { + return availableDataFiles_.size(); +} + +bool DataFileManager::setSelectedDataFile(const juce::String& fullPath) { + return setSelectedDataFile(juce::File(fullPath)); +} + +bool DataFileManager::setSelectedDataFile(const juce::File& fullPath) { + auto found = getDataFileInfo(fullPath); + if (found == nullptr) return false; + if (found != selectedDataFile_) { + selectedDataFile_ = found; + if (selectedDataFileChangeCallback_) { + selectedDataFileChangeCallback_(selectedDataFile_); + } + } + return true; +} + +bool DataFileManager::setSelectedDataFileDefault() { + updateAvailableFiles(); + // look for any released by order + for (auto const& bearReleaseFile : bearReleaseFiles_) { + if (setSelectedDataFile(bearReleaseFile)) { + return true; + } + } + return false; +} + +std::shared_ptr DataFileManager::getDataFileInfo( + const juce::String& fullPath) { + return getDataFileInfo(juce::File(fullPath)); +} + +std::shared_ptr DataFileManager::getDataFileInfo( + const juce::File& fullPath) { + auto it = std::find_if(availableDataFiles_.begin(), availableDataFiles_.end(), + [&fullPath](const std::shared_ptr& elm) { + return elm->fullPath == fullPath; + }); + + return it == availableDataFiles_.end() ? nullptr : *it; +} + +void DataFileManager::onSelectedDataFileChange( + std::function)> callback) { + selectedDataFileChangeCallback_ = callback; +} + +} // namespace plugin +} // namespace ear diff --git a/ear-production-suite-plugins/plugins/binaural_monitoring/src/bear_data_files.hpp b/ear-production-suite-plugins/plugins/binaural_monitoring/src/bear_data_files.hpp new file mode 100644 index 000000000..9528a4340 --- /dev/null +++ b/ear-production-suite-plugins/plugins/binaural_monitoring/src/bear_data_files.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "JuceHeader.h" +#include +#include +#include + +namespace ear { +namespace plugin { + +class DataFileManager { + public: + DataFileManager(); + + struct DataFile { + juce::String filename; + juce::File fullPath; + juce::String label; + juce::String description; + bool isBearRelease{false}; + }; + + void updateAvailableFiles(); + std::shared_ptr getSelectedDataFileInfo(); + std::vector> getAvailableDataFiles(); + int getAvailableDataFilesCount(); + bool setSelectedDataFile(const juce::String& fullPath); + bool setSelectedDataFile(const juce::File& fullPath); + bool setSelectedDataFileDefault(); + std::shared_ptr getDataFileInfo(const juce::String& fullPath); + std::shared_ptr getDataFileInfo(const juce::File& fullPath); + void onSelectedDataFileChange( + std::function)> callback); + + private: + juce::Array bearReleaseFiles_; + std::shared_ptr selectedDataFile_; + std::vector> availableDataFiles_; + std::function)> selectedDataFileChangeCallback_; +}; + +} // namespace plugin +} + diff --git a/ear-production-suite-plugins/plugins/binaural_monitoring/src/binaural_monitoring_frontend_connector.cpp b/ear-production-suite-plugins/plugins/binaural_monitoring/src/binaural_monitoring_frontend_connector.cpp index 662f835a8..381f50685 100644 --- a/ear-production-suite-plugins/plugins/binaural_monitoring/src/binaural_monitoring_frontend_connector.cpp +++ b/ear-production-suite-plugins/plugins/binaural_monitoring/src/binaural_monitoring_frontend_connector.cpp @@ -4,6 +4,18 @@ #include "components/ear_inverted_slider.hpp" #include "detail/weak_ptr_helpers.hpp" +#include +#include + +namespace { +void removePath(std::string& filePath) { + // Find the last occurrence of forward slash or backslash + size_t lastSlash = filePath.find_last_of("/\\"); + if (lastSlash != std::string::npos) { + filePath = filePath.substr(lastSlash + 1); + } +} +} namespace ear { namespace plugin { @@ -172,6 +184,68 @@ void BinauralMonitoringJuceFrontendConnector::setOscInvertQuatZButton( setOscInvertQuatZ(cachedOscInvertQuatZ_); } +void BinauralMonitoringJuceFrontendConnector::setDataFileComponent( + std::shared_ptr comp) { + dataFileComponent_ = comp; + p_->dataFileManager.updateAvailableFiles(); + auto dfs = p_->dataFileManager.getAvailableDataFiles(); + auto df = p_->dataFileManager.getSelectedDataFileInfo(); + if (dfs.size() == 1 && df && dfs[0] == df && df->isBearRelease) { + // Only 1 data file and it is a bear release. Don't show options. + comp->setVisible(false); + } else { + comp->setVisible(true); + } +} + +void BinauralMonitoringJuceFrontendConnector::setDataFileComboBox( + std::shared_ptr comboBox) { + comboBox->addListener(this); + dataFileComboBox_ = comboBox; + p_->dataFileManager.updateAvailableFiles(); + comboBox->clearEntries(); + auto dfs = p_->dataFileManager.getAvailableDataFiles(); + std::sort( + dfs.begin(), dfs.end(), + [](const std::shared_ptr& a, + const std::shared_ptr& b) { + if (a->isBearRelease != b->isBearRelease) { + return a->isBearRelease; + } + return a->filename.compareIgnoreCase(b->filename) < 0; + }); + for (const auto& df : dfs) { + juce::String txt; + if (!df->isBearRelease) { + txt = "[CUSTOM] "; + } + if (df->label.isEmpty()) { + // use filename - will be an unusual case in future + txt += df->filename; + } else { + txt += df->label; + txt += " (" + df->filename + ")"; + } + juce::StringArray subtexts; + if (!df->description.isEmpty()) { + subtexts.add(df->description); + } + subtexts.add(juce::String(df->fullPath.getFullPathName())); + auto entry = comboBox->addTextWithSubtextEntry(txt, subtexts, df->fullPath.getFullPathName()); + entry->setLightFont(!df->isBearRelease); + } + if (auto df = p_->dataFileManager.getSelectedDataFileInfo()) { + comboBox->setSelectedId(df->fullPath.getFullPathName(), + juce::NotificationType::dontSendNotification); + } +} + +void BinauralMonitoringJuceFrontendConnector::setRendererStatusLabel( + std::shared_ptr