Conversation
|
Can you please provide the XML schema used? How is it upward and backward compatible between mixxx versions? |
|
I have not written a formal schema, but here is an example For backwards compatibility, if For forwards compatibility, it would be a good idea to indicate the Mixxx version with the |
4ba4cd9 to
3eefcd1
Compare
| } else { | ||
| m_pControlValue->setParameter( | ||
| XmlParse::selectNodeDouble(knobParameterElement, "Value")); | ||
| m_pEffectParameter->setValue(m_pControlValue->get()); |
There was a problem hiding this comment.
I don't understand why this is necessary. m_pControlValue's valueChanged signal is connected to EffectParameterSlot::slotValueChanged in the EffectParameterSlot constructor, so the line above should already do this. Putting a qDebug() at the top of EffectParameterSlot::slotValueChanged shows that it is not triggered when the value is loaded from XML.
There was a problem hiding this comment.
setParameter does not send value change event to the owner. Please try setParamerterFrom( .. , nullptr);
I think you have also to deal with soft takeover since controllers will be out of sync:
m_pSoftTakeover->ignore(m_pControlValue, parameter);
There was a problem hiding this comment.
I tried m_pControlValue->set() and it had the same issue. I'll try setParameterFrom().
Soft takeover should not be active here. That would interfere with receiving initial positions of knobs from controllers. I have designed the Controls JS library to accommodate that.
There was a problem hiding this comment.
setParameterFrom(..., nullptr) does work. I don't understand how. Could you explain? I looked through the ControlObject and ControlDoublePrivate source but it isn't clear to me. I see that ControlDoublePrivate::set() has the line emit(valueChanged(value, pSender));, but I don't understand how that doesn't trigger the connected slot when pSender is the ControlObject as set in ControlObject::setParameter().
|
Saving the Mixxx version in the xml file will force us to migrate the file on every Mixxx verison change. |
| return false; | ||
| EffectChainPointer pEmptyChain; | ||
| QDomElement emptyChainElement = doc.createElement("EffectChain"); | ||
| for (int i = 0; i < 4; ++i) { |
There was a problem hiding this comment.
the 4 should be a common definition
There was a problem hiding this comment.
Is this the same 4 we have in StandardEffectRack::addEffectChainSlot?
Maybe it can become a const member of EffectChainManager,
| return chainElement; | ||
| } | ||
|
|
||
| XmlParse::addElement(*doc, chainElement, "Name", |
There was a problem hiding this comment.
Strings like "Name" should be placed in an anonymous namespace on top of this file.
There was a problem hiding this comment.
Anonymous namespaces will not work well for this because these strings are reused in both the Slot (toXML & loadValuesFromXml methods) and non-Slot (createFromXml methods) classes in src/effects. Can you suggest a better place to define the strings?
There was a problem hiding this comment.
Than you might use an independent header file that is included by both
| bool saveEffectChains(); | ||
| QList<std::pair<EffectChainPointer, QDomElement>> loadEffectChains(); | ||
|
|
||
| static const int kNumEffectsPerUnit = 4; |
There was a problem hiding this comment.
I am not sure if this works nice on all compilers.
I would prefer to wrap this into a getter and move this constant to the cpp file
|
|
||
| // Version history: | ||
| // 0 (Mixxx 2.1.0): initial support for saving state of effects | ||
| const int EFFECT_XML_VERSION = 0; |
There was a problem hiding this comment.
kEffectXmlVersion
MIXXX_VERSION is a macro
|
@daschuer any more issues? |
|
Whoops, I forgot to add the new header file in 61669a5. Added it in a new commit. |
|
Let's not merge this until #1118 is merged to avoid having to increase the effect version numbers. |
|
Okay, all merge conflicts and build issues fixed. Tests pass again. Ready for review. |
8499c99 to
7c3038e
Compare
|
ping @rryan: Is there anything left to do here? Ready for merge? |
rryan
left a comment
There was a problem hiding this comment.
I did one more careful review and found a few minor things to fix. LGTM, nice work!
| m_pControlValue->reset(); | ||
| } else { | ||
| bool conversionWorked = false; | ||
| double value = XmlParse::selectNodeDouble(parameterElement, |
There was a problem hiding this comment.
Looks like you're missing a &conversionWorked 3rd parameter here so the value will never be set.
| QString itemPrefix = formatItemPrefix(iParameterSlotNumber); | ||
| m_pControlLoaded = new ControlObject( | ||
| ConfigKey(m_group, itemPrefix + QString("_loaded"))); | ||
| m_pControlType = new ControlObject( |
There was a problem hiding this comment.
I know you just moved this, but I noticed m_pControlType and m_pControlLoaded are not deleted in the constructor. Could you add a delete please?
There was a problem hiding this comment.
They are deleted in the parent EffectParameterSlotBase destructor. Deleting them here causes a segfault. I'll add a comment.
There was a problem hiding this comment.
Oh oops, that's not confusing at all :) -- thanks.
| addEffectButtonParameterSlot(); | ||
| } | ||
|
|
||
| m_parametersById.clear(); |
There was a problem hiding this comment.
If loadEffect is called with a null EffectPointer, we won't clear m_parametersById. Should this be at the top of the function?
|
|
||
| QDomElement parametersElement = doc->createElement(EffectXml::ParametersRoot); | ||
|
|
||
| for (const auto& pParameter : m_parametersById) { |
There was a problem hiding this comment.
Since m_parametersById is only used in serializing/deserializing, I wonder if it makes sense to build it on the fly instead of storing at the class level (which requires keeping it in sync as new effects are loaded).
Also, why a QMap? did you want the items to be sorted by ID in the XML (instead of being in the parameter order in the manifest)?
There was a problem hiding this comment.
Since m_parametersById is only used in serializing/deserializing, I wonder if it makes sense to build it on the fly instead of storing at the class level (which requires keeping it in sync as new effects are loaded).
EDIT: You're right, that does make sense.
Also, why a QMap? did you want the items to be sorted by ID in the XML (instead of being in the parameter order in the manifest)?
I used a QMap to pair the EffectParameterSlotBasePointers with the corresponding manifests' ID (which is a QString). EffectSlot::loadEffectSlotFromXml needs to lookup an EffectParameterSlotBasePointer from the ID string in the XML. It would be a good idea to serialize them in order when writing the XML though. Then when support for users rearranging parameters is implemented that could be represented in the XML file with the order of the <Parameter> elements.
There was a problem hiding this comment.
Hmmm... I'll refactor this a bit to preserve parameter order.
| } | ||
|
|
||
| void EffectSlot::loadEffectSlotFromXml(const QDomElement& effectElement) { | ||
| if (effectElement.text().isEmpty()) { |
There was a problem hiding this comment.
QDomElement::text() requires recursively building up a string of all the children's .text(). I think QDomNode::hasChildNodes does what you want here but will be far cheaper?
https://code.qt.io/cgit/qt/qt.git/tree/src/xml/dom/qdom.cpp#n4565
| } | ||
|
|
||
| void EffectChainSlot::loadChainSlotFromXml(const QDomElement& effectChainElement) { | ||
| if (effectChainElement.text().isEmpty()) { |
There was a problem hiding this comment.
!element.hasChildNodes() instead?
(see my other comment)
| if (m_pEffectParameter == nullptr) { | ||
| return; | ||
| } | ||
| if (parameterElement.text().isEmpty()) { |
There was a problem hiding this comment.
!element.hasChildNodes() instead?
(see my other comment)
|
|
||
| QDomElement parametersElement = XmlParse::selectElement(effectElement, | ||
| EffectXml::ParametersRoot); | ||
| if (parametersElement.text().isEmpty()) { |
There was a problem hiding this comment.
!element.hasChildNodes() instead?
(see my other comment)
| if (effectElement.text().isEmpty()) { | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
Since loading the effect chain into the effect chain slot and loading the chain slot's XML config happen separately, want to add a sanity check here that the XML effect ID matches the loaded effect ID and print a warning if it doesn't?
| addEffectButtonParameterSlot(); | ||
| } | ||
|
|
||
| m_parametersById.clear(); |
There was a problem hiding this comment.
Please clear m_parametersById in EffectSlot::clear() as well
QDomNode::text recursively gets the text for each child node
In the future, it should be possible to rearrange effect parameters and store that in XML with the order of the <Parameter> elements.
|
Just did a final test and everything seems to work well. For fun, I changed Flanger's effect ID to the unicode poop symbol and I couldn't get it to load in an effect unit so I didn't get to test whether the UTF-8 encoded poop actually ended up in the XML file :P. I settled for adding accented characters to the EffectChain's description and verifying they were preserved across restarts. LGTM, thanks! |
|
Thanks for review and testing! 2.1 will have a much better user experience for effects. :)
I did not know such a thing existed. o.O |
|
In current Master, I cannot use prev_effect or prev_chain in Shade / LateNight. |
| pStandardRack->addEffectChainSlot(); | ||
| pStandardRack->addEffectChainSlot(); | ||
| pStandardRack->addEffectChainSlot(); | ||
| pStandardRack->addEffectChainSlot(); |
There was a problem hiding this comment.
I think the regression is caused by the missing empty Slots in case of no stored effect are available.
| pStandardRack->addEffectChainSlot(); | ||
| pStandardRack->addEffectChainSlot(); | ||
| for (auto chain : savedChains) { | ||
| pStandardRack->addEffectChainSlot(chain.first, chain.second); |
There was a problem hiding this comment.
Please use here named members instead pointless "first" and "second"
There was a problem hiding this comment.
This is used only once. I think creating a struct just for this one use would be more trouble than its worth.
There was a problem hiding this comment.
It already causes trouble and it tends to spread out later.
next_chain is working. It looks broken at first because there are 4 chains created that do not have any effects loaded and LateNight has no way to load different effects in a chain yet. Something is wrong with prev_chain though. prev_chain works for the chains made with the premade chain hack, which should be removed as soon as Shade and LateNight are updated, but it isn't working for the apparently blank chains. next/prev_effect are working fine in Shade. |
|
It looks like we have a conceptual issue here. The content of the xml file also creates the COs of the effects. This breaks the original concept, that the COs are independent from the loaded effect chains. Mixxx, or the loaded skin should set up the Effect slots and the stored chains from the XML is just responsible for the filling, loading the stored effects. |
No, sorry. Not if there is no XML file. |
I cannot reproduce this. |
Is skipped if there is no saved chain. And this instantiates and connects the prev_effect and next_effect COs. Without a connection nothing happens. But I think that the COs are not connected before is the real issue. |
No it is not. EffectChainManager::loadEffectChains will return 4 empty EffectChainPointer, QDomElement pairs if the file cannot be read or parsed. |
|
For some reason I have found this file in my .mixxx folder <?xml version='1.0' encoding='utf-8'?>
<MixxxEffects schemaVersion="0">
<Rack>
<Group>[EffectRack1]</Group>
<Chains/>
</Rack>
</MixxxEffects>If I delete it, the issue is gone. What could be the reason for getting it? |
IMHO this should not be in responds to loadEffectChains. Mixxx should setup the Chains before and load what is in the file. This would make the feature more robust against user edits. |
That was probably from testing an early version of this PR. Users should never have such a file. The feature could be more robust against unusual XML files by ensuring that EffectChainManager::loadEffectChain always returns 4 EffectChainPointer, QDomElement pairs. The XML file you posted is a valid XML file, so it looked for effect chains to load, but ended up returning none (which is not the same as returning null EffectChainPointers). |
Save state of effect chains to
effects.xmlin user settings directory on shutdown and reload state of effects chains from that file on startup. This paves the way for exporting & importing effect chain presets.Should the state of effect metaknobs and/or chain superknobs be saved as well?