mirror of
https://github.com/LMMS/lmms.git
synced 2026-05-17 11:18:30 -04:00
1906 lines
52 KiB
C++
1906 lines
52 KiB
C++
/*
|
|
* DataFile.cpp - implementation of class DataFile
|
|
*
|
|
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
|
* Copyright (c) 2012-2013 Paul Giblock <p/at/pgiblock.net>
|
|
*
|
|
* 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 "DataFile.h"
|
|
|
|
#include <math.h>
|
|
#include <map>
|
|
|
|
#include <QDebug>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QDir>
|
|
#include <QMessageBox>
|
|
|
|
#include "base64.h"
|
|
#include "ConfigManager.h"
|
|
#include "Effect.h"
|
|
#include "embed.h"
|
|
#include "GuiApplication.h"
|
|
#include "LocaleHelper.h"
|
|
#include "PluginFactory.h"
|
|
#include "ProjectVersion.h"
|
|
#include "SongEditor.h"
|
|
#include "TextFloat.h"
|
|
#include "PathUtil.h"
|
|
|
|
#include "lmmsversion.h"
|
|
|
|
static void findIds(const QDomElement& elem, QList<jo_id_t>& idList);
|
|
|
|
|
|
// QMap with the DOM elements that access file resources
|
|
const DataFile::ResourcesMap DataFile::ELEMENTS_WITH_RESOURCES = {
|
|
{ "sampletco", {"src"} },
|
|
{ "audiofileprocessor", {"src"} },
|
|
};
|
|
|
|
// Vector with all the upgrade methods
|
|
const std::vector<DataFile::UpgradeMethod> DataFile::UPGRADE_METHODS = {
|
|
&DataFile::upgrade_0_2_1_20070501 , &DataFile::upgrade_0_2_1_20070508,
|
|
&DataFile::upgrade_0_3_0_rc2 , &DataFile::upgrade_0_3_0,
|
|
&DataFile::upgrade_0_4_0_20080104 , &DataFile::upgrade_0_4_0_20080118,
|
|
&DataFile::upgrade_0_4_0_20080129 , &DataFile::upgrade_0_4_0_20080409,
|
|
&DataFile::upgrade_0_4_0_20080607 , &DataFile::upgrade_0_4_0_20080622,
|
|
&DataFile::upgrade_0_4_0_beta1 , &DataFile::upgrade_0_4_0_rc2,
|
|
&DataFile::upgrade_1_0_99 , &DataFile::upgrade_1_1_0,
|
|
&DataFile::upgrade_1_1_91 , &DataFile::upgrade_1_2_0_rc3,
|
|
&DataFile::upgrade_1_3_0 , &DataFile::upgrade_noHiddenClipNames,
|
|
&DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange,
|
|
&DataFile::upgrade_defaultTripleOscillatorHQ
|
|
};
|
|
|
|
// Vector of all versions that have upgrade routines.
|
|
const std::vector<ProjectVersion> DataFile::UPGRADE_VERSIONS = {
|
|
"0.2.1-20070501" , "0.2.1-20070508" , "0.3.0-rc2",
|
|
"0.3.0" , "0.4.0-20080104" , "0.4.0-20080118",
|
|
"0.4.0-20080129" , "0.4.0-20080409" , "0.4.0-20080607",
|
|
"0.4.0-20080622" , "0.4.0-beta1" , "0.4.0-rc2",
|
|
"1.0.99-0" , "1.1.0-0" , "1.1.91-0",
|
|
"1.2.0-rc3" , "1.3.0"
|
|
};
|
|
|
|
DataFile::typeDescStruct
|
|
DataFile::s_types[DataFile::TypeCount] =
|
|
{
|
|
{ DataFile::UnknownType, "unknown" },
|
|
{ DataFile::SongProject, "song" },
|
|
{ DataFile::SongProjectTemplate, "songtemplate" },
|
|
{ DataFile::InstrumentTrackSettings, "instrumenttracksettings" },
|
|
{ DataFile::DragNDropData, "dnddata" },
|
|
{ DataFile::ClipboardData, "clipboard-data" },
|
|
{ DataFile::JournalData, "journaldata" },
|
|
{ DataFile::EffectSettings, "effectsettings" },
|
|
{ DataFile::NotePattern, "pattern" }
|
|
} ;
|
|
|
|
|
|
|
|
|
|
DataFile::DataFile( Type type ) :
|
|
QDomDocument( "lmms-project" ),
|
|
m_fileName(""),
|
|
m_content(),
|
|
m_head(),
|
|
m_type( type ),
|
|
m_fileVersion( UPGRADE_METHODS.size() )
|
|
{
|
|
appendChild( createProcessingInstruction("xml", "version=\"1.0\""));
|
|
QDomElement root = createElement( "lmms-project" );
|
|
root.setAttribute( "version", m_fileVersion );
|
|
root.setAttribute( "type", typeName( type ) );
|
|
root.setAttribute( "creator", "LMMS" );
|
|
root.setAttribute( "creatorversion", LMMS_VERSION );
|
|
appendChild( root );
|
|
|
|
m_head = createElement( "head" );
|
|
root.appendChild( m_head );
|
|
|
|
m_content = createElement( typeName( type ) );
|
|
root.appendChild( m_content );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DataFile::DataFile( const QString & _fileName ) :
|
|
QDomDocument(),
|
|
m_fileName(_fileName),
|
|
m_content(),
|
|
m_head(),
|
|
m_fileVersion( UPGRADE_METHODS.size() )
|
|
{
|
|
QFile inFile( _fileName );
|
|
if( !inFile.open( QIODevice::ReadOnly ) )
|
|
{
|
|
if( getGUI() != nullptr )
|
|
{
|
|
QMessageBox::critical( nullptr,
|
|
SongEditor::tr( "Could not open file" ),
|
|
SongEditor::tr( "Could not open file %1. You probably "
|
|
"have no permissions to read this "
|
|
"file.\n Please make sure to have at "
|
|
"least read permissions to the file "
|
|
"and try again." ).arg( _fileName ) );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
loadData( inFile.readAll(), _fileName );
|
|
}
|
|
|
|
|
|
|
|
|
|
DataFile::DataFile( const QByteArray & _data ) :
|
|
QDomDocument(),
|
|
m_fileName(""),
|
|
m_content(),
|
|
m_head(),
|
|
m_fileVersion( UPGRADE_METHODS.size() )
|
|
{
|
|
loadData( _data, "<internal data>" );
|
|
}
|
|
|
|
|
|
|
|
|
|
DataFile::~DataFile()
|
|
{
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DataFile::validate( QString extension )
|
|
{
|
|
switch( m_type )
|
|
{
|
|
case Type::SongProject:
|
|
if( extension == "mmp" || extension == "mmpz" )
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case Type::SongProjectTemplate:
|
|
if( extension == "mpt" )
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case Type::InstrumentTrackSettings:
|
|
if ( extension == "xpf" || extension == "xml" )
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case Type::NotePattern:
|
|
if (extension == "xpt" || extension == "xptz")
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case Type::UnknownType:
|
|
if (! ( extension == "mmp" || extension == "mpt" || extension == "mmpz" ||
|
|
extension == "xpf" || extension == "xml" ||
|
|
( extension == "xiz" && ! getPluginFactory()->pluginSupportingExtension(extension).isNull()) ||
|
|
extension == "sf2" || extension == "sf3" || extension == "pat" || extension == "mid" ||
|
|
extension == "dll"
|
|
#ifdef LMMS_HAVE_LV2
|
|
|| extension == "lv2"
|
|
#endif
|
|
) )
|
|
{
|
|
return true;
|
|
}
|
|
if( extension == "wav" || extension == "ogg" ||
|
|
extension == "ds" )
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
QString DataFile::nameWithExtension( const QString & _fn ) const
|
|
{
|
|
const QString extension = _fn.section( '.', -1 );
|
|
|
|
switch( type() )
|
|
{
|
|
case SongProject:
|
|
if( extension != "mmp" &&
|
|
extension != "mpt" &&
|
|
extension != "mmpz" )
|
|
{
|
|
if( ConfigManager::inst()->value( "app",
|
|
"nommpz" ).toInt() == 0 )
|
|
{
|
|
return _fn + ".mmpz";
|
|
}
|
|
return _fn + ".mmp";
|
|
}
|
|
break;
|
|
case SongProjectTemplate:
|
|
if( extension != "mpt" )
|
|
{
|
|
return _fn + ".mpt";
|
|
}
|
|
break;
|
|
case InstrumentTrackSettings:
|
|
if( extension != "xpf" )
|
|
{
|
|
return _fn + ".xpf";
|
|
}
|
|
break;
|
|
default: ;
|
|
}
|
|
return _fn;
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataFile::write( QTextStream & _strm )
|
|
{
|
|
if( type() == SongProject || type() == SongProjectTemplate
|
|
|| type() == InstrumentTrackSettings )
|
|
{
|
|
cleanMetaNodes( documentElement() );
|
|
}
|
|
|
|
save(_strm, 2);
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DataFile::writeFile(const QString& filename, bool withResources)
|
|
{
|
|
// Small lambda function for displaying errors
|
|
auto showError = [this](QString title, QString body){
|
|
if (getGUI() != nullptr)
|
|
{
|
|
QMessageBox mb;
|
|
mb.setWindowTitle(title);
|
|
mb.setText(body);
|
|
mb.setIcon(QMessageBox::Warning);
|
|
mb.setStandardButtons(QMessageBox::Ok);
|
|
mb.exec();
|
|
}
|
|
else
|
|
{
|
|
qWarning() << body;
|
|
}
|
|
};
|
|
|
|
// If we are saving without resources, filename is just the file we are
|
|
// saving to. If we are saving with resources (project bundle), filename
|
|
// will be used (discarding extensions) to create a folder where the
|
|
// bundle will be saved in
|
|
|
|
QFileInfo fInfo(filename);
|
|
|
|
const QString bundleDir = fInfo.path() + "/" + fInfo.fileName().section('.', 0, 0);
|
|
const QString resourcesDir = bundleDir + "/resources";
|
|
const QString fullName = withResources
|
|
? nameWithExtension(bundleDir + "/" + fInfo.fileName())
|
|
: nameWithExtension(filename);
|
|
const QString fullNameTemp = fullName + ".new";
|
|
const QString fullNameBak = fullName + ".bak";
|
|
|
|
// If we are saving with resources, setup the bundle folder first
|
|
if (withResources)
|
|
{
|
|
// First check if there's a bundle folder with the same name in
|
|
// the path already. If so, warns user that we can't overwrite a
|
|
// project bundle.
|
|
if (QDir(bundleDir).exists())
|
|
{
|
|
showError(SongEditor::tr("Operation denied"),
|
|
SongEditor::tr("A bundle folder with that name already eists on the "
|
|
"selected path. Can't overwrite a project bundle. Please select a different "
|
|
"name."));
|
|
|
|
return false;
|
|
}
|
|
|
|
// Create bundle folder
|
|
if (!QDir().mkdir(bundleDir))
|
|
{
|
|
showError(SongEditor::tr("Error"),
|
|
SongEditor::tr("Couldn't create bundle folder."));
|
|
return false;
|
|
}
|
|
|
|
// Create resources folder
|
|
if (!QDir().mkdir(resourcesDir))
|
|
{
|
|
showError(SongEditor::tr("Error"),
|
|
SongEditor::tr("Couldn't create resources folder."));
|
|
return false;
|
|
}
|
|
|
|
// Copy resources to folder and update paths
|
|
if (!copyResources(resourcesDir))
|
|
{
|
|
showError(SongEditor::tr("Error"),
|
|
SongEditor::tr("Failed to copy resources."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QFile outfile (fullNameTemp);
|
|
|
|
if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
|
{
|
|
showError(SongEditor::tr("Could not write file"),
|
|
SongEditor::tr("Could not open %1 for writing. You probably are not permitted to"
|
|
"write to this file. Please make sure you have write-access to "
|
|
"the file and try again.").arg(fullName));
|
|
|
|
return false;
|
|
}
|
|
|
|
const QString extension = fullName.section('.', -1);
|
|
if (extension == "mmpz" || extension == "xptz")
|
|
{
|
|
QString xml;
|
|
QTextStream ts( &xml );
|
|
write( ts );
|
|
outfile.write( qCompress( xml.toUtf8() ) );
|
|
}
|
|
else
|
|
{
|
|
QTextStream ts( &outfile );
|
|
write( ts );
|
|
}
|
|
|
|
outfile.close();
|
|
|
|
// make sure the file has been written correctly
|
|
if( QFileInfo( outfile.fileName() ).size() > 0 )
|
|
{
|
|
if( ConfigManager::inst()->value( "app", "disablebackup" ).toInt() )
|
|
{
|
|
// remove current file
|
|
QFile::remove( fullName );
|
|
}
|
|
else
|
|
{
|
|
// remove old backup file
|
|
QFile::remove( fullNameBak );
|
|
// move current file to backup file
|
|
QFile::rename( fullName, fullNameBak );
|
|
}
|
|
// move temporary file to current file
|
|
QFile::rename( fullNameTemp, fullName );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DataFile::copyResources(const QString& resourcesDir)
|
|
{
|
|
// List of filenames used so we can append a counter to any
|
|
// repeating filenames
|
|
std::list<QString> namesList;
|
|
|
|
ResourcesMap::const_iterator it = ELEMENTS_WITH_RESOURCES.begin();
|
|
|
|
// Copy resources and manipulate the DataFile to have local paths to them
|
|
while (it != ELEMENTS_WITH_RESOURCES.end())
|
|
{
|
|
QDomNodeList list = elementsByTagName(it->first);
|
|
|
|
// Go through all elements with the tagname from our map
|
|
for (int i = 0; !list.item(i).isNull(); ++i)
|
|
{
|
|
QDomElement el = list.item(i).toElement();
|
|
|
|
std::vector<QString>::const_iterator res = it->second.begin();
|
|
|
|
// Search for attributes that point to resources
|
|
while (res != it->second.end())
|
|
{
|
|
// If the element has that attribute
|
|
if (el.hasAttribute(*res))
|
|
{
|
|
// Get absolute path to resource
|
|
bool error;
|
|
QString resPath = PathUtil::toAbsolute(el.attribute(*res), &error);
|
|
// If we are running without the project loaded (from CLI), "local:" base
|
|
// prefixes aren't converted, so we need to convert it ourselves
|
|
if (error)
|
|
{
|
|
resPath = QFileInfo(m_fileName).path() + "/" + resPath.remove(0,
|
|
PathUtil::basePrefix(PathUtil::Base::LocalDir).length());
|
|
}
|
|
|
|
// Check if we need to add a counter to the filename
|
|
QString finalFileName = QFileInfo(resPath).fileName();
|
|
QString extension = resPath.section('.', -1);
|
|
int repeatedNames = 0;
|
|
for (QString name : namesList)
|
|
{
|
|
if (finalFileName == name)
|
|
{
|
|
++repeatedNames;
|
|
}
|
|
}
|
|
// Add the name to the list before modifying it
|
|
namesList.push_back(finalFileName);
|
|
if (repeatedNames)
|
|
{
|
|
// Remove the extension, add the counter and add the
|
|
// extension again to get the final file name
|
|
finalFileName.truncate(finalFileName.lastIndexOf('.'));
|
|
finalFileName = finalFileName + "-" + QString::number(repeatedNames) + "." + extension;
|
|
}
|
|
|
|
// Final path is our resources dir + the new file name
|
|
QString finalPath = resourcesDir + "/" + finalFileName;
|
|
|
|
// Copy resource file to the resources folder
|
|
if(!QFile::copy(resPath, finalPath))
|
|
{
|
|
qWarning("ERROR: Failed to copy resource");
|
|
return false;
|
|
}
|
|
|
|
// Update attribute path to point to the bundle file
|
|
QString newAtt = PathUtil::basePrefix(PathUtil::Base::LocalDir) + "resources/" + finalFileName;
|
|
el.setAttribute(*res, newAtt);
|
|
}
|
|
++res;
|
|
}
|
|
}
|
|
++it;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* @brief This recursive method will go through all XML nodes of the DataFile
|
|
* and check whether any of them have local paths. If they are not on
|
|
* our list of elements that can have local paths we return true,
|
|
* indicating that we potentially have plugins with local paths that
|
|
* would be a security issue. The Song class can then abort loading
|
|
* this project.
|
|
* @param parent The parent node being iterated. When called
|
|
* without arguments, this will be an empty element that will be
|
|
* ignored (since the second parameter will be true).
|
|
* @param firstCall Defaults to true, and indicates to this recursive
|
|
* method whether this is the first call. If it is it will use the
|
|
* root element as the parent.
|
|
*/
|
|
bool DataFile::hasLocalPlugins(QDomElement parent /* = QDomElement()*/, bool firstCall /* = true*/) const
|
|
{
|
|
// If this is the first iteration of the recursion we use the root element
|
|
if (firstCall) { parent = documentElement(); }
|
|
|
|
auto children = parent.childNodes();
|
|
for (int i = 0; i < children.size(); ++i)
|
|
{
|
|
QDomNode child = children.at(i);
|
|
QDomElement childElement = child.toElement();
|
|
|
|
bool skipNode = false;
|
|
// Skip the nodes allowed to have "local:" attributes, but
|
|
// still check its children
|
|
for
|
|
(
|
|
ResourcesMap::const_iterator it = ELEMENTS_WITH_RESOURCES.begin();
|
|
it != ELEMENTS_WITH_RESOURCES.end();
|
|
++it
|
|
)
|
|
{
|
|
if (childElement.tagName() == it->first)
|
|
{
|
|
skipNode = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if they have "local:" attribute (unless they are allowed to
|
|
// and skipNode is true)
|
|
if (!skipNode)
|
|
{
|
|
auto attributes = childElement.attributes();
|
|
for (int i = 0; i < attributes.size(); ++i)
|
|
{
|
|
QDomNode attribute = attributes.item(i);
|
|
QDomAttr attr = attribute.toAttr();
|
|
if (attr.value().startsWith(PathUtil::basePrefix(PathUtil::Base::LocalDir),
|
|
Qt::CaseInsensitive))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now we check the children of this node (recursively)
|
|
// and if any return true we return true.
|
|
if (hasLocalPlugins(childElement, false))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If we got here none of the nodes had the "local:" path.
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
DataFile::Type DataFile::type( const QString& typeName )
|
|
{
|
|
for( int i = 0; i < TypeCount; ++i )
|
|
{
|
|
if( s_types[i].m_name == typeName )
|
|
{
|
|
return static_cast<DataFile::Type>( i );
|
|
}
|
|
}
|
|
|
|
// compat code
|
|
if( typeName == "channelsettings" )
|
|
{
|
|
return DataFile::InstrumentTrackSettings;
|
|
}
|
|
|
|
return UnknownType;
|
|
}
|
|
|
|
|
|
|
|
|
|
QString DataFile::typeName( Type type )
|
|
{
|
|
if( type >= UnknownType && type < TypeCount )
|
|
{
|
|
return s_types[type].m_name;
|
|
}
|
|
|
|
return s_types[UnknownType].m_name;
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataFile::cleanMetaNodes( QDomElement _de )
|
|
{
|
|
QDomNode node = _de.firstChild();
|
|
while( !node.isNull() )
|
|
{
|
|
if( node.isElement() )
|
|
{
|
|
if( node.toElement().attribute( "metadata" ).toInt() )
|
|
{
|
|
QDomNode ns = node.nextSibling();
|
|
_de.removeChild( node );
|
|
node = ns;
|
|
continue;
|
|
}
|
|
if( node.hasChildNodes() )
|
|
{
|
|
cleanMetaNodes( node.toElement() );
|
|
}
|
|
}
|
|
node = node.nextSibling();
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_2_1_20070501()
|
|
{
|
|
// Upgrade to version 0.2.1-20070501
|
|
QDomNodeList list = elementsByTagName( "arpandchords" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
if( el.hasAttribute( "arpdir" ) )
|
|
{
|
|
int arpdir = el.attribute( "arpdir" ).toInt();
|
|
if( arpdir > 0 )
|
|
{
|
|
el.setAttribute( "arpdir", arpdir - 1 );
|
|
}
|
|
else
|
|
{
|
|
el.setAttribute( "arpdisabled", "1" );
|
|
}
|
|
}
|
|
}
|
|
|
|
list = elementsByTagName( "sampletrack" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
if( el.attribute( "vol" ) != "" )
|
|
{
|
|
el.setAttribute( "vol", LocaleHelper::toFloat(
|
|
el.attribute( "vol" ) ) * 100.0f );
|
|
}
|
|
else
|
|
{
|
|
QDomNode node = el.namedItem(
|
|
"automation-pattern" );
|
|
if( !node.isElement() ||
|
|
!node.namedItem( "vol" ).isElement() )
|
|
{
|
|
el.setAttribute( "vol", 100.0f );
|
|
}
|
|
}
|
|
}
|
|
|
|
list = elementsByTagName( "ladspacontrols" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
QDomNode anode = el.namedItem( "automation-pattern" );
|
|
QDomNode node = anode.firstChild();
|
|
while( !node.isNull() )
|
|
{
|
|
if( node.isElement() )
|
|
{
|
|
QString name = node.nodeName();
|
|
if( name.endsWith( "link" ) )
|
|
{
|
|
el.setAttribute( name,
|
|
node.namedItem( "time" )
|
|
.toElement()
|
|
.attribute( "value" ) );
|
|
QDomNode oldNode = node;
|
|
node = node.nextSibling();
|
|
anode.removeChild( oldNode );
|
|
continue;
|
|
}
|
|
}
|
|
node = node.nextSibling();
|
|
}
|
|
}
|
|
|
|
QDomNode node = m_head.firstChild();
|
|
while( !node.isNull() )
|
|
{
|
|
if( node.isElement() )
|
|
{
|
|
if( node.nodeName() == "bpm" )
|
|
{
|
|
int value = node.toElement().attribute(
|
|
"value" ).toInt();
|
|
if( value > 0 )
|
|
{
|
|
m_head.setAttribute( "bpm",
|
|
value );
|
|
QDomNode oldNode = node;
|
|
node = node.nextSibling();
|
|
m_head.removeChild( oldNode );
|
|
continue;
|
|
}
|
|
}
|
|
else if( node.nodeName() == "mastervol" )
|
|
{
|
|
int value = node.toElement().attribute(
|
|
"value" ).toInt();
|
|
if( value > 0 )
|
|
{
|
|
m_head.setAttribute(
|
|
"mastervol", value );
|
|
QDomNode oldNode = node;
|
|
node = node.nextSibling();
|
|
m_head.removeChild( oldNode );
|
|
continue;
|
|
}
|
|
}
|
|
else if( node.nodeName() == "masterpitch" )
|
|
{
|
|
m_head.setAttribute( "masterpitch",
|
|
-node.toElement().attribute(
|
|
"value" ).toInt() );
|
|
QDomNode oldNode = node;
|
|
node = node.nextSibling();
|
|
m_head.removeChild( oldNode );
|
|
continue;
|
|
}
|
|
}
|
|
node = node.nextSibling();
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_2_1_20070508()
|
|
{
|
|
// Upgrade to version 0.2.1-20070508 from some version greater than or equal to 0.2.1-20070501
|
|
QDomNodeList list = elementsByTagName( "arpandchords" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
if( el.hasAttribute( "chorddisabled" ) )
|
|
{
|
|
el.setAttribute( "chord-enabled",
|
|
!el.attribute( "chorddisabled" )
|
|
.toInt() );
|
|
el.setAttribute( "arp-enabled",
|
|
!el.attribute( "arpdisabled" )
|
|
.toInt() );
|
|
}
|
|
else if( !el.hasAttribute( "chord-enabled" ) )
|
|
{
|
|
el.setAttribute( "chord-enabled", true );
|
|
el.setAttribute( "arp-enabled",
|
|
el.attribute( "arpdir" ).toInt() != 0 );
|
|
}
|
|
}
|
|
|
|
while( !( list = elementsByTagName( "channeltrack" ) ).isEmpty() )
|
|
{
|
|
QDomElement el = list.item( 0 ).toElement();
|
|
el.setTagName( "instrumenttrack" );
|
|
}
|
|
|
|
list = elementsByTagName( "instrumenttrack" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
if( el.hasAttribute( "vol" ) )
|
|
{
|
|
float value = LocaleHelper::toFloat( el.attribute( "vol" ) );
|
|
value = roundf( value * 0.585786438f );
|
|
el.setAttribute( "vol", value );
|
|
}
|
|
else
|
|
{
|
|
QDomNodeList vol_list = el.namedItem(
|
|
"automation-pattern" )
|
|
.namedItem( "vol" ).toElement()
|
|
.elementsByTagName( "time" );
|
|
for( int j = 0; !vol_list.item( j ).isNull();
|
|
++j )
|
|
{
|
|
QDomElement timeEl = list.item( j )
|
|
.toElement();
|
|
int value = timeEl.attribute( "value" )
|
|
.toInt();
|
|
value = (int)roundf( value *
|
|
0.585786438f );
|
|
timeEl.setAttribute( "value", value );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_3_0_rc2()
|
|
{
|
|
// Upgrade to version 0.3.0-rc2 from some version greater than or equal to 0.2.1-20070508
|
|
QDomNodeList list = elementsByTagName( "arpandchords" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
if( el.attribute( "arpdir" ).toInt() > 0 )
|
|
{
|
|
el.setAttribute( "arpdir",
|
|
el.attribute( "arpdir" ).toInt() - 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_3_0()
|
|
{
|
|
// Upgrade to version 0.3.0 (final) from some version greater than or equal to 0.3.0-rc2
|
|
QDomNodeList list;
|
|
while( !( list = elementsByTagName(
|
|
"pluckedstringsynth" ) ).isEmpty() )
|
|
{
|
|
QDomElement el = list.item( 0 ).toElement();
|
|
el.setTagName( "vibedstrings" );
|
|
el.setAttribute( "active0", 1 );
|
|
}
|
|
|
|
while( !( list = elementsByTagName( "lb303" ) ).isEmpty() )
|
|
{
|
|
QDomElement el = list.item( 0 ).toElement();
|
|
el.setTagName( "lb302" );
|
|
}
|
|
|
|
while( !( list = elementsByTagName( "channelsettings" ) ).
|
|
isEmpty() )
|
|
{
|
|
QDomElement el = list.item( 0 ).toElement();
|
|
el.setTagName( "instrumenttracksettings" );
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_4_0_20080104()
|
|
{
|
|
// Upgrade to version 0.4.0-20080104 from some version greater than or equal to 0.3.0 (final)
|
|
QDomNodeList list = elementsByTagName( "fx" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
if( el.hasAttribute( "fxdisabled" ) &&
|
|
el.attribute( "fxdisabled" ).toInt() == 0 )
|
|
{
|
|
el.setAttribute( "enabled", 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_4_0_20080118()
|
|
{
|
|
// Upgrade to version 0.4.0-20080118 from some version greater than or equal to 0.4.0-20080104
|
|
QDomNodeList list;
|
|
while( !( list = elementsByTagName( "fx" ) ).isEmpty() )
|
|
{
|
|
QDomElement fxchain = list.item( 0 ).toElement();
|
|
fxchain.setTagName( "fxchain" );
|
|
QDomNode rack = list.item( 0 ).firstChild();
|
|
QDomNodeList effects = rack.childNodes();
|
|
// move items one level up
|
|
while( effects.count() )
|
|
{
|
|
fxchain.appendChild( effects.at( 0 ) );
|
|
}
|
|
fxchain.setAttribute( "numofeffects",
|
|
rack.toElement().attribute( "numofeffects" ) );
|
|
fxchain.removeChild( rack );
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_4_0_20080129()
|
|
{
|
|
// Upgrade to version 0.4.0-20080129 from some version greater than or equal to 0.4.0-20080118
|
|
QDomNodeList list;
|
|
while( !( list =
|
|
elementsByTagName( "arpandchords" ) ).isEmpty() )
|
|
{
|
|
QDomElement aac = list.item( 0 ).toElement();
|
|
aac.setTagName( "arpeggiator" );
|
|
QDomNode cloned = aac.cloneNode();
|
|
cloned.toElement().setTagName( "chordcreator" );
|
|
aac.parentNode().appendChild( cloned );
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_4_0_20080409()
|
|
{
|
|
// Upgrade to version 0.4.0-20080409 from some version greater than or equal to 0.4.0-20080129
|
|
QStringList s;
|
|
s << "note" << "pattern" << "bbtco" << "sampletco" << "time";
|
|
for( QStringList::iterator it = s.begin(); it < s.end(); ++it )
|
|
{
|
|
QDomNodeList list = elementsByTagName( *it );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
el.setAttribute( "pos",
|
|
el.attribute( "pos" ).toInt()*3 );
|
|
el.setAttribute( "len",
|
|
el.attribute( "len" ).toInt()*3 );
|
|
}
|
|
}
|
|
QDomNodeList list = elementsByTagName( "timeline" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
el.setAttribute( "lp0pos",
|
|
el.attribute( "lp0pos" ).toInt()*3 );
|
|
el.setAttribute( "lp1pos",
|
|
el.attribute( "lp1pos" ).toInt()*3 );
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_4_0_20080607()
|
|
{
|
|
// Upgrade to version 0.4.0-20080607 from some version greater than or equal to 0.3.0-20080409
|
|
QDomNodeList list;
|
|
while( !( list = elementsByTagName( "midi" ) ).isEmpty() )
|
|
{
|
|
QDomElement el = list.item( 0 ).toElement();
|
|
el.setTagName( "midiport" );
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_4_0_20080622()
|
|
{
|
|
// Upgrade to version 0.4.0-20080622 from some version greater than or equal to 0.3.0-20080607
|
|
QDomNodeList list;
|
|
while( !( list = elementsByTagName(
|
|
"automation-pattern" ) ).isEmpty() )
|
|
{
|
|
QDomElement el = list.item( 0 ).toElement();
|
|
el.setTagName( "automationpattern" );
|
|
}
|
|
|
|
list = elementsByTagName( "bbtrack" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
QString s = el.attribute( "name" );
|
|
s.replace( QRegExp( "^Beat/Baseline " ),
|
|
"Beat/Bassline " );
|
|
el.setAttribute( "name", s );
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_4_0_beta1()
|
|
{
|
|
// Upgrade to version 0.4.0-beta1 from some version greater than or equal to 0.4.0-20080622
|
|
// convert binary effect-key-blobs to XML
|
|
QDomNodeList list;
|
|
list = elementsByTagName( "effect" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
QString k = el.attribute( "key" );
|
|
if( !k.isEmpty() )
|
|
{
|
|
const QList<QVariant> l =
|
|
base64::decode( k, QVariant::List ).toList();
|
|
if( !l.isEmpty() )
|
|
{
|
|
QString name = l[0].toString();
|
|
QVariant u = l[1];
|
|
EffectKey::AttributeMap m;
|
|
// VST-effect?
|
|
if( u.type() == QVariant::String )
|
|
{
|
|
m["file"] = u.toString();
|
|
}
|
|
// LADSPA-effect?
|
|
else if( u.type() == QVariant::StringList )
|
|
{
|
|
const QStringList sl = u.toStringList();
|
|
m["plugin"] = sl.value( 0 );
|
|
m["file"] = sl.value( 1 );
|
|
}
|
|
EffectKey key( nullptr, name, m );
|
|
el.appendChild( key.saveXML( *this ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_0_4_0_rc2()
|
|
{
|
|
// Upgrade to version 0.4.0-rc2 from some version greater than or equal to 0.4.0-beta1
|
|
QDomNodeList list = elementsByTagName( "audiofileprocessor" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
QString s = el.attribute( "src" );
|
|
s.replace( "drumsynth/misc ", "drumsynth/misc_" );
|
|
s.replace( "drumsynth/r&b", "drumsynth/r_n_b" );
|
|
s.replace( "drumsynth/r_b", "drumsynth/r_n_b" );
|
|
el.setAttribute( "src", s );
|
|
}
|
|
list = elementsByTagName( "lb302" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
int s = el.attribute( "shape" ).toInt();
|
|
if( s >= 1 )
|
|
{
|
|
s--;
|
|
}
|
|
el.setAttribute( "shape", QString("%1").arg(s) );
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_1_0_99()
|
|
{
|
|
jo_id_t last_assigned_id = 0;
|
|
|
|
QList<jo_id_t> idList;
|
|
findIds(documentElement(), idList);
|
|
|
|
QDomNodeList list = elementsByTagName("ladspacontrols");
|
|
for(int i = 0; !list.item(i).isNull(); ++i)
|
|
{
|
|
for(QDomNode node = list.item(i).firstChild(); !node.isNull();
|
|
node = node.nextSibling())
|
|
{
|
|
QDomElement el = node.toElement();
|
|
QDomNode data_child = el.namedItem("data");
|
|
if(!data_child.isElement())
|
|
{
|
|
if (el.attribute("scale_type") == "log")
|
|
{
|
|
QDomElement me = createElement("data");
|
|
me.setAttribute("value", el.attribute("data"));
|
|
me.setAttribute("scale_type", "log");
|
|
|
|
jo_id_t id;
|
|
for(id = last_assigned_id + 1;
|
|
idList.contains(id); id++)
|
|
{
|
|
}
|
|
|
|
last_assigned_id = id;
|
|
idList.append(id);
|
|
me.setAttribute("id", id);
|
|
el.appendChild(me);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_1_1_0()
|
|
{
|
|
QDomNodeList list = elementsByTagName("fxchannel");
|
|
for (int i = 1; !list.item(i).isNull(); ++i)
|
|
{
|
|
QDomElement el = list.item(i).toElement();
|
|
QDomElement send = createElement("send");
|
|
send.setAttribute("channel", "0");
|
|
send.setAttribute("amount", "1");
|
|
el.appendChild(send);
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_1_1_91()
|
|
{
|
|
// Upgrade to version 1.1.91 from some version less than 1.1.91
|
|
QDomNodeList list = elementsByTagName( "audiofileprocessor" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
QString s = el.attribute( "src" );
|
|
s.replace( QRegExp("/samples/bassloopes/"), "/samples/bassloops/" );
|
|
el.setAttribute( "src", s );
|
|
}
|
|
|
|
list = elementsByTagName( "attribute" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
if( el.attribute( "name" ) == "plugin" && el.attribute( "value" ) == "vocoder-lmms" ) {
|
|
el.setAttribute( "value", "vocoder" );
|
|
}
|
|
}
|
|
|
|
list = elementsByTagName( "crossoevereqcontrols" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
// invert the mute LEDs
|
|
for( int j = 1; j <= 4; ++j ){
|
|
QString a = QString( "mute%1" ).arg( j );
|
|
el.setAttribute( a, ( el.attribute( a ) == "0" ) ? "1" : "0" );
|
|
}
|
|
}
|
|
|
|
list = elementsByTagName( "arpeggiator" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
// Swap elements ArpDirRandom and ArpDirDownAndUp
|
|
if( el.attribute( "arpdir" ) == "3" )
|
|
{
|
|
el.setAttribute( "arpdir", "4" );
|
|
}
|
|
else if( el.attribute( "arpdir" ) == "4" )
|
|
{
|
|
el.setAttribute( "arpdir", "3" );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void upgradeElement_1_2_0_rc2_42( QDomElement & el )
|
|
{
|
|
if( el.hasAttribute( "syncmode" ) )
|
|
{
|
|
int syncmode = el.attribute( "syncmode" ).toInt();
|
|
QStringList names;
|
|
QDomNamedNodeMap atts = el.attributes();
|
|
for( uint i = 0; i < atts.length(); i++ )
|
|
{
|
|
QString name = atts.item( i ).nodeName();
|
|
if( name.endsWith( "_numerator" ) )
|
|
{
|
|
names << name.remove( "_numerator" )
|
|
+ "_syncmode";
|
|
}
|
|
}
|
|
for( QStringList::iterator it = names.begin(); it < names.end();
|
|
++it )
|
|
{
|
|
el.setAttribute( *it, syncmode );
|
|
}
|
|
}
|
|
|
|
QDomElement child = el.firstChildElement();
|
|
while ( !child.isNull() )
|
|
{
|
|
upgradeElement_1_2_0_rc2_42( child );
|
|
child = child.nextSiblingElement();
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade_1_2_0_rc3()
|
|
{
|
|
// Upgrade from earlier bbtrack beat note behaviour of adding
|
|
// steps if a note is placed after the last step.
|
|
QDomNodeList bbtracks = elementsByTagName( "bbtrack" );
|
|
for( int i = 0; !bbtracks.item( i ).isNull(); ++i )
|
|
{
|
|
QDomNodeList patterns = bbtracks.item( i
|
|
).toElement().elementsByTagName(
|
|
"pattern" );
|
|
for( int j = 0; !patterns.item( j ).isNull(); ++j )
|
|
{
|
|
int patternLength, steps;
|
|
QDomElement el = patterns.item( j ).toElement();
|
|
if( el.attribute( "len" ) != "" )
|
|
{
|
|
patternLength = el.attribute( "len" ).toInt();
|
|
steps = patternLength / 12;
|
|
el.setAttribute( "steps", steps );
|
|
}
|
|
}
|
|
}
|
|
|
|
// DataFile::upgrade_1_2_0_rc2_42
|
|
QDomElement el = firstChildElement();
|
|
while ( !el.isNull() )
|
|
{
|
|
upgradeElement_1_2_0_rc2_42( el );
|
|
el = el.nextSiblingElement();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper function to call a functor for all effect ports' DomElements,
|
|
* providing the functor with lists to add and remove DomElements. Helpful for
|
|
* patching port values from savefiles.
|
|
*/
|
|
template<class Ftor>
|
|
void iterate_ladspa_ports(QDomElement& effect, Ftor& ftor)
|
|
{
|
|
// Head back up the DOM to upgrade ports
|
|
QDomNodeList ladspacontrols = effect.elementsByTagName( "ladspacontrols" );
|
|
for( int m = 0; !ladspacontrols.item( m ).isNull(); ++m )
|
|
{
|
|
QList<QDomElement> addList, removeList;
|
|
QDomElement ladspacontrol = ladspacontrols.item( m ).toElement();
|
|
for( QDomElement port = ladspacontrol.firstChild().toElement();
|
|
!port.isNull(); port = port.nextSibling().toElement() )
|
|
{
|
|
QStringList parts = port.tagName().split("port");
|
|
// Not a "port"
|
|
if ( parts.size() < 2 )
|
|
{
|
|
continue;
|
|
}
|
|
int num = parts[1].toInt();
|
|
|
|
// From Qt's docs of QDomNode:
|
|
// * copying a QDomNode is OK, they still have the same
|
|
// pointer to the "internal" QDomNodePrivate.
|
|
// * Also, they are using linked lists, which means
|
|
// deleting or appending QDomNode does not invalidate
|
|
// any other pointers.
|
|
// => Inside ftor, you can (and should) push back the
|
|
// QDomElements by value, not references
|
|
// => The loops below for adding and removing don't
|
|
// invalidate any other QDomElements
|
|
ftor(port, num, addList, removeList);
|
|
}
|
|
|
|
// Add ports marked for adding
|
|
for ( QDomElement e : addList )
|
|
{
|
|
ladspacontrol.appendChild( e );
|
|
}
|
|
// Remove ports marked for removal
|
|
for ( QDomElement e : removeList )
|
|
{
|
|
ladspacontrol.removeChild( e );
|
|
}
|
|
}
|
|
}
|
|
|
|
// helper function if you need to print a QDomNode
|
|
QDebug operator<<(QDebug dbg, const QDomNode& node)
|
|
{
|
|
QString s;
|
|
QTextStream str(&s, QIODevice::WriteOnly);
|
|
node.save(str, 2);
|
|
dbg << qPrintable(s);
|
|
return dbg;
|
|
}
|
|
|
|
void DataFile::upgrade_1_3_0()
|
|
{
|
|
QDomNodeList list = elementsByTagName( "instrument" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement el = list.item( i ).toElement();
|
|
if( el.attribute( "name" ) == "papu" )
|
|
{
|
|
el.setAttribute( "name", "freeboy" );
|
|
QDomNodeList children = el.elementsByTagName( "papu" );
|
|
for( int j = 0; !children.item( j ).isNull(); ++j )
|
|
{
|
|
QDomElement child = children.item( j ).toElement();
|
|
child.setTagName( "freeboy" );
|
|
}
|
|
}
|
|
else if( el.attribute( "name" ) == "OPL2" )
|
|
{
|
|
el.setAttribute( "name", "opulenz" );
|
|
QDomNodeList children = el.elementsByTagName( "OPL2" );
|
|
for( int j = 0; !children.item( j ).isNull(); ++j )
|
|
{
|
|
QDomElement child = children.item( j ).toElement();
|
|
child.setTagName( "opulenz" );
|
|
}
|
|
}
|
|
}
|
|
|
|
list = elementsByTagName( "effect" );
|
|
for( int i = 0; !list.item( i ).isNull(); ++i )
|
|
{
|
|
QDomElement effect = list.item( i ).toElement();
|
|
if( effect.attribute( "name" ) == "ladspaeffect" )
|
|
{
|
|
QDomNodeList keys = effect.elementsByTagName( "key" );
|
|
for( int j = 0; !keys.item( j ).isNull(); ++j )
|
|
{
|
|
QDomElement key = keys.item( j ).toElement();
|
|
QDomNodeList attributes = key.elementsByTagName( "attribute" );
|
|
for( int k = 0; !attributes.item( k ).isNull(); ++k )
|
|
{
|
|
// Effect name changes
|
|
|
|
QDomElement attribute = attributes.item( k ).toElement();
|
|
if( attribute.attribute( "name" ) == "file" &&
|
|
( attribute.attribute( "value" ) == "calf" ||
|
|
attribute.attribute( "value" ) == "calf.so" ) )
|
|
{
|
|
attribute.setAttribute( "value", "veal" );
|
|
}
|
|
else if( attribute.attribute( "name" ) == "plugin" &&
|
|
attribute.attribute( "value" ) == "Sidechaincompressor" )
|
|
{
|
|
attribute.setAttribute( "value", "SidechainCompressor" );
|
|
}
|
|
else if( attribute.attribute( "name" ) == "plugin" &&
|
|
attribute.attribute( "value" ) == "Sidechaingate" )
|
|
{
|
|
attribute.setAttribute( "value", "SidechainGate" );
|
|
}
|
|
else if( attribute.attribute( "name" ) == "plugin" &&
|
|
attribute.attribute( "value" ) == "Multibandcompressor" )
|
|
{
|
|
attribute.setAttribute( "value", "MultibandCompressor" );
|
|
}
|
|
else if( attribute.attribute( "name" ) == "plugin" &&
|
|
attribute.attribute( "value" ) == "Multibandgate" )
|
|
{
|
|
attribute.setAttribute( "value", "MultibandGate" );
|
|
}
|
|
else if( attribute.attribute( "name" ) == "plugin" &&
|
|
attribute.attribute( "value" ) == "Multibandlimiter" )
|
|
{
|
|
attribute.setAttribute( "value", "MultibandLimiter" );
|
|
}
|
|
|
|
// Handle port changes
|
|
|
|
if( attribute.attribute( "name" ) == "plugin" &&
|
|
( attribute.attribute( "value" ) == "MultibandLimiter" ||
|
|
attribute.attribute( "value" ) == "MultibandCompressor" ||
|
|
attribute.attribute( "value" ) == "MultibandGate" ) )
|
|
{
|
|
auto fn = [&](QDomElement& port, int num, QList<QDomElement>&, QList<QDomElement>& removeList)
|
|
{
|
|
// Mark ports for removal
|
|
if ( num >= 18 && num <= 23 )
|
|
{
|
|
removeList << port;
|
|
}
|
|
// Bump higher ports up 6 positions
|
|
else if ( num >= 24 )
|
|
{
|
|
// port01...port010, etc
|
|
QString name( "port0" );
|
|
name.append( QString::number( num -6 ) );
|
|
port.setTagName( name );
|
|
}
|
|
};
|
|
iterate_ladspa_ports(effect, fn);
|
|
}
|
|
|
|
if( attribute.attribute( "name" ) == "plugin" &&
|
|
( attribute.attribute( "value" ) == "Pulsator" ) )
|
|
{
|
|
auto fn = [&](QDomElement& port, int num, QList<QDomElement>& addList, QList<QDomElement>& removeList)
|
|
{
|
|
switch(num)
|
|
{
|
|
case 16:
|
|
{
|
|
// old freq is now at port 25
|
|
QDomElement portCopy = createElement("port025");
|
|
portCopy.setAttribute("data", port.attribute("data"));
|
|
addList << portCopy;
|
|
// remove old freq port
|
|
removeList << port;
|
|
// set the "timing" port to choose port23+2=port25 (timing in Hz)
|
|
QDomElement timing = createElement("port022");
|
|
timing.setAttribute("data", 2);
|
|
addList << timing;
|
|
break;
|
|
}
|
|
// port 18 (modulation) => 17
|
|
case 17:
|
|
port.setTagName("port016");
|
|
break;
|
|
case 18:
|
|
{
|
|
// leave port 18 (offsetr), but add port 17 (offsetl)
|
|
QDomElement offsetl = createElement("port017");
|
|
offsetl.setAttribute("data", 0.0f);
|
|
addList << offsetl;
|
|
// additional: bash port 21 to 1
|
|
QDomElement pulsewidth = createElement("port021");
|
|
pulsewidth.setAttribute("data", 1.0f);
|
|
addList << pulsewidth;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
};
|
|
iterate_ladspa_ports(effect, fn);
|
|
}
|
|
|
|
|
|
if( attribute.attribute( "name" ) == "plugin" &&
|
|
( attribute.attribute( "value" ) == "VintageDelay" ) )
|
|
{
|
|
auto fn = [&](QDomElement& port, int num, QList<QDomElement>& addList, QList<QDomElement>& )
|
|
{
|
|
switch(num)
|
|
{
|
|
case 4:
|
|
{
|
|
// BPM is now port028
|
|
port.setTagName("port028");
|
|
// bash timing to BPM
|
|
QDomElement timing = createElement("port027");
|
|
timing.setAttribute("data", 0);
|
|
addList << timing;
|
|
|
|
// port 5 and 6 (in, out gain) need to be bashed to 1:
|
|
QDomElement input = createElement("port05");
|
|
input.setAttribute("data", 1.0f);
|
|
addList << input;
|
|
QDomElement output = createElement("port06");
|
|
output.setAttribute("data", 1.0f);
|
|
addList << output;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
// all other ports increase by 10
|
|
QString name( "port0" );
|
|
name.append( QString::number( num + 10 ) );
|
|
port.setTagName( name );
|
|
}
|
|
|
|
|
|
};
|
|
iterate_ladspa_ports(effect, fn);
|
|
}
|
|
|
|
if( attribute.attribute( "name" ) == "plugin" &&
|
|
( ( attribute.attribute( "value" ) == "Equalizer5Band" )
|
|
|| ( attribute.attribute( "value" ) == "Equalizer8Band" )
|
|
|| ( attribute.attribute( "value" ) == "Equalizer12Band" ) ) )
|
|
{
|
|
// NBand equalizers got 4 q nobs inserted. We need to shift everything else...
|
|
// HOWEVER: 5 band eq has only 2 q nobs inserted (no LS/HS filters)
|
|
bool band5 = ( attribute.attribute( "value" ) == "Equalizer5Band" );
|
|
auto fn = [&](QDomElement& port, int num, QList<QDomElement>& addList, QList<QDomElement>& )
|
|
{
|
|
if(num == 4)
|
|
{
|
|
// don't modify port 4, but some other ones:
|
|
int zoom_port;
|
|
if(attribute.attribute( "value" ) == "Equalizer5Band")
|
|
zoom_port = 36;
|
|
else if(attribute.attribute( "value" ) == "Equalizer8Band")
|
|
zoom_port = 48;
|
|
else // 12 band
|
|
zoom_port = 64;
|
|
// bash zoom to 0.25
|
|
QString name( "port0" );
|
|
name.append( QString::number( zoom_port ) );
|
|
QDomElement timing = createElement(name);
|
|
timing.setAttribute("data", 0.25f);
|
|
addList << timing;
|
|
}
|
|
// the following code could be refactored, but I did careful code-reading
|
|
// to prevent copy-paste-errors
|
|
if(num == 18)
|
|
{
|
|
// 18 => 19
|
|
port.setTagName("port019");
|
|
// insert port 18 (q)
|
|
QDomElement q = createElement("port018");
|
|
q.setAttribute("data", 0.707f);
|
|
addList << q;
|
|
}
|
|
else if(num >= 19 && num <= 20)
|
|
{
|
|
// num += 1
|
|
QString name( "port0" );
|
|
name.append( QString::number( num + 1 ) );
|
|
port.setTagName( name );
|
|
}
|
|
else if(num == 21)
|
|
{
|
|
// 21 => 23
|
|
port.setTagName("port023");
|
|
// insert port 22 (q)
|
|
QDomElement q = createElement("port022");
|
|
q.setAttribute("data", 0.707f);
|
|
addList << q;
|
|
}
|
|
else if(num >= 22 && (num <= 23 || band5))
|
|
{
|
|
// num += 2
|
|
QString name( "port0" );
|
|
name.append( QString::number( num + 2 ) );
|
|
port.setTagName( name );
|
|
}
|
|
else if(num == 24 && !band5)
|
|
{
|
|
// 24 => 27
|
|
port.setTagName("port027");
|
|
// insert port 26 (q)
|
|
QDomElement q = createElement("port026");
|
|
q.setAttribute("data", 0.707f);
|
|
addList << q;
|
|
}
|
|
else if(num >= 25 && num <= 26 && !band5)
|
|
{
|
|
// num += 3
|
|
QString name( "port0" );
|
|
name.append( QString::number( num + 3 ) );
|
|
port.setTagName( name );
|
|
}
|
|
else if(num == 27 && !band5)
|
|
{
|
|
// 27 => 31
|
|
port.setTagName("port031");
|
|
// insert port 30 (q)
|
|
QDomElement q = createElement("port030");
|
|
q.setAttribute("data", 0.707f);
|
|
addList << q;
|
|
}
|
|
else if(num >= 28 && !band5)
|
|
{
|
|
// num += 4
|
|
QString name( "port0" );
|
|
name.append( QString::number( num + 4 ) );
|
|
port.setTagName( name );
|
|
}
|
|
};
|
|
iterate_ladspa_ports(effect, fn);
|
|
}
|
|
|
|
if( attribute.attribute( "name" ) == "plugin" &&
|
|
attribute.attribute( "value" ) == "Saturator" )
|
|
{
|
|
auto fn = [&](QDomElement& port, int num, QList<QDomElement>&, QList<QDomElement>& )
|
|
{
|
|
// These ports have been shifted a bit weird...
|
|
if( num == 7 )
|
|
{
|
|
port.setTagName("port015");
|
|
}
|
|
else if(num == 12)
|
|
{
|
|
port.setTagName("port016");
|
|
}
|
|
else if(num == 13)
|
|
{
|
|
port.setTagName("port017");
|
|
}
|
|
else if ( num >= 15 )
|
|
{
|
|
QString name( "port0" );
|
|
name.append( QString::number( num + 3 ) );
|
|
port.setTagName( name );
|
|
}
|
|
};
|
|
iterate_ladspa_ports(effect, fn);
|
|
}
|
|
|
|
if( attribute.attribute( "name" ) == "plugin" &&
|
|
attribute.attribute( "value" ) == "StereoTools" )
|
|
{
|
|
auto fn = [&](QDomElement& port, int num, QList<QDomElement>&, QList<QDomElement>& )
|
|
{
|
|
// This effect can not be back-ported due to bugs in the old version,
|
|
// or due to different behaviour. We thus port all parameters we can,
|
|
// and bash all new parameters (in this case, s.level and m.level) to
|
|
// their new defaults (both 1.0f in this case)
|
|
|
|
if( num == 23 || num == 25 )
|
|
{
|
|
port.setAttribute("data", 1.0f);
|
|
}
|
|
};
|
|
iterate_ladspa_ports(effect, fn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DataFile::upgrade_noHiddenClipNames()
|
|
{
|
|
QDomNodeList tracks = elementsByTagName("track");
|
|
|
|
auto clearDefaultNames = [](QDomNodeList clips, QString trackName)
|
|
{
|
|
for (int j = 0; j < clips.size(); ++j)
|
|
{
|
|
QDomElement clip = clips.item(j).toElement();
|
|
QString clipName = clip.attribute("name", "");
|
|
if (clipName == trackName) { clip.setAttribute("name", ""); }
|
|
}
|
|
};
|
|
|
|
for (int i = 0; i < tracks.size(); ++i)
|
|
{
|
|
QDomElement track = tracks.item(i).toElement();
|
|
QString trackName = track.attribute("name", "");
|
|
|
|
QDomNodeList instClips = track.elementsByTagName("pattern");
|
|
QDomNodeList autoClips = track.elementsByTagName("automationpattern");
|
|
QDomNodeList bbClips = track.elementsByTagName("bbtco");
|
|
|
|
clearDefaultNames(instClips, trackName);
|
|
clearDefaultNames(autoClips, trackName);
|
|
clearDefaultNames(bbClips, trackName);
|
|
}
|
|
}
|
|
|
|
void DataFile::upgrade_automationNodes()
|
|
{
|
|
QDomNodeList autoPatterns = elementsByTagName("automationpattern");
|
|
|
|
// Go through all automation patterns
|
|
for (int i = 0; i < autoPatterns.size(); ++i)
|
|
{
|
|
QDomElement autoPattern = autoPatterns.item(i).toElement();
|
|
|
|
// On each automation pattern, get all <time> elements
|
|
QDomNodeList times = autoPattern.elementsByTagName("time");
|
|
|
|
// Loop through all <time> elements and change what we need
|
|
for (int j=0; j < times.size(); ++j)
|
|
{
|
|
QDomElement el = times.item(j).toElement();
|
|
|
|
float value = LocaleHelper::toFloat(el.attribute("value"));
|
|
|
|
// inValue will be equal to "value" and outValue will
|
|
// be set to the same
|
|
el.setAttribute("outValue", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** \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()
|
|
{
|
|
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";
|
|
};
|
|
|
|
if (!elementsByTagName("song").item(0).isNull())
|
|
{
|
|
// Dealing with a project file, go through all the tracks
|
|
QDomNodeList tracks = elementsByTagName("track");
|
|
for (int i = 0; !tracks.item(i).isNull(); i++)
|
|
{
|
|
// Ignore BB container tracks
|
|
if (tracks.item(i).toElement().attribute("type").toInt() == 1) { continue; }
|
|
|
|
QDomNodeList instruments = tracks.item(i).toElement().elementsByTagName("instrument");
|
|
if (instruments.isEmpty()) { continue; }
|
|
QDomElement instrument = instruments.item(0).toElement();
|
|
// 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);
|
|
// 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");
|
|
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
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** \brief TripleOscillator switched to using high-quality, alias-free oscillators by default
|
|
*
|
|
* Older projects were made without this feature and would sound differently if loaded
|
|
* with the new default setting. This upgrade routine preserves their old behavior.
|
|
*/
|
|
void DataFile::upgrade_defaultTripleOscillatorHQ()
|
|
{
|
|
QDomNodeList tripleoscillators = elementsByTagName("tripleoscillator");
|
|
for (int i = 0; !tripleoscillators.item(i).isNull(); i++)
|
|
{
|
|
for (int j = 1; j <= 3; j++)
|
|
{
|
|
// Only set the attribute if it does not exist (default template has it but reports as 1.2.0)
|
|
if (tripleoscillators.item(i).toElement().attribute("useWaveTable" + QString::number(j)) == "")
|
|
{
|
|
tripleoscillators.item(i).toElement().setAttribute("useWaveTable" + QString::number(j), 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DataFile::upgrade()
|
|
{
|
|
// Runs all necessary upgrade methods
|
|
std::size_t max = std::min(static_cast<std::size_t>(m_fileVersion), UPGRADE_METHODS.size());
|
|
std::for_each( UPGRADE_METHODS.begin() + max, UPGRADE_METHODS.end(),
|
|
[this](UpgradeMethod um)
|
|
{
|
|
(this->*um)();
|
|
}
|
|
);
|
|
|
|
// Bump the file version (which should be the size of the upgrade methods vector)
|
|
m_fileVersion = UPGRADE_METHODS.size();
|
|
|
|
// update document meta data
|
|
documentElement().setAttribute( "version", m_fileVersion );
|
|
documentElement().setAttribute( "type", typeName( type() ) );
|
|
documentElement().setAttribute( "creator", "LMMS" );
|
|
documentElement().setAttribute( "creatorversion", LMMS_VERSION );
|
|
|
|
if( type() == SongProject || type() == SongProjectTemplate )
|
|
{
|
|
// Time-signature
|
|
if ( !m_head.hasAttribute( "timesig_numerator" ) )
|
|
{
|
|
m_head.setAttribute( "timesig_numerator", 4 );
|
|
m_head.setAttribute( "timesig_denominator", 4 );
|
|
}
|
|
|
|
if( !m_head.hasAttribute( "mastervol" ) )
|
|
{
|
|
m_head.setAttribute( "mastervol", 100 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataFile::loadData( const QByteArray & _data, const QString & _sourceFile )
|
|
{
|
|
QString errorMsg;
|
|
int line = -1, col = -1;
|
|
if( !setContent( _data, &errorMsg, &line, &col ) )
|
|
{
|
|
// parsing failed? then try to uncompress data
|
|
QByteArray uncompressed = qUncompress( _data );
|
|
if( !uncompressed.isEmpty() )
|
|
{
|
|
if( setContent( uncompressed, &errorMsg, &line, &col ) )
|
|
{
|
|
line = col = -1;
|
|
}
|
|
}
|
|
if( line >= 0 && col >= 0 )
|
|
{
|
|
qWarning() << "at line" << line << "column" << errorMsg;
|
|
if( getGUI() != nullptr )
|
|
{
|
|
QMessageBox::critical( nullptr,
|
|
SongEditor::tr( "Error in file" ),
|
|
SongEditor::tr( "The file %1 seems to contain "
|
|
"errors and therefore can't be "
|
|
"loaded." ).
|
|
arg( _sourceFile ) );
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
QDomElement root = documentElement();
|
|
m_type = type( root.attribute( "type" ) );
|
|
m_head = root.elementsByTagName( "head" ).item( 0 ).toElement();
|
|
|
|
if (!root.hasAttribute("version") || root.attribute("version")=="1.0")
|
|
{
|
|
// The file versioning is now a unsigned int, not maj.min, so we use
|
|
// legacyFileVersion() to retrieve the appropriate version
|
|
m_fileVersion = legacyFileVersion();
|
|
}
|
|
else
|
|
{
|
|
bool success;
|
|
m_fileVersion = root.attribute( "version" ).toUInt( &success );
|
|
if( !success ) qWarning("File Version conversion failure.");
|
|
}
|
|
|
|
if (root.hasAttribute("creatorversion"))
|
|
{
|
|
// compareType defaults to All, so it doesn't have to be set here
|
|
ProjectVersion createdWith = root.attribute("creatorversion");
|
|
ProjectVersion openedWith = LMMS_VERSION;
|
|
|
|
if (createdWith < openedWith) { upgrade(); }
|
|
|
|
if (createdWith.setCompareType(ProjectVersion::Minor)
|
|
!= openedWith.setCompareType(ProjectVersion::Minor)
|
|
&& getGUI() != nullptr && root.attribute("type") == "song"
|
|
){
|
|
auto projectType = _sourceFile.endsWith(".mpt") ?
|
|
SongEditor::tr("template") : SongEditor::tr("project");
|
|
|
|
TextFloat::displayMessage(
|
|
SongEditor::tr("Version difference"),
|
|
SongEditor::tr("This %1 was created with LMMS %2")
|
|
.arg(projectType).arg(createdWith.getVersion()),
|
|
embed::getIconPixmap("whatsthis", 24, 24),
|
|
2500
|
|
);
|
|
}
|
|
}
|
|
|
|
m_content = root.elementsByTagName(typeName(m_type)).item(0).toElement();
|
|
}
|
|
|
|
|
|
void findIds(const QDomElement& elem, QList<jo_id_t>& idList)
|
|
{
|
|
if(elem.hasAttribute("id"))
|
|
{
|
|
idList.append(elem.attribute("id").toInt());
|
|
}
|
|
QDomElement child = elem.firstChildElement();
|
|
while(!child.isNull())
|
|
{
|
|
findIds(child, idList);
|
|
child = child.nextSiblingElement();
|
|
}
|
|
}
|
|
|
|
unsigned int DataFile::legacyFileVersion()
|
|
{
|
|
// Version of LMMs that created this project
|
|
ProjectVersion creator =
|
|
documentElement().attribute( "creatorversion" ).
|
|
replace( "svn", "" );
|
|
|
|
// Get an iterator pointing at the first upgrade we need to run (or at the end if there is no such upgrade)
|
|
auto firstRequiredUpgrade = std::upper_bound( UPGRADE_VERSIONS.begin(), UPGRADE_VERSIONS.end(), creator );
|
|
|
|
// Convert the iterator to an index, which is our file version (starting at 0)
|
|
return std::distance( UPGRADE_VERSIONS.begin(), firstRequiredUpgrade );
|
|
}
|