From 999c10e4b32fe1f8c4692cfd6187d74504d4861e Mon Sep 17 00:00:00 2001 From: IanCaio Date: Fri, 6 Oct 2023 19:37:46 -0300 Subject: [PATCH] Adds feature to edit tangents of Cubic Hermite progressions (#5924) Enables the editing of a node's tangents of Cubic Hermite progressions. * First commit This commit introduces the use of AutomationNodes instead of just floats for keeping the automation values. The AutomationNodes will give more flexibility when it comes to storing extra information about the values (and make it possible to have multiple progression types in the future). Now the tangents are stored on the node, requiring one less timeMap on the automation pattern. Some methods had to be changed for that, since before they used const_iterator, which wouldn't allow us to change the tangent values. TODO: - Check TODO comments that were added in places of the code I had some doubts about. - Maybe change the timeMap to hold pointers to AutomationNodes and not actual instances. - In some pieces of the code, I check if the AutomationNode already exists before setting its value or creating another node. I think that's unnecessary since if I create another node and assign it to the current existing one it could just clone its value. (That would not be valid for pointers to AutomationNodes, so that's something to consider when deciding between the two). - I still didn't find a good solution for renaming QMap::iterator method from "value()" to something else, so now we have lines that look a bit odd like "it.value().getValue()", because value() is actually returning the node, and getValue() is getting the node's automation value. * Implements inValue/outValue on Automation Nodes This commit is a big change in comparison to the previous one. Automation nodes now have a inValue and outValue instead of a single value. The inValue represents the core value of the node, which is used for incoming progressions. The outValue represents the value of the node for progressions starting from that node on. In practice, the true value of the node is the inValue and outValue represents a way of creating discrete jumps in a node's position. By default inValue and outValue are the same. The user will then be allowed to change the outValue to create those discrete jumps. Because their values might be different, we now need two tangents for a single node: One for the curve coming before the node and one for the curve coming after the node. So instead of a single tangent variable, we now have inTangent and outTangent. If inValue and outValue are the same, so are inTangent and outTangent, but if they are different both are calculated according to the curve before and after the node. On the Automation Editor, the inValue of the node is represented by our default blue circle, while the outValue is represented by a red circle with 80% alpha (we should add a variable to the Automation Editor class to hold the color of this second circle in the future). Lots of comments were added on the modified files to explain the existing methods where changes were required (not only explaining the logic of the methods but the reason behind using inValue or outValue on them). Lots of TODO comments were also added as placeholders for changes that could or should be done before the finishing of this PR (or after it). For now only the logic for the nodes was added, but there's still no way for the user to change outValues (on the next commit a small placeholder shortcut will be added to do that for testing purposes). The drawing of the notes detuning on the piano roll was updated to account for the discrete jumps. The drawing of the pattern TCO was updated to account for the discrete jumps. The drawing of the pattern on the AutomationEditor was updated to account for the discrete jumps. IMPORTANT: - There were changes to the loadSettings and saveSettings of AutomationPatterns, to account for inValues and outValues, but I didn't create an upgrade routine yet. Some behavior that is important to mention: - Most operations on nodes (drawing, moving, X/Y flipping, and even selection copy/paste, apparently not fully finished) ignore the outValue, basically reseting it to the inValue. So when an user moves a node with a discrete jump, for example, that discrete jump will be lost and the user will need to set it again. Obviously in the future we might want to keep that information, but that isn't a critical issue, just a behavior that can be improved later by upgrading the code. - Later on we might want to connect a signal to the AutomationNode class, so it calls generateTangents when node data is changed. - When an object is disconnected from an automation pattern and it has to rescale the values so they fit the new range of values (max and min) the outValue is reset, meaning all discrete jumps are lost. This behavior will be changed in the future. Things that I think are also important noting: - Mainly in the src/gui/editor/AutomationEditor.cpp file there were lots of codes that apparently are related to a feature that is not yet finished (moving/cutting/copying/pasting selections of automation values). This doesn't sound good unless it's currently being worked on. I tried my best to update the current code to comply to the use of AutomationNodes so their developing can be picked up from a unbroken state. As with other operations involving AutomationNodes, they only account for the inValue discarding any discrete jumps that were present. - I added comments on some logic that seemed flawed in the src/gui/editor/AutomationEditor.cpp file so it can be reviewed. It's beyond the scope of this PR, but since I had to read and change a lot on that file I thought it was pertinent to at least comment those observations. * Fixes and refactor code on AutomationEditor.cpp While implementing the automation nodes, I noticed AutomatinEditor.cpp had some issues regarding flawed logic, code style convention, code that could comply to DRY paradigm, conditions that resulted in Undefined Behavior and unused legacy code. That's probably due to how old some changes are, they probably reflect a much different state of LMMS's code base. To make the transition to automation nodes a better one and avoid having to rework everything later I'm using the fact I have to get in touch with most of this code to try to fix some things. This commit starts refactoring AutomationEditor::mousePressEvent and AutomationEditor::mouseMoveEvent. There are still things to be improved on both but I'll slowly commit them so I can have better versioning control of the PR. Some changes worth noting: - A new action was created in the AutomationEditor class for drawing lines, since its logic was too mixed up with the logic of drawing and dragging a single node. - Changed most variable names to fit the current code style (just very few left to change). - Improved comments explaining the code. - Created a separate method for checking if the mouse position is sitting over an existing node (previously this code was repeated inside the event method and it had flaws on its logic, most of the times returning that the user didn't click a node even when he/she clicked one). Method is called getNodeAt(x,y,r), r being the "radius" to be considered (not actually a radius, the area is actually a square). Some changes that are still planned: - Removing legacy code for features that weren't finished (select and move selection) if it's agreed. - Adding some logic to the DRAW_LINE action so it can be even improved. - Not forgetting the main focus of the PR, adding a way for the user to edit the outValue of nodes. * Avoids unnecessary check in putValue When adding a value to a particular time, instead of checking if there's a node there already and manually setting its value, we just assign it with a new AutomationNode. QMap silently removes the existing node and adds the new one. * Adds upgrade routine for the automation nodes Adds an upgrade method for the change in the automation nodes settings. The "value" attribute of the