mirror of
https://github.com/LMMS/lmms.git
synced 2026-01-13 00:48:05 -05:00
* Remove QTextCodec QTextCodec was removed from Qt6 and is only available through the Qt5Compat module. QTextCodec was only used by the HydrogenImport plugin when importing old Hydrogen files that were saved using TinyXML before it supported UTF-8. HydrogenImport would use QTextCodec to try to get the current encoding from the locale, and then use that as a best guess for interpreting the XML data in the unspecified encoding it was saved in. None of this was ever reliable, since the encoding of the computer that saved the Hydrogen file might not be the same as the computer running LMMS and importing that file. There is no good solution here, so I decided to simply assume the old Hydrogen files are UTF-8 encoded. The worst that can happen is someone's ancient Hydrogen files containing non-ASCII text of some random encoding becomes mojibake'd after importing into LMMS, which is something that already could have happened. * Clean up a little
359 lines
8.9 KiB
C++
359 lines
8.9 KiB
C++
#include "HydrogenImport.h"
|
|
|
|
#include <QDomDocument>
|
|
|
|
#include "LocalFileMng.h"
|
|
#include "Song.h"
|
|
#include "Engine.h"
|
|
#include "Instrument.h"
|
|
#include "InstrumentTrack.h"
|
|
#include "Note.h"
|
|
#include "MidiClip.h"
|
|
#include "PatternStore.h"
|
|
#include "Track.h"
|
|
|
|
#include "plugin_export.h"
|
|
|
|
#define MAX_LAYERS 4
|
|
|
|
namespace lmms
|
|
{
|
|
|
|
|
|
extern "C"
|
|
{
|
|
|
|
Plugin::Descriptor PLUGIN_EXPORT hydrogenimport_plugin_descriptor =
|
|
{
|
|
LMMS_STRINGIFY( PLUGIN_NAME ),
|
|
"Hydrogen Import",
|
|
QT_TRANSLATE_NOOP( "PluginBrowser",
|
|
"Filter for importing Hydrogen files into LMMS" ),
|
|
"frank mather",
|
|
0x0100,
|
|
Plugin::Type::ImportFilter,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
} ;
|
|
|
|
}
|
|
|
|
QString filename;
|
|
class NoteKey
|
|
{
|
|
public:
|
|
enum class Key {
|
|
C = 0,
|
|
Cs,
|
|
D,
|
|
Ef,
|
|
E,
|
|
F,
|
|
Fs,
|
|
G,
|
|
Af,
|
|
A,
|
|
Bf,
|
|
B,
|
|
};
|
|
|
|
static int stringToNoteKey( const QString& str )
|
|
{
|
|
auto m_key = Key::C;
|
|
|
|
|
|
QString sKey = str.left( str.length() - 1 );
|
|
QString sOct = str.mid( str.length() - 1, str.length() );
|
|
|
|
if ( sKey.endsWith( "-" ) )
|
|
{
|
|
sKey.replace( "-", "" );
|
|
sOct.insert( 0, "-" );
|
|
}
|
|
int nOctave = sOct.toInt();
|
|
|
|
if ( sKey == "C" )
|
|
{
|
|
m_key = Key::C;
|
|
}
|
|
else if ( sKey == "Cs" )
|
|
{
|
|
m_key = Key::Cs;
|
|
}
|
|
else if ( sKey == "D" )
|
|
{
|
|
m_key = Key::D;
|
|
}
|
|
else if ( sKey == "Ef" )
|
|
{
|
|
m_key = Key::Ef;
|
|
}
|
|
else if ( sKey == "E" )
|
|
{
|
|
m_key = Key::E;
|
|
}
|
|
else if ( sKey == "F" )
|
|
{
|
|
m_key = Key::F;
|
|
}
|
|
else if ( sKey == "Fs" )
|
|
{
|
|
m_key = Key::Fs;
|
|
}
|
|
else if ( sKey == "G" )
|
|
{
|
|
m_key = Key::G;
|
|
}
|
|
else if ( sKey == "Af" )
|
|
{
|
|
m_key = Key::Af;
|
|
}
|
|
else if ( sKey == "A" )
|
|
{
|
|
m_key = Key::A;
|
|
}
|
|
else if ( sKey == "Bf" )
|
|
{
|
|
m_key = Key::Bf;
|
|
}
|
|
else if ( sKey == "B" ) {
|
|
m_key = Key::B;
|
|
}
|
|
|
|
// Hydrogen records MIDI notes from C-1 to B5, and exports them as a number ranging from -3 to 3
|
|
return static_cast<int>(m_key) + ((nOctave + 3) * 12);
|
|
}
|
|
|
|
};
|
|
HydrogenImport::HydrogenImport( const QString & _file ) :
|
|
ImportFilter( _file, &hydrogenimport_plugin_descriptor )
|
|
{
|
|
filename = _file;
|
|
}
|
|
|
|
|
|
|
|
|
|
Instrument * ins;
|
|
bool HydrogenImport::readSong()
|
|
{
|
|
QHash<QString, InstrumentTrack *> drum_track;
|
|
QHash<QString, int> pattern_length;
|
|
QHash<QString, int> pattern_id;
|
|
|
|
Song *s = Engine::getSong();
|
|
int song_num_tracks = s->tracks().size();
|
|
if ( QFile( filename ).exists() == false )
|
|
{
|
|
printf( "Song file not found \n" );
|
|
return false;
|
|
}
|
|
QDomDocument doc = LocalFileMng::openXmlDocument( filename );
|
|
QDomNodeList nodeList = doc.elementsByTagName( "song" );
|
|
|
|
if( nodeList.isEmpty() )
|
|
{
|
|
printf( "Error reading song: song node not found\n" );
|
|
return false;
|
|
}
|
|
QDomNode songNode = nodeList.at( 0 );
|
|
|
|
QString m_sSongVersion = LocalFileMng::readXmlString( songNode , "version", "Unknown version" );
|
|
|
|
|
|
QString sName( LocalFileMng::readXmlString( songNode, "name", "Untitled Song" ) );
|
|
QString sAuthor( LocalFileMng::readXmlString( songNode, "author", "Unknown Author" ) );
|
|
QString sNotes( LocalFileMng::readXmlString( songNode, "notes", "..." ) );
|
|
QString sLicense( LocalFileMng::readXmlString( songNode, "license", "Unknown license" ) );
|
|
QString sMode = LocalFileMng::readXmlString( songNode, "mode", "pattern" );
|
|
|
|
QDomNode instrumentListNode = songNode.firstChildElement( "instrumentList" );
|
|
if ( ( ! instrumentListNode.isNull() ) )
|
|
{
|
|
|
|
int instrumentList_count = 0;
|
|
QDomNode instrumentNode;
|
|
instrumentNode = instrumentListNode.firstChildElement( "instrument" );
|
|
while ( ! instrumentNode.isNull() )
|
|
{
|
|
instrumentList_count++;
|
|
QString sId = LocalFileMng::readXmlString( instrumentNode, "id", "" ); // instrument id
|
|
QString sDrumkit = LocalFileMng::readXmlString( instrumentNode, "drumkit", "" ); // drumkit
|
|
QString sName = LocalFileMng::readXmlString( instrumentNode, "name", "" ); // name
|
|
float fVolume = LocalFileMng::readXmlFloat( instrumentNode, "volume", 1.0 ); // volume
|
|
float fPan_L = LocalFileMng::readXmlFloat( instrumentNode, "pan_L", 0.5 ); // pan L
|
|
float fPan_R = LocalFileMng::readXmlFloat( instrumentNode, "pan_R", 0.5 ); // pan R
|
|
|
|
if ( sId.isEmpty() ) {
|
|
printf( "Empty ID for instrument. skipping \n" );
|
|
instrumentNode = (QDomNode) instrumentNode.nextSiblingElement( "instrument" );
|
|
continue;
|
|
}
|
|
QDomNode filenameNode = instrumentNode.firstChildElement( "filename" );
|
|
|
|
if ( ! filenameNode.isNull() )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
unsigned nLayer = 0;
|
|
QDomNode instrumentComponentNode = instrumentNode.firstChildElement("instrumentComponent");
|
|
if (instrumentComponentNode.isNull())
|
|
{
|
|
instrumentComponentNode = instrumentNode;
|
|
}
|
|
|
|
QDomNode layerNode = instrumentComponentNode.firstChildElement( "layer" );
|
|
while ( ! layerNode.isNull() )
|
|
{
|
|
if ( nLayer >= MAX_LAYERS )
|
|
{
|
|
printf("nLayer >= MAX_LAYERS\n");
|
|
break;
|
|
}
|
|
QString sFilename = LocalFileMng::readXmlString( layerNode, "filename", "" );
|
|
QString sMode = LocalFileMng::readXmlString( layerNode, "smode", "forward" );
|
|
|
|
if ( nLayer == 0 )
|
|
{
|
|
drum_track[sId] = static_cast<InstrumentTrack*>(
|
|
Track::create(Track::Type::Instrument, Engine::patternStore())
|
|
);
|
|
drum_track[sId]->volumeModel()->setValue( fVolume * 100 );
|
|
drum_track[sId]->panningModel()->setValue( ( fPan_R - fPan_L ) * 100 );
|
|
ins = drum_track[sId]->loadInstrument( "audiofileprocessor" );
|
|
ins->loadFile( sFilename );
|
|
}
|
|
nLayer++;
|
|
layerNode = ( QDomNode ) layerNode.nextSiblingElement( "layer" );
|
|
}
|
|
}
|
|
|
|
instrumentNode = (QDomNode) instrumentNode.nextSiblingElement( "instrument" );
|
|
}
|
|
if ( instrumentList_count == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
QDomNode patterns = songNode.firstChildElement( "patternList" );
|
|
int pattern_count = 0;
|
|
int existing_patterns = Engine::patternStore()->numOfPatterns();
|
|
QDomNode patternNode = patterns.firstChildElement( "pattern" );
|
|
int pn = 1;
|
|
while ( !patternNode.isNull() )
|
|
{
|
|
if ( pn > 0 )
|
|
{
|
|
pattern_count++;
|
|
s->addPatternTrack();
|
|
pn = 0;
|
|
}
|
|
QString sName; // name
|
|
sName = LocalFileMng::readXmlString( patternNode, "name", sName );
|
|
|
|
QString sCategory = ""; // category
|
|
sCategory = LocalFileMng::readXmlString( patternNode, "category", sCategory ,false ,false );
|
|
int nSize = -1;
|
|
nSize = LocalFileMng::readXmlInt( patternNode, "size", nSize, false, false );
|
|
pattern_length[sName] = nSize;
|
|
QDomNode pNoteListNode = patternNode.firstChildElement( "noteList" );
|
|
if ( ! pNoteListNode.isNull() ) {
|
|
QDomNode noteNode = pNoteListNode.firstChildElement( "note" );
|
|
while ( ! noteNode.isNull() ) {
|
|
int nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 );
|
|
float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f );
|
|
float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 );
|
|
float fPan_R = LocalFileMng::readXmlFloat( noteNode, "pan_R", 0.5 );
|
|
QString sKey = LocalFileMng::readXmlString( noteNode, "key", "C0", false, false );
|
|
QString nNoteOff = LocalFileMng::readXmlString( noteNode, "note_off", "false", false, false );
|
|
|
|
QString instrId = LocalFileMng::readXmlString( noteNode, "instrument", 0,false, false );
|
|
int i = pattern_count - 1 + existing_patterns;
|
|
pattern_id[sName] = pattern_count - 1;
|
|
auto p = dynamic_cast<MidiClip*>(drum_track[instrId]->getClip(i));
|
|
Note n;
|
|
n.setPos( nPosition );
|
|
if ( (nPosition + 48) <= nSize )
|
|
{
|
|
n.setLength( 48 );
|
|
}
|
|
else
|
|
{
|
|
n.setLength( nSize - nPosition );
|
|
}
|
|
n.setVolume( fVelocity * 100 );
|
|
n.setPanning( ( fPan_R - fPan_L ) * 100 );
|
|
n.setKey( NoteKey::stringToNoteKey( sKey ) );
|
|
p->addNote( n,false );
|
|
pn = pn + 1;
|
|
noteNode = ( QDomNode ) noteNode.nextSiblingElement( "note" );
|
|
}
|
|
}
|
|
patternNode = ( QDomNode ) patternNode.nextSiblingElement( "pattern" );
|
|
}
|
|
// MidiClip sequence
|
|
QDomNode patternSequenceNode = songNode.firstChildElement( "patternSequence" );
|
|
QDomNode groupNode = patternSequenceNode.firstChildElement( "group" );
|
|
int pos = 0;
|
|
while ( !groupNode.isNull() )
|
|
{
|
|
int best_length = 0;
|
|
QDomNode patternId = groupNode.firstChildElement( "patternID" );
|
|
while ( !patternId.isNull() )
|
|
{
|
|
QString patId = patternId.firstChild().nodeValue();
|
|
patternId = ( QDomNode ) patternId.nextSiblingElement( "patternID" );
|
|
|
|
int i = pattern_id[patId]+song_num_tracks;
|
|
Track* t = s->tracks().at(i);
|
|
t->createClip(pos);
|
|
|
|
if ( pattern_length[patId] > best_length )
|
|
{
|
|
best_length = pattern_length[patId];
|
|
}
|
|
}
|
|
pos = pos + best_length;
|
|
groupNode = groupNode.nextSiblingElement( "group" );
|
|
}
|
|
|
|
if ( pattern_count == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
bool HydrogenImport::tryImport( TrackContainer* tc )
|
|
{
|
|
if( openFile() == false )
|
|
{
|
|
return false;
|
|
}
|
|
return readSong();
|
|
}
|
|
|
|
|
|
|
|
extern "C"
|
|
{
|
|
|
|
// necessary for getting instance out of shared lib
|
|
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *, void * _data )
|
|
{
|
|
return new HydrogenImport( QString::fromUtf8(
|
|
static_cast<const char *>( _data ) ) );
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} // namespace lmms
|