From 7dd9bb4c22caadf4c14fa1a18435b881bc49b13a Mon Sep 17 00:00:00 2001 From: Alex Birch Date: Tue, 30 Jul 2019 21:36:54 +0100 Subject: [PATCH] fix choice of preset via plugin host (safely updates table from non-message thread) --- Source/FluidSynthModel.cpp | 59 +++++++--------- Source/PluginProcessor.cpp | 91 +----------------------- Source/TableComponent.cpp | 141 +++---------------------------------- 3 files changed, 37 insertions(+), 254 deletions(-) diff --git a/Source/FluidSynthModel.cpp b/Source/FluidSynthModel.cpp index 24e1b72..9bde407 100644 --- a/Source/FluidSynthModel.cpp +++ b/Source/FluidSynthModel.cpp @@ -417,44 +417,39 @@ void FluidSynthModel::setCurrentProgram(int index) RangedAudioParameter *param{valueTreeState.getParameter("preset")}; jassert(dynamic_cast(param) != nullptr); AudioParameterInt* castParam{dynamic_cast(param)}; + // setCurrentProgram() gets invoked from non-message thread. + // AudioParameterInt#operator= will activate any listeners of audio parameter "preset". + // This includes TableComponent, who will update its UI. + // we need to lock the message thread whilst it does that UI update. + const MessageManagerLock mmLock; *castParam = index; } const String FluidSynthModel::getProgramName(int index) { - // fluid_sfont_t* sfont{ - // sfont_id == -1 - // ? nullptr - // : fluid_synth_get_sfont_by_id(synth.get(), sfont_id) - // }; - // if (!sfont) { - // return {}; - // } - // int bank, presetNum; - // { - // RangedAudioParameter *param {valueTreeState.getParameter("bank")}; - // jassert(dynamic_cast (param) != nullptr); - // AudioParameterInt* castParam {dynamic_cast (param)}; - // bank = castParam->get(); - // } - // { - // RangedAudioParameter *param {valueTreeState.getParameter("preset")}; - // jassert(dynamic_cast (param) != nullptr); - // AudioParameterInt* castParam {dynamic_cast (param)}; - // presetNum = castParam->get(); - // } - // fluid_preset_t *preset{fluid_sfont_get_preset( - // sfont, - // bank, - // presetNum)}; - // if (!preset) { - // return {}; - // } - // return {fluid_preset_get_name(preset)}; + fluid_sfont_t* sfont{ + sfont_id == -1 + ? nullptr + : fluid_synth_get_sfont_by_id(synth.get(), sfont_id) + }; + if (!sfont) { + String presetName{"Preset "}; + return presetName << index; + } + RangedAudioParameter *param{valueTreeState.getParameter("bank")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + int bank{castParam->get()}; - // I think the presets' names will be collected only at synth startup, so we won't yet have loaded the soundfont. - String presetName{"Preset "}; - return presetName << index; + fluid_preset_t *preset{fluid_sfont_get_preset( + sfont, + bank, + index)}; + if (!preset) { + String presetName{"Preset "}; + return presetName << index; + } + return {fluid_preset_get_name(preset)}; } void FluidSynthModel::changeProgramName(int index, const String& newName) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index da7dfa3..1df3466 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -215,30 +215,17 @@ void JuicySFAudioProcessor::getStateInformation (MemoryBlock& destData) // Create an outer XML element.. XmlElement xml{"MYPLUGINSETTINGS"}; -// sharedParams->setAttributesOnXml(xml); -// auto state{valueTreeState.copyState()}; -// unique_ptr xml{state.createXml()}; -// sharedParams.setAttributesOnXml(xml); - -// list::iterator p; -// for(p = stateChangeSubscribers.begin(); p != stateChangeSubscribers.end(); p++) { -// (*p)->getStateInformation(xml); -// } // Store the values of all our parameters, using their param ID as the XML attribute XmlElement* params{xml.createNewChildElement("params")}; for (auto* param : getParameters()) { if (auto* p = dynamic_cast (param)) { -// xml.setAttribute(p->paramID, p->getValue()); -// XmlElement* param{params->createNewChildElement("PARAM")}; -// param->setAttribute(p->paramID, p->getValue()); params->setAttribute(p->paramID, p->getValue()); } } { ValueTree tree{valueTreeState.state.getChildWithName("uiState")}; XmlElement* newElement{xml.createNewChildElement("uiState")}; -// Value value{tree.getPropertyAsValue("width", nullptr)}; { double value{tree.getProperty("width", GuiConstants::minWidth)}; newElement->setAttribute("width", value); @@ -259,10 +246,6 @@ void JuicySFAudioProcessor::getStateInformation (MemoryBlock& destData) DEBUG_PRINT(xml.createDocument("",false,false)); - // then use this helper function to stuff it into the binary blob and return it.. -// if (xml.get() != nullptr) { -// copyXmlToBinary(*xml, destData); -// } copyXmlToBinary(xml, destData); } @@ -272,54 +255,24 @@ void JuicySFAudioProcessor::setStateInformation (const void* data, int sizeInByt // whose contents will have been created by the getStateInformation() call. // This getXmlFromBinary() helper function retrieves our XML from the binary blob.. shared_ptr xmlState{getXmlFromBinary(data, sizeInBytes)}; -// unique_ptr xmlState{getXmlFromBinary(data, sizeInBytes)}; DEBUG_PRINT(xmlState->createDocument("",false,false)); -/* - - - - - - - - - - - - */ + if (xmlState.get() != nullptr) { // make sure that it's actually our type of XML object.. -// if (xmlState->hasTagName ("MYPLUGINSETTINGS")) { if (xmlState->hasTagName(valueTreeState.state.getType())) { - // valueTreeState.replaceState(ValueTree::fromXml(*xmlState)); -// for (auto* param : getParameters()) -// if (auto* p = dynamic_cast(param)) -// p->setValue(static_cast(xmlState->getDoubleAttribute(p->paramID, p->getValue()))); XmlElement* params{xmlState->getChildByName("params")}; - if (params) { + if (params) for (auto* param : getParameters()) if (auto* p = dynamic_cast(param)) - // XmlElement* xmlParam{params->getChildByAttribute("id", p->paramID)}; - // p->setValue(static_cast(xmlState->getDoubleAttribute(p->paramID, p->getValue()))); p->setValue(static_cast(params->getDoubleAttribute(p->paramID, p->getValue()))); - } { - // Value value{valueTreeState.state.getPropertyAsValue("soundFontPath", nullptr)}; - // value = xmlState->getStringAttribute("soundFontPath", value.getValue()); XmlElement* xmlElement{xmlState->getChildByName("soundFont")}; if (xmlElement) { ValueTree tree{valueTreeState.state.getChildWithName("soundFont")}; Value value{tree.getPropertyAsValue("path", nullptr)}; value = xmlElement->getStringAttribute("path", value.getValue()); } - - // valueTreeState.getParameter("soundFontPath")->getValue() - // valueTreeState.getParameter("soundFontPath")->getValue(); - // RangedAudioParameter *param {valueTreeState.getParameter("release")}; - // jassert(dynamic_cast (param) != nullptr); - // AudioParameterInt* castParam {dynamic_cast (param)}; - // *castParam = m.getControllerValue(); } { ValueTree tree{valueTreeState.state.getChildWithName("uiState")}; @@ -334,47 +287,7 @@ void JuicySFAudioProcessor::setStateInformation (const void* data, int sizeInByt value = xmlElement->getIntAttribute("height", value.getValue()); } } - -// tree.getPropertyAsValue("width", nullptr) -// tree. -// valueTreeState.replaceState(ValueTree::fromXml(*xmlState)) -// value = xmlState->getStringAttribute("soundFontPath", value.getValue()); } - -// list::iterator p; -// for(p = stateChangeSubscribers.begin(); p != stateChangeSubscribers.end(); p++) { -// (*p)->setStateInformation(xmlState); -// } - - // ok, now pull out our last window size.. -// sharedParams.loadAttributesFromXml(xmlState); - - // Now reload our parameters.. - -// for (auto* param : getParameters()) -// if (auto* p = dynamic_cast (param)) -// p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue())); -// -// fluidSynthModel.onFileNameChanged( -// sharedParams->getSoundFontPath(), -// sharedParams->getBank(), -// sharedParams->getPreset()); -// -// AudioProcessorEditor* editor{getActiveEditor()}; -// if (editor != nullptr) { -// editor->setSize( -// sharedParams->getUiWidth(), -// sharedParams->getUiHeight()); -// -// jassert(dynamic_cast (editor) != nullptr); -// ExposesComponents* exposesComponents = dynamic_cast (editor); -// exposesComponents->getFilePicker().setDisplayedFilePath(sharedParams->getSoundFontPath()); -// } - -// const String& currentSoundFontAbsPath = fluidSynthModel->getCurrentSoundFontAbsPath(); -// if (currentSoundFontAbsPath.isNotEmpty()) { -// fileChooser.setCurrentFile(File(currentSoundFontAbsPath), true, dontSendNotification); -// } } } } diff --git a/Source/TableComponent.cpp b/Source/TableComponent.cpp index b43d253..e52ea81 100644 --- a/Source/TableComponent.cpp +++ b/Source/TableComponent.cpp @@ -21,18 +21,9 @@ using namespace Util; */ TableComponent::TableComponent( AudioProcessorValueTreeState& valueTreeState - // const vector &columns, -// const vector &rows, - // const function &onRowSelected, - // const function&)> &rowToIDMapper, - // int initiallySelectedRow ) : valueTreeState{valueTreeState} , font{14.0f} -//, columns{columns} -//, rows{rows} -// , onRowSelected{onRowSelected} -// rowToIDMapper(rowToIDMapper) { // Create our table component and add it to this component.. addAndMakeVisible (table); @@ -44,19 +35,6 @@ TableComponent::TableComponent( int columnIx = 1; - // Add some columns to the table header, based on the column list in our database.. - // for (auto &column : columns) // access by reference to avoid copying - // { - // const int colWidth{ columnIx == 1 ? 30 : 200 }; - // table.getHeader().addColumn ( - // String(column), - // columnIx++, - // colWidth, // column width - // 30, // min width - // 400, // max width - // TableHeaderComponent::defaultFlags - // ); - // } table.getHeader().addColumn ( String("#"), columnIx++, @@ -76,20 +54,11 @@ TableComponent::TableComponent( table.setWantsKeyboardFocus(false); - // table.selectRow(); - // ValueTree presets{valueTreeState.state.getChildWithName("presets")}; ValueTree banks{valueTreeState.state.getChildWithName("banks")}; loadModelFrom(banks); - // selectCurrentPreset(); // we could now change some initial settings.. table.getHeader().setSortColumnId(1, false); // sort ascending by ID column -// table.getHeader().setColumnVisible (7, false); // hide the "length" column until the user shows it - - // un-comment this line to have a go of stretch-to-fit mode - // table.getHeader().setStretchToFitActive (true); - -// table.setMultipleSelectionEnabled (false); valueTreeState.state.addListener(this); valueTreeState.addParameterListener("bank", this); valueTreeState.addParameterListener("preset", this); @@ -101,27 +70,10 @@ TableComponent::~TableComponent() { valueTreeState.state.removeListener(this); } -// void TableComponent::loadModelFrom(ValueTree& presets) { -// rows.clear(); -// int numChildren{presets.getNumChildren()}; -// for(int i{0}; i presets; ValueTree bank{banks.getChild(bankIx)}; int bankNum{bank.getProperty("num")}; int bankChildren{bank.getNumChildren()}; @@ -129,9 +81,7 @@ void TableComponent::loadModelFrom(ValueTree& banks) { ValueTree preset{bank.getChild(presetIx)}; int presetNum{preset.getProperty("num")}; String presetName{preset.getProperty("name")}; - // presets.emplace_back(presetNum, presetName); TableRow row{presetNum, move(presetName)}; - // banksToPresets.insert(BanksToPresets::value_type(bankNum, move(row))); banksToPresets.emplace(bankNum, move(row)); } } @@ -139,15 +89,10 @@ void TableComponent::loadModelFrom(ValueTree& banks) { } void TableComponent::parameterChanged(const String& parameterID, float newValue) { - // valueTreeState.getParameter if (parameterID == "bank") { repopulateTable(); } else if (parameterID == "preset") { selectCurrentPreset(); - // RangedAudioParameter *param {valueTreeState.getParameter("preset")}; - // jassert(dynamic_cast (param) != nullptr); - // AudioParameterInt* castParam {dynamic_cast (param)}; - // int value{castParam->get()}; } } @@ -170,7 +115,6 @@ void TableComponent::repopulateTable() { upperBound, back_inserter(rows), mem_fn(&BanksToPresets::value_type::second) - // [](BanksToPresets::value_type element){return element.second;} ); table.deselectAllRows(); table.updateContent(); @@ -182,11 +126,6 @@ void TableComponent::repopulateTable() { void TableComponent::valueTreePropertyChanged( ValueTree& treeWhosePropertyHasChanged, const Identifier& property) { - // if (treeWhosePropertyHasChanged.getType() == StringRef("presets")) { - // if (property == StringRef("synthetic")) { - // loadModelFrom(treeWhosePropertyHasChanged); - // } - // } if (treeWhosePropertyHasChanged.getType() == StringRef("banks")) { if (property == StringRef("synthetic")) { loadModelFrom(treeWhosePropertyHasChanged); @@ -194,52 +133,6 @@ void TableComponent::valueTreePropertyChanged( } } -// void TableComponent::valueTreeParentChanged(ValueTree& treeWhoseParentHasChanged) { -// if (treeWhoseParentHasChanged.getType() == StringRef("presets")) { -// loadModelFrom(treeWhoseParentHasChanged); -// } -// } - -// void TableComponent::valueTreePropertyChanged( -// ValueTree& treeWhosePropertyHasChanged, -// const Identifier& property) { -// DEBUG_PRINT(treeWhosePropertyHasChanged.getType().toString()); -// } -// void TableComponent::valueTreeChildAdded( -// ValueTree& parentTree, -// ValueTree& childWhichHasBeenAdded) { -// DEBUG_PRINT(parentTree.getType().toString()); -// } -// void TableComponent::valueTreeChildRemoved( -// ValueTree& parentTree, -// ValueTree& childWhichHasBeenRemoved, -// int indexFromWhichChildWasRemoved) { -// DEBUG_PRINT(parentTree.getType().toString()); -// } -// void TableComponent::valueTreeChildOrderChanged( -// ValueTree& parentTreeWhoseChildrenHaveMoved, -// int oldIndex, -// int newIndex) { -// DEBUG_PRINT(parentTreeWhoseChildrenHaveMoved.getType().toString()); -// } -// void TableComponent::valueTreeParentChanged( -// ValueTree& treeWhoseParentHasChanged) { -// DEBUG_PRINT(treeWhoseParentHasChanged.getType().toString()); -// } -// void TableComponent::valueTreeRedirected( -// ValueTree& treeWhichHasBeenChanged) { -// DEBUG_PRINT(treeWhichHasBeenChanged.getType().toString()); -// } - -// void TableComponent::setRows(const vector>& rows, int initiallySelectedRow) { -// this->rows = rows; -// table.deselectAllRows(); -// table.updateContent(); -// table.getHeader().setSortColumnId(0, true); -// table.selectRow(initiallySelectedRow); -// table.repaint(); -// } - // This is overloaded from TableListBoxModel, and must return the total number of rows in our table int TableComponent::getNumRows() { @@ -297,40 +190,24 @@ void TableComponent::sortOrderChanged ( bool isForwards ) { if (newSortColumnId != 0) { - // int selectedRowIx = table.getSelectedRow(); - // TableRow* selectedRow; - // if (selectedRowIx >= 0) { - // selectedRow = &rows[selectedRowIx]; - // } - TableComponent::DataSorter sorter (newSortColumnId, isForwards); sort(rows.begin(), rows.end(), sorter); table.updateContent(); selectCurrentPreset(); - - // if (selectedRowIx >= 0) { - // for (auto it = rows.begin(); it != rows.end(); ++it) { - // if(it->preset == value) { - // int index {static_cast(std::distance(rows.begin(), it))}; - // table.selectRow(index); - // break; - // } - // } - // } } } void TableComponent::selectCurrentPreset() { table.deselectAllRows(); - RangedAudioParameter *param {valueTreeState.getParameter("preset")}; - jassert(dynamic_cast (param) != nullptr); - AudioParameterInt* castParam {dynamic_cast (param)}; + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; int value{castParam->get()}; - for (auto it = rows.begin(); it != rows.end(); ++it) { + for (auto it{rows.begin()}; it != rows.end(); ++it) { if(it->preset == value) { - int index {static_cast(distance(rows.begin(), it))}; + int index{static_cast(distance(rows.begin(), it))}; table.selectRow(index); break; } @@ -397,12 +274,10 @@ void TableComponent::selectedRowsChanged (int row) { if (row < 0) { return; } - // onRowSelected(rowToIDMapper(rows[row])); - // onRowSelected(stoi(rows[row][0])); int newPreset{rows[row].preset}; - RangedAudioParameter *param {valueTreeState.getParameter("preset")}; - jassert(dynamic_cast (param) != nullptr); - AudioParameterInt* castParam {dynamic_cast (param)}; + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; *castParam = newPreset; }