From 20c83e50213fcb831ec3629d0397c3ba9cb204e6 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Mon, 5 Jun 2023 20:09:30 +0200 Subject: [PATCH 1/9] Fix automated base notes and their automations (#6548) Fix all base notes that are used in automations and their corresponding automation values. Base notes that are automated are stored as elements in the save file whereas non-automated base notes are stored as attributes. So far the method `upgrade_extendedNoteRange` only upgraded the non-automated base notes that are stored in attributes. This commit fixes the automated ones which are stored in elements. The fix works as follows: * Collect all base note elements. * Store their ids in a set so that we can later identify automations that reference them. * Collect all automation pattern and check if they reference a base note. * Adjust the values and out values of all automations that reference base notes. Note: for many older files the out values will be introduced by the upgrade `method upgrade_automationNodes` and do not appear in the files themselves! --- src/core/DataFile.cpp | 73 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 2e7b21e8b..a39db430b 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -1723,6 +1724,71 @@ void DataFile::upgrade_extendedNoteRange() } } } + + // Fix the base notes that are used in automations and the automations that use them. + + // First fix the base notes used in automations and collect their ids while doing so. + // Base notes that are used in automations appear as elements in the document. + // The ids are used later to find the automations that automate these corrected base + // notes so that we can correct the automation values as well. + std::set baseNoteIds; + + QDomNodeList baseNotes = elementsByTagName("basenote"); + for (int j = 0; j < baseNotes.size(); ++j) + { + QDomElement baseNote = baseNotes.item(j).toElement(); + if (!baseNote.isNull()) + { + if (baseNote.hasAttribute("value")) + { + int const value = baseNote.attribute("value").toInt(); + baseNote.setAttribute("value", value + 12); + } + + // The ids of base notes are of type jo_id_t which are in fact uint32_t. + // So let's just use these here to save some casting. + unsigned int const id = baseNote.attribute("id").toUInt(); + baseNoteIds.insert(id); + } + } + + // Now collect all automation patterns and correct all their automations that + // use the corrected base notes. + QDomNodeList automationPatterns = elementsByTagName("automationpattern"); + for (int j = 0; j < automationPatterns.size(); ++j) + { + QDomElement automationPattern = automationPatterns.item(j).toElement(); + if (!automationPattern.isNull()) + { + // Iterate the objects. These contain the ids of the automated objects. + QDomElement object = automationPattern.firstChildElement("object"); + while(!object.isNull()) { + unsigned int const id = object.attribute("id").toUInt(); + if (baseNoteIds.find(id) != baseNoteIds.end()) + { + // The automation pattern belongs to a corrected base note. + // Collect all time elements to correct their values and out + // values. + QDomElement time = automationPattern.firstChildElement("time"); + while(!time.isNull()) { + // Value is in fact a float but if we save automations for + // base notes we in fact save integer values. + int const value = time.attribute("value").toInt(); + time.setAttribute("value", value + 12); + + // The method "upgrade_automationNodes" adds some attributes + // with the name "outValue". We have to correct these as well. + int const outValue = time.attribute("outValue").toInt(); + time.setAttribute("outValue", outValue + 12); + + time = time.nextSiblingElement("time"); + } + } + + object = object.nextSiblingElement("object"); + } + } + } } else { @@ -1837,8 +1903,11 @@ void DataFile::upgrade_bbTcoRename() void DataFile::upgrade() { // Runs all necessary upgrade methods - std::size_t max = std::min(static_cast(m_fileVersion), UPGRADE_METHODS.size()); - std::for_each( UPGRADE_METHODS.begin() + max, UPGRADE_METHODS.end(), + size_t const upgradedVersion = static_cast(m_fileVersion); + size_t const numberOfVersions = UPGRADE_METHODS.size(); + std::size_t offsetToUpgradeStart = std::min(upgradedVersion, numberOfVersions); + auto upgradeMethodIt = UPGRADE_VERSIONS.begin() + offsetToUpgradeStart; + std::for_each( UPGRADE_METHODS.begin() + offsetToUpgradeStart, UPGRADE_METHODS.end(), [this](UpgradeMethod um) { (this->*um)(); From 9f34c5cfa336a03d78a8783829357e15cd84107b Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Mon, 5 Jun 2023 20:20:34 +0200 Subject: [PATCH 2/9] Remove debug code from DataFile::upgrade Remove debug code from DataFile::upgrade which was accidentally committed. --- src/core/DataFile.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index a39db430b..68058fb7f 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -1903,11 +1903,8 @@ void DataFile::upgrade_bbTcoRename() void DataFile::upgrade() { // Runs all necessary upgrade methods - size_t const upgradedVersion = static_cast(m_fileVersion); - size_t const numberOfVersions = UPGRADE_METHODS.size(); - std::size_t offsetToUpgradeStart = std::min(upgradedVersion, numberOfVersions); - auto upgradeMethodIt = UPGRADE_VERSIONS.begin() + offsetToUpgradeStart; - std::for_each( UPGRADE_METHODS.begin() + offsetToUpgradeStart, UPGRADE_METHODS.end(), + std::size_t max = std::min(static_cast(m_fileVersion), UPGRADE_METHODS.size()); + std::for_each( UPGRADE_METHODS.begin() + max, UPGRADE_METHODS.end(), [this](UpgradeMethod um) { (this->*um)(); From 02fef122ae5158658be142d962c09889ef1c3d1a Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Thu, 8 Jun 2023 10:48:02 +0200 Subject: [PATCH 3/9] Extend fix for mixed automation tracks/patterns (#6548) Move the fix for the automated base notes to the code that fixes the non-automated base notes. Improve the fix for automation tracks and patterns by potentially splitting them into two tracks: * The original track which is adjusted to only contain patterns with targets that are not base notes. * A cloned track that only contains patterns with base note targets. This is done by iterating over all automation tracks and checking which types of automations they contain: * Base note automations * Automations of other targets The result for each automation track are then evaluated as follows. * If an automation track does not contain any base note automations it is kept as it is, i.e. nothing is done. * If an automation track only contains patterns with base note automations its patterns are corrected in place. * If an automation track contains patterns with base note automations and other targets then the track is duplicated so that we can split it as described above. This split and correction is done on a per pattern level. Patterns that would become empty are removed. Cloned tracks will keep the same names and attributes as their original tracks. TODOs ------ * Base notes are read as integers, corrected by an integer value of 12 and then stored back as an integer. In some files base notes have float values, i.e. if the file was stored after the base notes have been automated linearly. * B&B tracks are not corrected although they can contain instruments with (automated) base notes! * Nested for-loops with "i" as their running variables. (Fix in a separate commit) --- src/core/DataFile.cpp | 337 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 276 insertions(+), 61 deletions(-) diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 68058fb7f..e8d4c9941 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -1670,6 +1670,76 @@ void DataFile::upgrade_automationNodes() } } +/** + * @brief Used by the helper function that analyzes automation patterns. + */ +struct PatternAnalysisResult +{ + PatternAnalysisResult(bool hasBaseNoteAutomations, bool hasNonBaseNoteAutomations) + { + this->hasBaseNoteAutomations = hasBaseNoteAutomations; + this->hasNonBaseNoteAutomations = hasNonBaseNoteAutomations; + } + bool hasBaseNoteAutomations; + bool hasNonBaseNoteAutomations; +}; + +/** + * @brief Helper function that checks for an automation pattern if it contains automation for + * targets that are base notes and/or other targets. + * @param automationPattern The automation pattern to be checked. + * @param automatedBaseNoteIds A set of id of automated base notes that are used in the check. + * @return A struct that contains the results. + */ +static PatternAnalysisResult analyzeAutomationPattern(QDomElement const & automationPattern, std::set const & automatedBaseNoteIds) +{ + bool hasBaseNoteAutomations = false; + bool hasNonBaseNoteAutomations = false; + + // Iterate the objects. These contain the ids of the automated objects. + QDomElement object = automationPattern.firstChildElement("object"); + while(!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + // Check if the automated object is a base note. + if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) + { + hasBaseNoteAutomations = true; + } + else + { + hasNonBaseNoteAutomations = true; + } + + object = object.nextSiblingElement("object"); + } + + return PatternAnalysisResult(hasBaseNoteAutomations, hasNonBaseNoteAutomations); +} + +/** + * @brief Helper method that fixes the values and out values for an automation pattern. + * @param automationPattern The automation pattern to be fixed. + */ +static void fixAutomationPattern(QDomElement & automationPattern) +{ + QDomElement time = automationPattern.firstChildElement("time"); + while(!time.isNull()) + { + // Automation patterns can automate base notes as floats + // so we read and correct them as floats here. + float const value = time.attribute("value").toFloat(); + time.setAttribute("value", value + 12.); + + // The method "upgrade_automationNodes" adds some attributes + // with the name "outValue". We have to correct these as well. + float const outValue = time.attribute("outValue").toFloat(); + time.setAttribute("outValue", outValue + 12.); + + time = time.nextSiblingElement("time"); + }; +} /** \brief Note range has been extended to match MIDI specification * @@ -1680,37 +1750,100 @@ void DataFile::upgrade_extendedNoteRange() { auto affected = [](const QDomElement& instrument) { - return instrument.attribute("name") == "zynaddsubfx" || - instrument.attribute("name") == "vestige" || - instrument.attribute("name") == "lv2instrument" || - instrument.attribute("name") == "carlapatchbay" || - instrument.attribute("name") == "carlarack"; + assert(instrument.hasAttribute("name")); + QString const name = instrument.attribute("name"); + + return name == "zynaddsubfx" || + name == "vestige" || name == "lv2instrument" || + name == "carlapatchbay" || name == "carlarack"; }; if (!elementsByTagName("song").item(0).isNull()) { + // This set will later contain all ids of automated base notes. They are + // used to find out which automation patterns must to be corrected, i.e. to + // check if an automation pattern has one or more base notes as its target. + std::set automatedBaseNoteIds; + // Dealing with a project file, go through all the tracks QDomNodeList tracks = elementsByTagName("track"); - for (int i = 0; !tracks.item(i).isNull(); i++) + for (int i = 0; i < tracks.size(); ++i) { - // Ignore BB container tracks - if (tracks.item(i).toElement().attribute("type").toInt() == 1) { continue; } + QDomElement currentTrack = tracks.item(i).toElement(); + if (!currentTrack.hasAttribute("type")) + { + continue; + } + Track::TrackTypes const trackType = static_cast(currentTrack.attribute("type").toInt()); + + // Ignore BB container tracks + if (trackType == Track::PatternTrack) + { + continue; + } + + QDomNodeList instruments = currentTrack.elementsByTagName("instrument"); + + if (instruments.isEmpty()) + { + continue; + } + + assert(instruments.size() < 2 && "More than one instrument found in a track!"); - QDomNodeList instruments = tracks.item(i).toElement().elementsByTagName("instrument"); - if (instruments.isEmpty()) { continue; } QDomElement instrument = instruments.item(0).toElement(); + + if (instrument.isNull()) + { + continue; + } + // Raise the base note of every instrument by 12 to compensate for the change // of A4 key code from 57 to 69. This ensures that notes are labeled correctly. - instrument.parentNode().toElement().setAttribute( - "basenote", - instrument.parentNode().toElement().attribute("basenote").toInt() + 12); + QDomElement instrumentParent = instrument.parentNode().toElement(); + + // Correct the base note of the instrument. Base notes which are automated are + // stored as elements. Non-automated base notes are stored as attributes. + if (instrumentParent.hasAttribute("basenote")) + { + // TODO Base notes can have float values in the save file! This might need to be changed! + int const currentBaseNote = instrumentParent.attribute("basenote").toInt(); + instrumentParent.setAttribute("basenote", currentBaseNote + 12); + } + else + { + // Check if the instrument track has an automated base note. + // Correct the value of the base note and collect their ids while doing so. + // The ids are used later to find the automations that automate these corrected base + // notes so that we can correct the automation values as well. + QDomNodeList baseNotes = instrumentParent.elementsByTagName("basenote"); + for (int j = 0; j < baseNotes.size(); ++j) + { + QDomElement baseNote = baseNotes.item(j).toElement(); + if (!baseNote.isNull()) + { + if (baseNote.hasAttribute("value")) + { + // TODO Base notes can have float values in the save file! This might need to be changed! + int const value = baseNote.attribute("value").toInt(); + baseNote.setAttribute("value", value + 12); + } + + // The ids of base notes are of type jo_id_t which are in fact uint32_t. + // So let's just use these here to save some casting. + unsigned int const id = baseNote.attribute("id").toUInt(); + automatedBaseNoteIds.insert(id); + } + } + } + // Raise the pitch of all notes in patterns assigned to instruments not affected // by #1857 by an octave. This negates the base note change for normal instruments, // but leaves the MIDI-based instruments sounding an octave lower, preserving their // pitch in existing projects. if (!affected(instrument)) { - QDomNodeList patterns = tracks.item(i).toElement().elementsByTagName("pattern"); + QDomNodeList patterns = currentTrack.elementsByTagName("pattern"); for (int i = 0; !patterns.item(i).isNull(); i++) { QDomNodeList notes = patterns.item(i).toElement().elementsByTagName("note"); @@ -1725,69 +1858,151 @@ void DataFile::upgrade_extendedNoteRange() } } - // Fix the base notes that are used in automations and the automations that use them. + // Now fix all the automation tracks. + // We have to collect the tracks that we need to duplicate and cannot do this in-place + // because if we did the iteration might never stop. + std::vector tracksToDuplicate; + tracksToDuplicate.reserve(tracks.size()); - // First fix the base notes used in automations and collect their ids while doing so. - // Base notes that are used in automations appear as elements in the document. - // The ids are used later to find the automations that automate these corrected base - // notes so that we can correct the automation values as well. - std::set baseNoteIds; - - QDomNodeList baseNotes = elementsByTagName("basenote"); - for (int j = 0; j < baseNotes.size(); ++j) + // Iterate the tracks again. This time work on all automation tracks. + for (int i = 0; i < tracks.size(); ++i) { - QDomElement baseNote = baseNotes.item(j).toElement(); - if (!baseNote.isNull()) + QDomElement currentTrack = tracks.item(i).toElement(); + if (currentTrack.attribute("type").toInt() != Track::AutomationTrack) { - if (baseNote.hasAttribute("value")) - { - int const value = baseNote.attribute("value").toInt(); - baseNote.setAttribute("value", value + 12); - } + continue; + } - // The ids of base notes are of type jo_id_t which are in fact uint32_t. - // So let's just use these here to save some casting. - unsigned int const id = baseNote.attribute("id").toUInt(); - baseNoteIds.insert(id); + // Check each track for the types of automations it contains in its patterns. + bool containsPatternsWithBaseNoteTargets = false; + bool containsPatternsWithNonBaseNoteTargets = false; + + QDomElement automationPattern = currentTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + containsPatternsWithBaseNoteTargets |= analysis.hasBaseNoteAutomations; + containsPatternsWithNonBaseNoteTargets |= analysis.hasNonBaseNoteAutomations; + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + + if (!containsPatternsWithBaseNoteTargets) + { + // No base notes are automated by this automation track so we have nothing to do + continue; + } + else + { + if (!containsPatternsWithNonBaseNoteTargets) + { + // Only base note targets. This means we can simply keep the track and fix it. + automationPattern = currentTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + fixAutomationPattern(automationPattern); + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + } + else + { + // The automation track has automations for base notes and other targets in its patterns. + // We will later need to duplicate/split the track. + tracksToDuplicate.push_back(currentTrack); + } } } - // Now collect all automation patterns and correct all their automations that - // use the corrected base notes. - QDomNodeList automationPatterns = elementsByTagName("automationpattern"); - for (int j = 0; j < automationPatterns.size(); ++j) + // Now fix the tracks that need duplication/splitting + for (QDomElement & track : tracksToDuplicate) { - QDomElement automationPattern = automationPatterns.item(j).toElement(); - if (!automationPattern.isNull()) + // First clone the original track + QDomNode cloneOfTrack = track.cloneNode(); + + // Now that we have the original and the clone we can manipulate both of them. + // The original track will keep only patterns without base note automations. + // Note: for the original track these might also be automation patterns without + // any targets. We will keep these because they might have been saved by the users + // like this. + QDomElement automationPattern = track.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) { - // Iterate the objects. These contain the ids of the automated objects. - QDomElement object = automationPattern.firstChildElement("object"); - while(!object.isNull()) { - unsigned int const id = object.attribute("id").toUInt(); - if (baseNoteIds.find(id) != baseNoteIds.end()) + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + if (!analysis.hasBaseNoteAutomations) + { + // This pattern has no base note automations. Leave it alone. + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + else if (!analysis.hasNonBaseNoteAutomations) + { + // The pattern only has base note automations. Remove it completely as it would become empty. + QDomElement patternToRemove = automationPattern; + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + track.removeChild(patternToRemove); + } + else + { + // The pattern itself is mixed. Remove the base note objects. + QDomElement object = automationPattern.firstChildElement("object"); + while(!object.isNull()) { - // The automation pattern belongs to a corrected base note. - // Collect all time elements to correct their values and out - // values. - QDomElement time = automationPattern.firstChildElement("time"); - while(!time.isNull()) { - // Value is in fact a float but if we save automations for - // base notes we in fact save integer values. - int const value = time.attribute("value").toInt(); - time.setAttribute("value", value + 12); + unsigned int const id = object.attribute("id").toUInt(); - // The method "upgrade_automationNodes" adds some attributes - // with the name "outValue". We have to correct these as well. - int const outValue = time.attribute("outValue").toInt(); - time.setAttribute("outValue", outValue + 12); - - time = time.nextSiblingElement("time"); + if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) + { + QDomElement objectToRemove = object; + object = object.nextSiblingElement("object"); + automationPattern.removeChild(objectToRemove); + } + else + { + object = object.nextSiblingElement("object"); } } - object = object.nextSiblingElement("object"); + automationPattern = automationPattern.nextSiblingElement("automationpattern"); } } + + // The clone will only keep non-empty patterns with base note automations and the values of the patterns will be corrected. + automationPattern = cloneOfTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + if (analysis.hasBaseNoteAutomations) + { + // This pattern has base note automations. Remove all other ones and fix the pattern. + QDomElement object = automationPattern.firstChildElement("object"); + while(!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + if (automatedBaseNoteIds.find(id) == automatedBaseNoteIds.end()) + { + QDomElement objectToRemove = object; + object = object.nextSiblingElement("object"); + automationPattern.removeChild(objectToRemove); + } + else + { + object = object.nextSiblingElement("object"); + } + } + + fixAutomationPattern(automationPattern); + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + else + { + // The pattern has no base note automations. Remove it completely. + QDomElement patternToRemove = automationPattern; + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + cloneOfTrack.removeChild(patternToRemove); + } + } + track.parentNode().appendChild(cloneOfTrack); } } else From 502e95d5b4cf5dac0d02c49793154c0ec165e66b Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Thu, 8 Jun 2023 11:09:38 +0200 Subject: [PATCH 4/9] Try to fix GitHub builds The GitHub builds seem to need the explicit include which for some reason is not needed on my developer machine. --- src/core/DataFile.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index e8d4c9941..b3884c9e0 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include From cf83b52783e172aaddc2befb01df97b463f2ef9d Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Thu, 8 Jun 2023 11:27:04 +0200 Subject: [PATCH 5/9] Fix nested for loops with identical variables (#6548) Fix the problem with the nested for loops that all had variables called "i" by extracting a helper method with a corrected nesting. Due the extraction we do not have to care anymore if the correction is running beneath another for loop with potentially the same variable names. --- src/core/DataFile.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index b3884c9e0..009a8fe81 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -1719,6 +1719,23 @@ static PatternAnalysisResult analyzeAutomationPattern(QDomElement const & automa return PatternAnalysisResult(hasBaseNoteAutomations, hasNonBaseNoteAutomations); } +static void fixNotePatterns(QDomNodeList & patterns) +{ + for (int i = 0; i < patterns.size(); ++i) + { + QDomNodeList notes = patterns.item(i).toElement().elementsByTagName("note"); + for (int j = 0; j < notes.size(); ++j) + { + QDomElement note = notes.item(j).toElement(); + if (note.hasAttribute("key")) + { + int const currentKey = note.attribute("key").toInt(); + note.setAttribute("key", currentKey + 12); + } + } + } +} + /** * @brief Helper method that fixes the values and out values for an automation pattern. * @param automationPattern The automation pattern to be fixed. @@ -1845,17 +1862,7 @@ void DataFile::upgrade_extendedNoteRange() if (!affected(instrument)) { QDomNodeList patterns = currentTrack.elementsByTagName("pattern"); - for (int i = 0; !patterns.item(i).isNull(); i++) - { - QDomNodeList notes = patterns.item(i).toElement().elementsByTagName("note"); - for (int i = 0; !notes.item(i).isNull(); i++) - { - notes.item(i).toElement().setAttribute( - "key", - notes.item(i).toElement().attribute("key").toInt() + 12 - ); - } - } + fixNotePatterns(patterns); } } From 4f01094d9865ee5e4f929b903d38d1f2686804bf Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Thu, 8 Jun 2023 14:25:20 +0200 Subject: [PATCH 6/9] Fix base notes of B+B tracks (#6548) Fix the base notes and automations of B+B tracks. This fixes for example the problem that when a TripleOscillator had been put into a B+B track, e.g. with version 1.2 that it sounded too high when being loaded with a current master version. The cause seems to be that the notes of the instrument pattern are corrected/transposed too often (likely due to the collection of all tracks starting from the song/document root). The multiple correction of notes is fixed by traversing the song structure in a more consise way. First all track containers of the song are collected and for each of them the tracks are collected. These tracks are then fixed one by one. B+B tracks are getting a special handling while doing so. It is assumed that a B+B track cannot have other B+B tracks inside and therefore all sub tracks of the B+B track are searched for so that they can be fixed recursively. Refactor some more functionality into static helper methods: * Everything beneath a track is now fixed by the helper method `fixTrack`. * The fix of the base notes of the instruments themselves and the extraction of the ids of automated base notes has been moved into `fixInstrumentBaseNoteAndCollectIds`. * The lambda `affected` has been converted into a static method because it is must be accessible by one of the static helper methods. * The code for fixing the automation tracks of a song has been moved into the helper function `fixAutomationTracks`. --- src/core/DataFile.cpp | 473 ++++++++++++++++++++++-------------------- 1 file changed, 252 insertions(+), 221 deletions(-) diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 009a8fe81..2d28a3090 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -1736,6 +1736,51 @@ static void fixNotePatterns(QDomNodeList & patterns) } } +static void fixInstrumentBaseNoteAndCollectIds(QDomElement & instrument, std::set & automatedBaseNoteIds) +{ + // Raise the base note of every instrument by 12 to compensate for the change + // of A4 key code from 57 to 69. This ensures that notes are labeled correctly. + QDomElement instrumentParent = instrument.parentNode().toElement(); + + // Correct the base note of the instrument. Base notes which are automated are + // stored as elements. Non-automated base notes are stored as attributes. + if (instrumentParent.hasAttribute("basenote")) + { + // TODO Base notes can have float values in the save file! This might need to be changed! + int const currentBaseNote = instrumentParent.attribute("basenote").toInt(); + instrumentParent.setAttribute("basenote", currentBaseNote + 12); + } + else + { + // Check if the instrument track has an automated base note. + // Correct the value of the base note and collect their ids while doing so. + // The ids are used later to find the automations that automate these corrected base + // notes so that we can correct the automation values as well. + QDomNodeList baseNotes = instrumentParent.elementsByTagName("basenote"); + for (int j = 0; j < baseNotes.size(); ++j) + { + QDomElement baseNote = baseNotes.item(j).toElement(); + if (!baseNote.isNull()) + { + if (baseNote.hasAttribute("value")) + { + // Base notes can have float values in the save file, e.g. if the file + // is saved after a linear automation has run on the base note. Therefore + // it is fixed here as a float even if the nominal values of base notes + // are integers. + float const value = baseNote.attribute("value").toFloat(); + baseNote.setAttribute("value", value + 12); + } + + // The ids of base notes are of type jo_id_t which are in fact uint32_t. + // So let's just use these here to save some casting. + unsigned int const id = baseNote.attribute("id").toUInt(); + automatedBaseNoteIds.insert(id); + } + } + } +} + /** * @brief Helper method that fixes the values and out values for an automation pattern. * @param automationPattern The automation pattern to be fixed. @@ -1759,101 +1804,45 @@ static void fixAutomationPattern(QDomElement & automationPattern) }; } -/** \brief Note range has been extended to match MIDI specification - * - * The non-standard note range previously affected all MIDI-based instruments - * except OpulenZ, and made them sound an octave lower than they should (#1857). - */ -void DataFile::upgrade_extendedNoteRange() +static bool affected(QDomElement & instrument) { - auto affected = [](const QDomElement& instrument) + assert(instrument.hasAttribute("name")); + QString const name = instrument.attribute("name"); + + return name == "zynaddsubfx" || + name == "vestige" || name == "lv2instrument" || + name == "carlapatchbay" || name == "carlarack"; +} + +static void fixTrack(QDomElement & track, std::set & automatedBaseNoteIds) +{ + if (!track.hasAttribute("type")) { - assert(instrument.hasAttribute("name")); - QString const name = instrument.attribute("name"); + return; + } - return name == "zynaddsubfx" || - name == "vestige" || name == "lv2instrument" || - name == "carlapatchbay" || name == "carlarack"; - }; + Track::TrackTypes const trackType = static_cast(track.attribute("type").toInt()); - if (!elementsByTagName("song").item(0).isNull()) + // BB tracks need special handling because they container a track container of their own + if (trackType == Track::PatternTrack) { - // This set will later contain all ids of automated base notes. They are - // used to find out which automation patterns must to be corrected, i.e. to - // check if an automation pattern has one or more base notes as its target. - std::set automatedBaseNoteIds; - - // Dealing with a project file, go through all the tracks - QDomNodeList tracks = elementsByTagName("track"); - for (int i = 0; i < tracks.size(); ++i) + // Assuming that a BB track cannot contain another BB track here... + QDomNodeList subTracks = track.elementsByTagName("track"); + for (int i = 0; i < subTracks.size(); ++i) { - QDomElement currentTrack = tracks.item(i).toElement(); - if (!currentTrack.hasAttribute("type")) - { - continue; - } - Track::TrackTypes const trackType = static_cast(currentTrack.attribute("type").toInt()); + QDomElement subTrack = subTracks.item(i).toElement(); + fixTrack(subTrack, automatedBaseNoteIds); + } + } + else + { + QDomNodeList instruments = track.elementsByTagName("instrument"); - // Ignore BB container tracks - if (trackType == Track::PatternTrack) - { - continue; - } + for (int i = 0; i < instruments.size(); ++i) + { + QDomElement instrument = instruments.item(i).toElement(); - QDomNodeList instruments = currentTrack.elementsByTagName("instrument"); - - if (instruments.isEmpty()) - { - continue; - } - - assert(instruments.size() < 2 && "More than one instrument found in a track!"); - - QDomElement instrument = instruments.item(0).toElement(); - - if (instrument.isNull()) - { - continue; - } - - // Raise the base note of every instrument by 12 to compensate for the change - // of A4 key code from 57 to 69. This ensures that notes are labeled correctly. - QDomElement instrumentParent = instrument.parentNode().toElement(); - - // Correct the base note of the instrument. Base notes which are automated are - // stored as elements. Non-automated base notes are stored as attributes. - if (instrumentParent.hasAttribute("basenote")) - { - // TODO Base notes can have float values in the save file! This might need to be changed! - int const currentBaseNote = instrumentParent.attribute("basenote").toInt(); - instrumentParent.setAttribute("basenote", currentBaseNote + 12); - } - else - { - // Check if the instrument track has an automated base note. - // Correct the value of the base note and collect their ids while doing so. - // The ids are used later to find the automations that automate these corrected base - // notes so that we can correct the automation values as well. - QDomNodeList baseNotes = instrumentParent.elementsByTagName("basenote"); - for (int j = 0; j < baseNotes.size(); ++j) - { - QDomElement baseNote = baseNotes.item(j).toElement(); - if (!baseNote.isNull()) - { - if (baseNote.hasAttribute("value")) - { - // TODO Base notes can have float values in the save file! This might need to be changed! - int const value = baseNote.attribute("value").toInt(); - baseNote.setAttribute("value", value + 12); - } - - // The ids of base notes are of type jo_id_t which are in fact uint32_t. - // So let's just use these here to save some casting. - unsigned int const id = baseNote.attribute("id").toUInt(); - automatedBaseNoteIds.insert(id); - } - } - } + fixInstrumentBaseNoteAndCollectIds(instrument, automatedBaseNoteIds); // Raise the pitch of all notes in patterns assigned to instruments not affected // by #1857 by an octave. This negates the base note change for normal instruments, @@ -1861,159 +1850,201 @@ void DataFile::upgrade_extendedNoteRange() // pitch in existing projects. if (!affected(instrument)) { - QDomNodeList patterns = currentTrack.elementsByTagName("pattern"); + QDomNodeList patterns = track.elementsByTagName("pattern"); fixNotePatterns(patterns); } } + } +} - // Now fix all the automation tracks. - // We have to collect the tracks that we need to duplicate and cannot do this in-place - // because if we did the iteration might never stop. - std::vector tracksToDuplicate; - tracksToDuplicate.reserve(tracks.size()); +static void fixAutomationTracks(QDomElement & song, std::set const & automatedBaseNoteIds) +{ + // Now fix all the automation tracks. + QDomNodeList tracks = song.elementsByTagName("track"); - // Iterate the tracks again. This time work on all automation tracks. - for (int i = 0; i < tracks.size(); ++i) + // We have to collect the tracks that we need to duplicate and cannot do this in-place + // because if we did the iteration might never stop. + std::vector tracksToDuplicate; + tracksToDuplicate.reserve(tracks.size()); + + // Iterate the tracks again. This time work on all automation tracks. + for (int i = 0; i < tracks.size(); ++i) + { + QDomElement currentTrack = tracks.item(i).toElement(); + if (currentTrack.attribute("type").toInt() != Track::AutomationTrack) { - QDomElement currentTrack = tracks.item(i).toElement(); - if (currentTrack.attribute("type").toInt() != Track::AutomationTrack) - { - continue; - } - - // Check each track for the types of automations it contains in its patterns. - bool containsPatternsWithBaseNoteTargets = false; - bool containsPatternsWithNonBaseNoteTargets = false; - - QDomElement automationPattern = currentTrack.firstChildElement("automationpattern"); - while (!automationPattern.isNull()) - { - auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); - containsPatternsWithBaseNoteTargets |= analysis.hasBaseNoteAutomations; - containsPatternsWithNonBaseNoteTargets |= analysis.hasNonBaseNoteAutomations; - - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - - if (!containsPatternsWithBaseNoteTargets) - { - // No base notes are automated by this automation track so we have nothing to do - continue; - } - else - { - if (!containsPatternsWithNonBaseNoteTargets) - { - // Only base note targets. This means we can simply keep the track and fix it. - automationPattern = currentTrack.firstChildElement("automationpattern"); - while (!automationPattern.isNull()) - { - fixAutomationPattern(automationPattern); - - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - } - else - { - // The automation track has automations for base notes and other targets in its patterns. - // We will later need to duplicate/split the track. - tracksToDuplicate.push_back(currentTrack); - } - } + continue; } - // Now fix the tracks that need duplication/splitting - for (QDomElement & track : tracksToDuplicate) + // Check each track for the types of automations it contains in its patterns. + bool containsPatternsWithBaseNoteTargets = false; + bool containsPatternsWithNonBaseNoteTargets = false; + + QDomElement automationPattern = currentTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) { - // First clone the original track - QDomNode cloneOfTrack = track.cloneNode(); + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + containsPatternsWithBaseNoteTargets |= analysis.hasBaseNoteAutomations; + containsPatternsWithNonBaseNoteTargets |= analysis.hasNonBaseNoteAutomations; - // Now that we have the original and the clone we can manipulate both of them. - // The original track will keep only patterns without base note automations. - // Note: for the original track these might also be automation patterns without - // any targets. We will keep these because they might have been saved by the users - // like this. - QDomElement automationPattern = track.firstChildElement("automationpattern"); - while (!automationPattern.isNull()) + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + + if (!containsPatternsWithBaseNoteTargets) + { + // No base notes are automated by this automation track so we have nothing to do + continue; + } + else + { + if (!containsPatternsWithNonBaseNoteTargets) { - auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); - if (!analysis.hasBaseNoteAutomations) + // Only base note targets. This means we can simply keep the track and fix it. + automationPattern = currentTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) { - // This pattern has no base note automations. Leave it alone. - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - else if (!analysis.hasNonBaseNoteAutomations) - { - // The pattern only has base note automations. Remove it completely as it would become empty. - QDomElement patternToRemove = automationPattern; - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - track.removeChild(patternToRemove); - } - else - { - // The pattern itself is mixed. Remove the base note objects. - QDomElement object = automationPattern.firstChildElement("object"); - while(!object.isNull()) - { - unsigned int const id = object.attribute("id").toUInt(); - - if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) - { - QDomElement objectToRemove = object; - object = object.nextSiblingElement("object"); - automationPattern.removeChild(objectToRemove); - } - else - { - object = object.nextSiblingElement("object"); - } - } - - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - } - - // The clone will only keep non-empty patterns with base note automations and the values of the patterns will be corrected. - automationPattern = cloneOfTrack.firstChildElement("automationpattern"); - while (!automationPattern.isNull()) - { - auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); - if (analysis.hasBaseNoteAutomations) - { - // This pattern has base note automations. Remove all other ones and fix the pattern. - QDomElement object = automationPattern.firstChildElement("object"); - while(!object.isNull()) - { - unsigned int const id = object.attribute("id").toUInt(); - - if (automatedBaseNoteIds.find(id) == automatedBaseNoteIds.end()) - { - QDomElement objectToRemove = object; - object = object.nextSiblingElement("object"); - automationPattern.removeChild(objectToRemove); - } - else - { - object = object.nextSiblingElement("object"); - } - } - fixAutomationPattern(automationPattern); automationPattern = automationPattern.nextSiblingElement("automationpattern"); } - else - { - // The pattern has no base note automations. Remove it completely. - QDomElement patternToRemove = automationPattern; - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - cloneOfTrack.removeChild(patternToRemove); - } } - track.parentNode().appendChild(cloneOfTrack); + else + { + // The automation track has automations for base notes and other targets in its patterns. + // We will later need to duplicate/split the track. + tracksToDuplicate.push_back(currentTrack); + } } } - else + + // Now fix the tracks that need duplication/splitting + for (QDomElement & track : tracksToDuplicate) + { + // First clone the original track + QDomNode cloneOfTrack = track.cloneNode(); + + // Now that we have the original and the clone we can manipulate both of them. + // The original track will keep only patterns without base note automations. + // Note: for the original track these might also be automation patterns without + // any targets. We will keep these because they might have been saved by the users + // like this. + QDomElement automationPattern = track.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + if (!analysis.hasBaseNoteAutomations) + { + // This pattern has no base note automations. Leave it alone. + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + else if (!analysis.hasNonBaseNoteAutomations) + { + // The pattern only has base note automations. Remove it completely as it would become empty. + QDomElement patternToRemove = automationPattern; + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + track.removeChild(patternToRemove); + } + else + { + // The pattern itself is mixed. Remove the base note objects. + QDomElement object = automationPattern.firstChildElement("object"); + while(!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) + { + QDomElement objectToRemove = object; + object = object.nextSiblingElement("object"); + automationPattern.removeChild(objectToRemove); + } + else + { + object = object.nextSiblingElement("object"); + } + } + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + } + + // The clone will only keep non-empty patterns with base note automations + // and the values of the patterns will be corrected. + automationPattern = cloneOfTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + if (analysis.hasBaseNoteAutomations) + { + // This pattern has base note automations. Remove all other ones and fix the pattern. + QDomElement object = automationPattern.firstChildElement("object"); + while(!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + if (automatedBaseNoteIds.find(id) == automatedBaseNoteIds.end()) + { + QDomElement objectToRemove = object; + object = object.nextSiblingElement("object"); + automationPattern.removeChild(objectToRemove); + } + else + { + object = object.nextSiblingElement("object"); + } + } + + fixAutomationPattern(automationPattern); + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + else + { + // The pattern has no base note automations. Remove it completely. + QDomElement patternToRemove = automationPattern; + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + cloneOfTrack.removeChild(patternToRemove); + } + } + track.parentNode().appendChild(cloneOfTrack); + } +} + +/** \brief Note range has been extended to match MIDI specification + * + * The non-standard note range previously affected all MIDI-based instruments + * except OpulenZ, and made them sound an octave lower than they should (#1857). + */ +void DataFile::upgrade_extendedNoteRange() +{ + QDomElement song = documentElement().firstChildElement("song"); + while (!song.isNull()) + { + // This set will later contain all ids of automated base notes. They are + // used to find out which automation patterns must to be corrected, i.e. to + // check if an automation pattern has one or more base notes as its target. + std::set automatedBaseNoteIds; + + QDomElement trackContainer = song.firstChildElement("trackcontainer"); + while (!trackContainer.isNull()) + { + QDomElement track = trackContainer.firstChildElement("track"); + while (!track.isNull()) + { + fixTrack(track, automatedBaseNoteIds); + + track = track.nextSiblingElement("track"); + } + + trackContainer = trackContainer.nextSiblingElement("trackcontainer"); + } + + fixAutomationTracks(song, automatedBaseNoteIds); + + song = song.nextSiblingElement("song"); + }; + + if (elementsByTagName("song").item(0).isNull()) { // Dealing with a preset, not a song QDomNodeList presets = elementsByTagName("instrumenttrack"); From dac1f773470123ee0d4f9708db165021f2e00a75 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Fri, 11 Aug 2023 17:55:26 +0200 Subject: [PATCH 7/9] Code review changes (#6548) Add a space after some while loops. Fix a typo in a comment. --- src/core/DataFile.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 2d28a3090..81bdc1fe2 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -1699,7 +1699,7 @@ static PatternAnalysisResult analyzeAutomationPattern(QDomElement const & automa // Iterate the objects. These contain the ids of the automated objects. QDomElement object = automationPattern.firstChildElement("object"); - while(!object.isNull()) + while (!object.isNull()) { unsigned int const id = object.attribute("id").toUInt(); @@ -1788,7 +1788,7 @@ static void fixInstrumentBaseNoteAndCollectIds(QDomElement & instrument, std::se static void fixAutomationPattern(QDomElement & automationPattern) { QDomElement time = automationPattern.firstChildElement("time"); - while(!time.isNull()) + while (!time.isNull()) { // Automation patterns can automate base notes as floats // so we read and correct them as floats here. @@ -1823,7 +1823,7 @@ static void fixTrack(QDomElement & track, std::set & automatedBase Track::TrackTypes const trackType = static_cast(track.attribute("type").toInt()); - // BB tracks need special handling because they container a track container of their own + // BB tracks need special handling because they contain a track container of their own if (trackType == Track::PatternTrack) { // Assuming that a BB track cannot contain another BB track here... @@ -1948,7 +1948,7 @@ static void fixAutomationTracks(QDomElement & song, std::set const { // The pattern itself is mixed. Remove the base note objects. QDomElement object = automationPattern.firstChildElement("object"); - while(!object.isNull()) + while (!object.isNull()) { unsigned int const id = object.attribute("id").toUInt(); @@ -1978,7 +1978,7 @@ static void fixAutomationTracks(QDomElement & song, std::set const { // This pattern has base note automations. Remove all other ones and fix the pattern. QDomElement object = automationPattern.firstChildElement("object"); - while(!object.isNull()) + while (!object.isNull()) { unsigned int const id = object.attribute("id").toUInt(); From 5335f6d8c6274f02957d8d7b4a0f7a8f1ebc6055 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Fri, 11 Aug 2023 18:33:35 +0200 Subject: [PATCH 8/9] Separate class for extended note range upgrade (#6548) Extract the code that upgrades the extended note range into its own class. This hides the helper functions that are related to the upgrade from the other upgrade methods in DataFile.cpp. The header file is put into the src/core directory as it is not part of the public interface and therefore should not be included in the "global" include directory. The whole thing is just an implementation detail of the upgrade in DataFile.cpp. --- src/core/CMakeLists.txt | 2 + src/core/DataFile.cpp | 391 +---------------------- src/core/UpgradeExtendedNoteRange.cpp | 431 ++++++++++++++++++++++++++ src/core/UpgradeExtendedNoteRange.h | 47 +++ 4 files changed, 483 insertions(+), 388 deletions(-) create mode 100644 src/core/UpgradeExtendedNoteRange.cpp create mode 100644 src/core/UpgradeExtendedNoteRange.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b8809ed78..319882af2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -77,6 +77,8 @@ set(LMMS_SRCS core/ToolPlugin.cpp core/Track.cpp core/TrackContainer.cpp + core/UpgradeExtendedNoteRange.h + core/UpgradeExtendedNoteRange.cpp core/Clip.cpp core/ValueBuffer.cpp core/VstSyncController.cpp diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 81bdc1fe2..2361b1926 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -28,8 +28,6 @@ #include #include -#include -#include #include #include @@ -49,6 +47,7 @@ #include "TextFloat.h" #include "Track.h" #include "PathUtil.h" +#include "UpgradeExtendedNoteRange.h" #include "lmmsversion.h" @@ -1671,344 +1670,6 @@ void DataFile::upgrade_automationNodes() } } -/** - * @brief Used by the helper function that analyzes automation patterns. - */ -struct PatternAnalysisResult -{ - PatternAnalysisResult(bool hasBaseNoteAutomations, bool hasNonBaseNoteAutomations) - { - this->hasBaseNoteAutomations = hasBaseNoteAutomations; - this->hasNonBaseNoteAutomations = hasNonBaseNoteAutomations; - } - bool hasBaseNoteAutomations; - bool hasNonBaseNoteAutomations; -}; - -/** - * @brief Helper function that checks for an automation pattern if it contains automation for - * targets that are base notes and/or other targets. - * @param automationPattern The automation pattern to be checked. - * @param automatedBaseNoteIds A set of id of automated base notes that are used in the check. - * @return A struct that contains the results. - */ -static PatternAnalysisResult analyzeAutomationPattern(QDomElement const & automationPattern, std::set const & automatedBaseNoteIds) -{ - bool hasBaseNoteAutomations = false; - bool hasNonBaseNoteAutomations = false; - - // Iterate the objects. These contain the ids of the automated objects. - QDomElement object = automationPattern.firstChildElement("object"); - while (!object.isNull()) - { - unsigned int const id = object.attribute("id").toUInt(); - - // Check if the automated object is a base note. - if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) - { - hasBaseNoteAutomations = true; - } - else - { - hasNonBaseNoteAutomations = true; - } - - object = object.nextSiblingElement("object"); - } - - return PatternAnalysisResult(hasBaseNoteAutomations, hasNonBaseNoteAutomations); -} - -static void fixNotePatterns(QDomNodeList & patterns) -{ - for (int i = 0; i < patterns.size(); ++i) - { - QDomNodeList notes = patterns.item(i).toElement().elementsByTagName("note"); - for (int j = 0; j < notes.size(); ++j) - { - QDomElement note = notes.item(j).toElement(); - if (note.hasAttribute("key")) - { - int const currentKey = note.attribute("key").toInt(); - note.setAttribute("key", currentKey + 12); - } - } - } -} - -static void fixInstrumentBaseNoteAndCollectIds(QDomElement & instrument, std::set & automatedBaseNoteIds) -{ - // Raise the base note of every instrument by 12 to compensate for the change - // of A4 key code from 57 to 69. This ensures that notes are labeled correctly. - QDomElement instrumentParent = instrument.parentNode().toElement(); - - // Correct the base note of the instrument. Base notes which are automated are - // stored as elements. Non-automated base notes are stored as attributes. - if (instrumentParent.hasAttribute("basenote")) - { - // TODO Base notes can have float values in the save file! This might need to be changed! - int const currentBaseNote = instrumentParent.attribute("basenote").toInt(); - instrumentParent.setAttribute("basenote", currentBaseNote + 12); - } - else - { - // Check if the instrument track has an automated base note. - // Correct the value of the base note and collect their ids while doing so. - // The ids are used later to find the automations that automate these corrected base - // notes so that we can correct the automation values as well. - QDomNodeList baseNotes = instrumentParent.elementsByTagName("basenote"); - for (int j = 0; j < baseNotes.size(); ++j) - { - QDomElement baseNote = baseNotes.item(j).toElement(); - if (!baseNote.isNull()) - { - if (baseNote.hasAttribute("value")) - { - // Base notes can have float values in the save file, e.g. if the file - // is saved after a linear automation has run on the base note. Therefore - // it is fixed here as a float even if the nominal values of base notes - // are integers. - float const value = baseNote.attribute("value").toFloat(); - baseNote.setAttribute("value", value + 12); - } - - // The ids of base notes are of type jo_id_t which are in fact uint32_t. - // So let's just use these here to save some casting. - unsigned int const id = baseNote.attribute("id").toUInt(); - automatedBaseNoteIds.insert(id); - } - } - } -} - -/** - * @brief Helper method that fixes the values and out values for an automation pattern. - * @param automationPattern The automation pattern to be fixed. - */ -static void fixAutomationPattern(QDomElement & automationPattern) -{ - QDomElement time = automationPattern.firstChildElement("time"); - while (!time.isNull()) - { - // Automation patterns can automate base notes as floats - // so we read and correct them as floats here. - float const value = time.attribute("value").toFloat(); - time.setAttribute("value", value + 12.); - - // The method "upgrade_automationNodes" adds some attributes - // with the name "outValue". We have to correct these as well. - float const outValue = time.attribute("outValue").toFloat(); - time.setAttribute("outValue", outValue + 12.); - - time = time.nextSiblingElement("time"); - }; -} - -static bool affected(QDomElement & instrument) -{ - assert(instrument.hasAttribute("name")); - QString const name = instrument.attribute("name"); - - return name == "zynaddsubfx" || - name == "vestige" || name == "lv2instrument" || - name == "carlapatchbay" || name == "carlarack"; -} - -static void fixTrack(QDomElement & track, std::set & automatedBaseNoteIds) -{ - if (!track.hasAttribute("type")) - { - return; - } - - Track::TrackTypes const trackType = static_cast(track.attribute("type").toInt()); - - // BB tracks need special handling because they contain a track container of their own - if (trackType == Track::PatternTrack) - { - // Assuming that a BB track cannot contain another BB track here... - QDomNodeList subTracks = track.elementsByTagName("track"); - for (int i = 0; i < subTracks.size(); ++i) - { - QDomElement subTrack = subTracks.item(i).toElement(); - fixTrack(subTrack, automatedBaseNoteIds); - } - } - else - { - QDomNodeList instruments = track.elementsByTagName("instrument"); - - for (int i = 0; i < instruments.size(); ++i) - { - QDomElement instrument = instruments.item(i).toElement(); - - fixInstrumentBaseNoteAndCollectIds(instrument, automatedBaseNoteIds); - - // Raise the pitch of all notes in patterns assigned to instruments not affected - // by #1857 by an octave. This negates the base note change for normal instruments, - // but leaves the MIDI-based instruments sounding an octave lower, preserving their - // pitch in existing projects. - if (!affected(instrument)) - { - QDomNodeList patterns = track.elementsByTagName("pattern"); - fixNotePatterns(patterns); - } - } - } -} - -static void fixAutomationTracks(QDomElement & song, std::set const & automatedBaseNoteIds) -{ - // Now fix all the automation tracks. - QDomNodeList tracks = song.elementsByTagName("track"); - - // We have to collect the tracks that we need to duplicate and cannot do this in-place - // because if we did the iteration might never stop. - std::vector tracksToDuplicate; - tracksToDuplicate.reserve(tracks.size()); - - // Iterate the tracks again. This time work on all automation tracks. - for (int i = 0; i < tracks.size(); ++i) - { - QDomElement currentTrack = tracks.item(i).toElement(); - if (currentTrack.attribute("type").toInt() != Track::AutomationTrack) - { - continue; - } - - // Check each track for the types of automations it contains in its patterns. - bool containsPatternsWithBaseNoteTargets = false; - bool containsPatternsWithNonBaseNoteTargets = false; - - QDomElement automationPattern = currentTrack.firstChildElement("automationpattern"); - while (!automationPattern.isNull()) - { - auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); - containsPatternsWithBaseNoteTargets |= analysis.hasBaseNoteAutomations; - containsPatternsWithNonBaseNoteTargets |= analysis.hasNonBaseNoteAutomations; - - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - - if (!containsPatternsWithBaseNoteTargets) - { - // No base notes are automated by this automation track so we have nothing to do - continue; - } - else - { - if (!containsPatternsWithNonBaseNoteTargets) - { - // Only base note targets. This means we can simply keep the track and fix it. - automationPattern = currentTrack.firstChildElement("automationpattern"); - while (!automationPattern.isNull()) - { - fixAutomationPattern(automationPattern); - - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - } - else - { - // The automation track has automations for base notes and other targets in its patterns. - // We will later need to duplicate/split the track. - tracksToDuplicate.push_back(currentTrack); - } - } - } - - // Now fix the tracks that need duplication/splitting - for (QDomElement & track : tracksToDuplicate) - { - // First clone the original track - QDomNode cloneOfTrack = track.cloneNode(); - - // Now that we have the original and the clone we can manipulate both of them. - // The original track will keep only patterns without base note automations. - // Note: for the original track these might also be automation patterns without - // any targets. We will keep these because they might have been saved by the users - // like this. - QDomElement automationPattern = track.firstChildElement("automationpattern"); - while (!automationPattern.isNull()) - { - auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); - if (!analysis.hasBaseNoteAutomations) - { - // This pattern has no base note automations. Leave it alone. - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - else if (!analysis.hasNonBaseNoteAutomations) - { - // The pattern only has base note automations. Remove it completely as it would become empty. - QDomElement patternToRemove = automationPattern; - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - track.removeChild(patternToRemove); - } - else - { - // The pattern itself is mixed. Remove the base note objects. - QDomElement object = automationPattern.firstChildElement("object"); - while (!object.isNull()) - { - unsigned int const id = object.attribute("id").toUInt(); - - if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) - { - QDomElement objectToRemove = object; - object = object.nextSiblingElement("object"); - automationPattern.removeChild(objectToRemove); - } - else - { - object = object.nextSiblingElement("object"); - } - } - - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - } - - // The clone will only keep non-empty patterns with base note automations - // and the values of the patterns will be corrected. - automationPattern = cloneOfTrack.firstChildElement("automationpattern"); - while (!automationPattern.isNull()) - { - auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); - if (analysis.hasBaseNoteAutomations) - { - // This pattern has base note automations. Remove all other ones and fix the pattern. - QDomElement object = automationPattern.firstChildElement("object"); - while (!object.isNull()) - { - unsigned int const id = object.attribute("id").toUInt(); - - if (automatedBaseNoteIds.find(id) == automatedBaseNoteIds.end()) - { - QDomElement objectToRemove = object; - object = object.nextSiblingElement("object"); - automationPattern.removeChild(objectToRemove); - } - else - { - object = object.nextSiblingElement("object"); - } - } - - fixAutomationPattern(automationPattern); - - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - } - else - { - // The pattern has no base note automations. Remove it completely. - QDomElement patternToRemove = automationPattern; - automationPattern = automationPattern.nextSiblingElement("automationpattern"); - cloneOfTrack.removeChild(patternToRemove); - } - } - track.parentNode().appendChild(cloneOfTrack); - } -} /** \brief Note range has been extended to match MIDI specification * @@ -2017,54 +1678,8 @@ static void fixAutomationTracks(QDomElement & song, std::set const */ void DataFile::upgrade_extendedNoteRange() { - QDomElement song = documentElement().firstChildElement("song"); - while (!song.isNull()) - { - // This set will later contain all ids of automated base notes. They are - // used to find out which automation patterns must to be corrected, i.e. to - // check if an automation pattern has one or more base notes as its target. - std::set automatedBaseNoteIds; - - QDomElement trackContainer = song.firstChildElement("trackcontainer"); - while (!trackContainer.isNull()) - { - QDomElement track = trackContainer.firstChildElement("track"); - while (!track.isNull()) - { - fixTrack(track, automatedBaseNoteIds); - - track = track.nextSiblingElement("track"); - } - - trackContainer = trackContainer.nextSiblingElement("trackcontainer"); - } - - fixAutomationTracks(song, automatedBaseNoteIds); - - song = song.nextSiblingElement("song"); - }; - - if (elementsByTagName("song").item(0).isNull()) - { - // Dealing with a preset, not a song - QDomNodeList presets = elementsByTagName("instrumenttrack"); - if (presets.item(0).isNull()) { return; } - QDomElement preset = presets.item(0).toElement(); - // Common correction for all instrument presets (make base notes match the new MIDI range). - // NOTE: Many older presets do not have any basenote defined, assume they were "made for 57". - // (Specifying a default like this also happens to fix a FileBrowser bug where previews of presets - // with undefined basenote always play with the basenote inherited from previously played preview.) - int oldBase = preset.attribute("basenote", "57").toInt(); - preset.setAttribute("basenote", oldBase + 12); - // Extra correction for Zyn, VeSTige, LV2 and Carla (to preserve the original buggy behavior). - QDomNodeList instruments = presets.item(0).toElement().elementsByTagName("instrument"); - if (instruments.isEmpty()) { return; } - QDomElement instrument = instruments.item(0).toElement(); - if (affected(instrument)) - { - preset.setAttribute("basenote", preset.attribute("basenote").toInt() + 12); - } - } + auto root = documentElement(); + UpgradeExtendedNoteRange upgradeExtendedNoteRange(root); } diff --git a/src/core/UpgradeExtendedNoteRange.cpp b/src/core/UpgradeExtendedNoteRange.cpp new file mode 100644 index 000000000..aab47f6b1 --- /dev/null +++ b/src/core/UpgradeExtendedNoteRange.cpp @@ -0,0 +1,431 @@ +/* + * UpgradeExtendedNoteRange.cpp - Upgrades the extended note range + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "UpgradeExtendedNoteRange.h" + +#include "Track.h" + +#include + +#include + +namespace lmms +{ + +/** + * @brief Used by the helper function that analyzes automation patterns. + */ +struct PatternAnalysisResult +{ + PatternAnalysisResult(bool hasBaseNoteAutomations, bool hasNonBaseNoteAutomations) + { + this->hasBaseNoteAutomations = hasBaseNoteAutomations; + this->hasNonBaseNoteAutomations = hasNonBaseNoteAutomations; + } + bool hasBaseNoteAutomations; + bool hasNonBaseNoteAutomations; +}; + +/** + * @brief Helper function that checks for an automation pattern if it contains automation for + * targets that are base notes and/or other targets. + * @param automationPattern The automation pattern to be checked. + * @param automatedBaseNoteIds A set of id of automated base notes that are used in the check. + * @return A struct that contains the results. + */ +static PatternAnalysisResult analyzeAutomationPattern(QDomElement const & automationPattern, std::set const & automatedBaseNoteIds) +{ + bool hasBaseNoteAutomations = false; + bool hasNonBaseNoteAutomations = false; + + // Iterate the objects. These contain the ids of the automated objects. + QDomElement object = automationPattern.firstChildElement("object"); + while (!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + // Check if the automated object is a base note. + if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) + { + hasBaseNoteAutomations = true; + } + else + { + hasNonBaseNoteAutomations = true; + } + + object = object.nextSiblingElement("object"); + } + + return PatternAnalysisResult(hasBaseNoteAutomations, hasNonBaseNoteAutomations); +} + +static void fixNotePatterns(QDomNodeList & patterns) +{ + for (int i = 0; i < patterns.size(); ++i) + { + QDomNodeList notes = patterns.item(i).toElement().elementsByTagName("note"); + for (int j = 0; j < notes.size(); ++j) + { + QDomElement note = notes.item(j).toElement(); + if (note.hasAttribute("key")) + { + int const currentKey = note.attribute("key").toInt(); + note.setAttribute("key", currentKey + 12); + } + } + } +} + +static void fixInstrumentBaseNoteAndCollectIds(QDomElement & instrument, std::set & automatedBaseNoteIds) +{ + // Raise the base note of every instrument by 12 to compensate for the change + // of A4 key code from 57 to 69. This ensures that notes are labeled correctly. + QDomElement instrumentParent = instrument.parentNode().toElement(); + + // Correct the base note of the instrument. Base notes which are automated are + // stored as elements. Non-automated base notes are stored as attributes. + if (instrumentParent.hasAttribute("basenote")) + { + // TODO Base notes can have float values in the save file! This might need to be changed! + int const currentBaseNote = instrumentParent.attribute("basenote").toInt(); + instrumentParent.setAttribute("basenote", currentBaseNote + 12); + } + else + { + // Check if the instrument track has an automated base note. + // Correct the value of the base note and collect their ids while doing so. + // The ids are used later to find the automations that automate these corrected base + // notes so that we can correct the automation values as well. + QDomNodeList baseNotes = instrumentParent.elementsByTagName("basenote"); + for (int j = 0; j < baseNotes.size(); ++j) + { + QDomElement baseNote = baseNotes.item(j).toElement(); + if (!baseNote.isNull()) + { + if (baseNote.hasAttribute("value")) + { + // Base notes can have float values in the save file, e.g. if the file + // is saved after a linear automation has run on the base note. Therefore + // it is fixed here as a float even if the nominal values of base notes + // are integers. + float const value = baseNote.attribute("value").toFloat(); + baseNote.setAttribute("value", value + 12); + } + + // The ids of base notes are of type jo_id_t which are in fact uint32_t. + // So let's just use these here to save some casting. + unsigned int const id = baseNote.attribute("id").toUInt(); + automatedBaseNoteIds.insert(id); + } + } + } +} + +/** + * @brief Helper method that fixes the values and out values for an automation pattern. + * @param automationPattern The automation pattern to be fixed. + */ +static void fixAutomationPattern(QDomElement & automationPattern) +{ + QDomElement time = automationPattern.firstChildElement("time"); + while (!time.isNull()) + { + // Automation patterns can automate base notes as floats + // so we read and correct them as floats here. + float const value = time.attribute("value").toFloat(); + time.setAttribute("value", value + 12.); + + // The method "upgrade_automationNodes" adds some attributes + // with the name "outValue". We have to correct these as well. + float const outValue = time.attribute("outValue").toFloat(); + time.setAttribute("outValue", outValue + 12.); + + time = time.nextSiblingElement("time"); + }; +} + +static bool affected(QDomElement & instrument) +{ + assert(instrument.hasAttribute("name")); + QString const name = instrument.attribute("name"); + + return name == "zynaddsubfx" || + name == "vestige" || name == "lv2instrument" || + name == "carlapatchbay" || name == "carlarack"; +} + +static void fixTrack(QDomElement & track, std::set & automatedBaseNoteIds) +{ + if (!track.hasAttribute("type")) + { + return; + } + + Track::TrackTypes const trackType = static_cast(track.attribute("type").toInt()); + + // BB tracks need special handling because they contain a track container of their own + if (trackType == Track::PatternTrack) + { + // Assuming that a BB track cannot contain another BB track here... + QDomNodeList subTracks = track.elementsByTagName("track"); + for (int i = 0; i < subTracks.size(); ++i) + { + QDomElement subTrack = subTracks.item(i).toElement(); + fixTrack(subTrack, automatedBaseNoteIds); + } + } + else + { + QDomNodeList instruments = track.elementsByTagName("instrument"); + + for (int i = 0; i < instruments.size(); ++i) + { + QDomElement instrument = instruments.item(i).toElement(); + + fixInstrumentBaseNoteAndCollectIds(instrument, automatedBaseNoteIds); + + // Raise the pitch of all notes in patterns assigned to instruments not affected + // by #1857 by an octave. This negates the base note change for normal instruments, + // but leaves the MIDI-based instruments sounding an octave lower, preserving their + // pitch in existing projects. + if (!affected(instrument)) + { + QDomNodeList patterns = track.elementsByTagName("pattern"); + fixNotePatterns(patterns); + } + } + } +} + +static void fixAutomationTracks(QDomElement & song, std::set const & automatedBaseNoteIds) +{ + // Now fix all the automation tracks. + QDomNodeList tracks = song.elementsByTagName("track"); + + // We have to collect the tracks that we need to duplicate and cannot do this in-place + // because if we did the iteration might never stop. + std::vector tracksToDuplicate; + tracksToDuplicate.reserve(tracks.size()); + + // Iterate the tracks again. This time work on all automation tracks. + for (int i = 0; i < tracks.size(); ++i) + { + QDomElement currentTrack = tracks.item(i).toElement(); + if (currentTrack.attribute("type").toInt() != Track::AutomationTrack) + { + continue; + } + + // Check each track for the types of automations it contains in its patterns. + bool containsPatternsWithBaseNoteTargets = false; + bool containsPatternsWithNonBaseNoteTargets = false; + + QDomElement automationPattern = currentTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + containsPatternsWithBaseNoteTargets |= analysis.hasBaseNoteAutomations; + containsPatternsWithNonBaseNoteTargets |= analysis.hasNonBaseNoteAutomations; + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + + if (!containsPatternsWithBaseNoteTargets) + { + // No base notes are automated by this automation track so we have nothing to do + continue; + } + else + { + if (!containsPatternsWithNonBaseNoteTargets) + { + // Only base note targets. This means we can simply keep the track and fix it. + automationPattern = currentTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + fixAutomationPattern(automationPattern); + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + } + else + { + // The automation track has automations for base notes and other targets in its patterns. + // We will later need to duplicate/split the track. + tracksToDuplicate.push_back(currentTrack); + } + } + } + + // Now fix the tracks that need duplication/splitting + for (QDomElement & track : tracksToDuplicate) + { + // First clone the original track + QDomNode cloneOfTrack = track.cloneNode(); + + // Now that we have the original and the clone we can manipulate both of them. + // The original track will keep only patterns without base note automations. + // Note: for the original track these might also be automation patterns without + // any targets. We will keep these because they might have been saved by the users + // like this. + QDomElement automationPattern = track.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + if (!analysis.hasBaseNoteAutomations) + { + // This pattern has no base note automations. Leave it alone. + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + else if (!analysis.hasNonBaseNoteAutomations) + { + // The pattern only has base note automations. Remove it completely as it would become empty. + QDomElement patternToRemove = automationPattern; + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + track.removeChild(patternToRemove); + } + else + { + // The pattern itself is mixed. Remove the base note objects. + QDomElement object = automationPattern.firstChildElement("object"); + while (!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) + { + QDomElement objectToRemove = object; + object = object.nextSiblingElement("object"); + automationPattern.removeChild(objectToRemove); + } + else + { + object = object.nextSiblingElement("object"); + } + } + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + } + + // The clone will only keep non-empty patterns with base note automations + // and the values of the patterns will be corrected. + automationPattern = cloneOfTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + if (analysis.hasBaseNoteAutomations) + { + // This pattern has base note automations. Remove all other ones and fix the pattern. + QDomElement object = automationPattern.firstChildElement("object"); + while (!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + if (automatedBaseNoteIds.find(id) == automatedBaseNoteIds.end()) + { + QDomElement objectToRemove = object; + object = object.nextSiblingElement("object"); + automationPattern.removeChild(objectToRemove); + } + else + { + object = object.nextSiblingElement("object"); + } + } + + fixAutomationPattern(automationPattern); + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + else + { + // The pattern has no base note automations. Remove it completely. + QDomElement patternToRemove = automationPattern; + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + cloneOfTrack.removeChild(patternToRemove); + } + } + track.parentNode().appendChild(cloneOfTrack); + } +} + + +UpgradeExtendedNoteRange::UpgradeExtendedNoteRange(QDomElement & domElement) : + m_domElement(domElement) +{ +} + +void UpgradeExtendedNoteRange::upgrade() +{ + QDomElement song = m_domElement.firstChildElement("song"); + while (!song.isNull()) + { + // This set will later contain all ids of automated base notes. They are + // used to find out which automation patterns must to be corrected, i.e. to + // check if an automation pattern has one or more base notes as its target. + std::set automatedBaseNoteIds; + + QDomElement trackContainer = song.firstChildElement("trackcontainer"); + while (!trackContainer.isNull()) + { + QDomElement track = trackContainer.firstChildElement("track"); + while (!track.isNull()) + { + fixTrack(track, automatedBaseNoteIds); + + track = track.nextSiblingElement("track"); + } + + trackContainer = trackContainer.nextSiblingElement("trackcontainer"); + } + + fixAutomationTracks(song, automatedBaseNoteIds); + + song = song.nextSiblingElement("song"); + }; + + if (m_domElement.elementsByTagName("song").item(0).isNull()) + { + // Dealing with a preset, not a song + QDomNodeList presets = m_domElement.elementsByTagName("instrumenttrack"); + if (presets.item(0).isNull()) { return; } + QDomElement preset = presets.item(0).toElement(); + // Common correction for all instrument presets (make base notes match the new MIDI range). + // NOTE: Many older presets do not have any basenote defined, assume they were "made for 57". + // (Specifying a default like this also happens to fix a FileBrowser bug where previews of presets + // with undefined basenote always play with the basenote inherited from previously played preview.) + int oldBase = preset.attribute("basenote", "57").toInt(); + preset.setAttribute("basenote", oldBase + 12); + // Extra correction for Zyn, VeSTige, LV2 and Carla (to preserve the original buggy behavior). + QDomNodeList instruments = presets.item(0).toElement().elementsByTagName("instrument"); + if (instruments.isEmpty()) { return; } + QDomElement instrument = instruments.item(0).toElement(); + if (affected(instrument)) + { + preset.setAttribute("basenote", preset.attribute("basenote").toInt() + 12); + } + } +} + +} diff --git a/src/core/UpgradeExtendedNoteRange.h b/src/core/UpgradeExtendedNoteRange.h new file mode 100644 index 000000000..66f95419a --- /dev/null +++ b/src/core/UpgradeExtendedNoteRange.h @@ -0,0 +1,47 @@ +/* + * UpgradeExtendedNoteRange.h - Upgrades the extended note range + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#pragma once + +#ifndef LMMS_UPGRADEEXTENDEDNOTERANGE_H +#define LMMS_UPGRADEEXTENDEDNOTERANGE_H + + +class QDomElement; + +namespace lmms +{ + +class UpgradeExtendedNoteRange +{ +public: + UpgradeExtendedNoteRange(QDomElement & domElement); + + void upgrade(); + +private: + QDomElement & m_domElement; +}; + +} + +#endif // LMMS_UPGRADEEXTENDEDNOTERANGE_H From 5a6799314abf4cca8e8280eefef0f5ca5ca854fc Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Fri, 11 Aug 2023 18:44:13 +0200 Subject: [PATCH 9/9] Fix builds (#6548) Add the comment "// namespace lmms" to the closing scope of the lmms namespaces. The "scripted-checks" are quite strict. Perhaps they should be renamed to "strict-checks". ;) Include "cassert" in UpgradeExtendedNoteRange.cpp to hopefully fix the mingw builds. --- src/core/UpgradeExtendedNoteRange.cpp | 4 +++- src/core/UpgradeExtendedNoteRange.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/UpgradeExtendedNoteRange.cpp b/src/core/UpgradeExtendedNoteRange.cpp index aab47f6b1..41cbc5c26 100644 --- a/src/core/UpgradeExtendedNoteRange.cpp +++ b/src/core/UpgradeExtendedNoteRange.cpp @@ -27,6 +27,8 @@ #include #include +#include + namespace lmms { @@ -428,4 +430,4 @@ void UpgradeExtendedNoteRange::upgrade() } } -} +} // namespace lmms diff --git a/src/core/UpgradeExtendedNoteRange.h b/src/core/UpgradeExtendedNoteRange.h index 66f95419a..ae444e82a 100644 --- a/src/core/UpgradeExtendedNoteRange.h +++ b/src/core/UpgradeExtendedNoteRange.h @@ -42,6 +42,6 @@ private: QDomElement & m_domElement; }; -} +} // namespace lmms #endif // LMMS_UPGRADEEXTENDEDNOTERANGE_H