Added HydrogenImport plugin

This is a new plugin that imports hydrogen drum machine songs.

Signed-off-by: Tobias Doerffel <tobias.doerffel@gmail.com>
This commit is contained in:
Frank Mather
2013-02-09 12:27:17 +01:00
committed by Tobias Doerffel
parent 26625b137f
commit 0acfff036f
6 changed files with 699 additions and 0 deletions

View File

@@ -2,6 +2,7 @@ ADD_SUBDIRECTORY(audio_file_processor)
ADD_SUBDIRECTORY(bass_booster)
ADD_SUBDIRECTORY(bit_invader)
ADD_SUBDIRECTORY(flp_import)
ADD_SUBDIRECTORY(HydrogenImport)
ADD_SUBDIRECTORY(kicker)
ADD_SUBDIRECTORY(ladspa_browser)
ADD_SUBDIRECTORY(ladspa_effect)

View File

@@ -0,0 +1,4 @@
INCLUDE(BuildPlugin)
BUILD_PLUGIN(hydrogenimport HydrogenImport.cpp HydrogenImport.h local_file_mgr.cpp LocalFileMng.h)

View File

@@ -0,0 +1,404 @@
#include <QtXml/QDomDocument>
#include <QtCore/QDir>
#include <QtGui/QApplication>
#include <QtGui/QMessageBox>
#include <QtGui/QProgressDialog>
#include <QTextStream>
#include <stdlib.h>
#include "LocalFileMng.h"
#include "HydrogenImport.h"
#include "song.h"
#include "engine.h"
#include "Instrument.h"
#include "InstrumentTrack.h"
#include "note.h"
#include "pattern.h"
#include "track.h"
#include "bb_track.h"
#include "bb_track_container.h"
#include "Instrument.h"
#define MAX_LAYERS 4
extern "C"
{
Plugin::Descriptor PLUGIN_EXPORT hydrogenimport_plugin_descriptor =
{
STRINGIFY( PLUGIN_NAME ),
"Hydrogen Import",
QT_TRANSLATE_NOOP( "pluginBrowser",
"Filter for importing Hydrogen files into LMMS" ),
"frank mather",
0x0100,
Plugin::ImportFilter,
NULL,
NULL,
NULL
} ;
}
QString filename;
class NoteKey
{
public:
enum Key {
C = 0,
Cs,
D,
Ef,
E,
F,
Fs,
G,
Af,
A,
Bf,
B,
};
static int stringToNoteKey( const QString& str )
{
int m_key;
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 = NoteKey::C;
}
else if ( sKey == "Cs" )
{
m_key = NoteKey::Cs;
}
else if ( sKey == "D" )
{
m_key = NoteKey::D;
}
else if ( sKey == "Ef" )
{
m_key = NoteKey::Ef;
}
else if ( sKey == "E" )
{
m_key = NoteKey::E;
}
else if ( sKey == "F" )
{
m_key = NoteKey::F;
}
else if ( sKey == "Fs" )
{
m_key = NoteKey::Fs;
}
else if ( sKey == "G" )
{
m_key = NoteKey::G;
}
else if ( sKey == "Af" )
{
m_key = NoteKey::Af;
}
else if ( sKey == "A" )
{
m_key = NoteKey::A;
}
else if ( sKey == "Bf" )
{
m_key = NoteKey::Bf;
}
else if ( sKey == "B" ) {
m_key = NoteKey::B;
}
return m_key + (nOctave*12)+57;
}
};
HydrogenImport::HydrogenImport( const QString & _file ) :
ImportFilter( _file, &hydrogenimport_plugin_descriptor )
{
filename = _file;
}
HydrogenImport::~HydrogenImport()
{
}
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 NULL;
}
QDomNode songNode = nodeList.at( 0 );
QString m_sSongVersion = LocalFileMng::readXmlString( songNode , "version", "Unknown version" );
float fBpm = LocalFileMng::readXmlFloat( songNode, "bpm", 120 );
float fVolume = LocalFileMng::readXmlFloat( songNode, "volume", 0.5 );
float fMetronomeVolume = LocalFileMng::readXmlFloat( songNode, "metronomeVolume", 0.5 );
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" ) );
bool bLoopEnabled = LocalFileMng::readXmlBool( songNode, "loopEnabled", false );
QString sMode = LocalFileMng::readXmlString( songNode, "mode", "pattern" );
float fHumanizeTimeValue = LocalFileMng::readXmlFloat( songNode, "humanize_time", 0.0 );
float fHumanizeVelocityValue = LocalFileMng::readXmlFloat( songNode, "humanize_velocity", 0.0 );
float fSwingFactor = LocalFileMng::readXmlFloat( songNode, "swing_factor", 0.0 );
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
bool bIsMuted = LocalFileMng::readXmlBool( instrumentNode, "isMuted", false ); // is muted
float fPan_L = LocalFileMng::readXmlFloat( instrumentNode, "pan_L", 0.5 ); // pan L
float fPan_R = LocalFileMng::readXmlFloat( instrumentNode, "pan_R", 0.5 ); // pan R
float fFX1Level = LocalFileMng::readXmlFloat( instrumentNode, "FX1Level", 0.0 ); // FX level
float fFX2Level = LocalFileMng::readXmlFloat( instrumentNode, "FX2Level", 0.0 ); // FX level
float fFX3Level = LocalFileMng::readXmlFloat( instrumentNode, "FX3Level", 0.0 ); // FX level
float fFX4Level = LocalFileMng::readXmlFloat( instrumentNode, "FX4Level", 0.0 ); // FX level
float fGain = LocalFileMng::readXmlFloat( instrumentNode, "gain", 1.0, false, false ); // instrument gain
int fAttack = LocalFileMng::readXmlInt( instrumentNode, "Attack", 0, false, false ); // Attack
int fDecay = LocalFileMng::readXmlInt( instrumentNode, "Decay", 0, false, false ); // Decay
float fSustain = LocalFileMng::readXmlFloat( instrumentNode, "Sustain", 1.0, false, false ); // Sustain
int fRelease = LocalFileMng::readXmlInt( instrumentNode, "Release", 1000, false, false ); // Release
float fRandomPitchFactor = LocalFileMng::readXmlFloat( instrumentNode, "randomPitchFactor", 0.0f, false, false );
bool bFilterActive = LocalFileMng::readXmlBool( instrumentNode, "filterActive", false );
float fFilterCutoff = LocalFileMng::readXmlFloat( instrumentNode, "filterCutoff", 1.0f, false );
float fFilterResonance = LocalFileMng::readXmlFloat( instrumentNode, "filterResonance", 0.0f, false );
QString sMuteGroup = LocalFileMng::readXmlString( instrumentNode, "muteGroup", "-1", false );
QString sMidiOutChannel = LocalFileMng::readXmlString( instrumentNode, "midiOutChannel", "-1", false, false );
QString sMidiOutNote = LocalFileMng::readXmlString( instrumentNode, "midiOutNote", "60", false, false );
int nMuteGroup = sMuteGroup.toInt();
bool isStopNote = LocalFileMng::readXmlBool( instrumentNode, "isStopNote", false );
int nMidiOutChannel = sMidiOutChannel.toInt();
int nMidiOutNote = sMidiOutNote.toInt();
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 layerNode = instrumentNode.firstChildElement( "layer" );
while ( ! layerNode.isNull() )
{
if ( nLayer >= MAX_LAYERS )
{
printf( "nLayer >= MAX_LAYERS" );
continue;
}
QString sFilename = LocalFileMng::readXmlString( layerNode, "filename", "" );
bool sIsModified = LocalFileMng::readXmlBool( layerNode, "ismodified", false );
QString sMode = LocalFileMng::readXmlString( layerNode, "smode", "forward" );
unsigned sStartframe = LocalFileMng::readXmlInt( layerNode, "startframe", 0 );
unsigned sLoopFrame = LocalFileMng::readXmlInt( layerNode, "loopframe", 0 );
int sLoops = LocalFileMng::readXmlInt( layerNode, "loops", 0 );
unsigned sEndframe = LocalFileMng::readXmlInt( layerNode, "endframe", 0 );
bool sUseRubber = LocalFileMng::readXmlInt( layerNode, "userubber", 0, false );
float sRubberDivider = LocalFileMng::readXmlFloat( layerNode, "rubberdivider", 0.0 );
int sRubberCsettings = LocalFileMng::readXmlInt( layerNode, "rubberCsettings", 1 );
int sRubberPitch = LocalFileMng::readXmlFloat( layerNode, "rubberPitch", 0.0 );
float fMin = LocalFileMng::readXmlFloat( layerNode, "min", 0.0 );
float fMax = LocalFileMng::readXmlFloat( layerNode, "max", 1.0 );
float fGain = LocalFileMng::readXmlFloat( layerNode, "gain", 1.0 );
float fPitch = LocalFileMng::readXmlFloat( layerNode, "pitch", 0.0, false, false );
if ( nLayer == 0 )
{
drum_track[sId] = ( InstrumentTrack * ) track::create( track::InstrumentTrack,engine::getBBTrackContainer() );
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 nbb = engine::getBBTrackContainer()->numOfBBs();
QDomNode patternNode = patterns.firstChildElement( "pattern" );
int pn = 1;
while ( !patternNode.isNull() )
{
if ( pn > 0 )
{
pattern_count++;
s->addBBTrack();
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() ) {
unsigned nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 );
float fLeadLag = LocalFileMng::readXmlFloat( noteNode, "leadlag", 0.0 , false , false );
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 );
int nLength = LocalFileMng::readXmlInt( noteNode, "length", -1, true );
float nPitch = LocalFileMng::readXmlFloat( noteNode, "pitch", 0.0, false, false );
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 + nbb;
pattern_id[sName] = pattern_count - 1;
pattern *p = dynamic_cast<pattern *>( drum_track[instrId]->getTCO( 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" );
}
// Pattern 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 = ( bbTrack * ) s->tracks().at( i );
trackContentObject *tco = t->createTCO( pos );
tco->movePosition( 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 * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data )
{
return new HydrogenImport( QString::fromUtf8(
static_cast<const char *>( _data ) ) );
}
}

View File

@@ -0,0 +1,27 @@
#ifndef _HYDROGEN_IMPORT_H
#define _HYDROGEN_IMPORT_H
#include <QtCore/QString>
#include <QtCore/QPair>
#include <QtCore/QVector>
#include "ImportFilter.h"
class HydrogenImport : public ImportFilter
{
public:
HydrogenImport( const QString & _file );
bool readSong();
virtual ~HydrogenImport();
virtual PluginView * instantiateView( QWidget * )
{
return( NULL );
}
private:
virtual bool tryImport( trackContainer * _tc );
};
#endif

View File

@@ -0,0 +1,29 @@
#ifndef LFILEMNG_H
#define LFILEMNG_H
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <QDomDocument>
class LocalFileMng
{
public:
LocalFileMng();
~LocalFileMng();
std::vector<QString> getallPatternList(){
return m_allPatternList;
}
static QString readXmlString( QDomNode , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false);
static float readXmlFloat( QDomNode , const QString& nodeName, float defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false);
static int readXmlInt( QDomNode , const QString& nodeName, int defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false);
static bool readXmlBool( QDomNode , const QString& nodeName, bool defaultValue, bool bShouldExists = true , bool tinyXmlCompatMode = false );
static void convertFromTinyXMLString( QByteArray* str );
static bool checkTinyXMLCompatMode( const QString& filename );
static QDomDocument openXmlDocument( const QString& filename );
std::vector<QString> m_allPatternList;
};
#endif //LFILEMNG_H

View File

@@ -0,0 +1,234 @@
#include <cstdlib>
#include <cassert>
#include <sys/stat.h>
#include <ctype.h>
#include <QDir>
#include <QApplication>
#include <QVector>
#include <QDomDocument>
#include <QLocale>
#include <QTextCodec>
#include <algorithm>
#include "LocalFileMng.h"
/* New QtXml based methods */
QString LocalFileMng::readXmlString( QDomNode node , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
{
QDomElement element = node.firstChildElement( nodeName );
if( !node.isNull() && !element.isNull() ){
if( !element.text().isEmpty() ){
return element.text();
} else {
if ( !bCanBeEmpty ) {
//_WARNINGLOG( "Using default value in " + nodeName );
}
return defaultValue;
}
} else {
if( bShouldExists ){
//_WARNINGLOG( "'" + nodeName + "' node not found" );
}
return defaultValue;
}
}
float LocalFileMng::readXmlFloat( QDomNode node , const QString& nodeName, float defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
{
QLocale c_locale = QLocale::c();
QDomElement element = node.firstChildElement( nodeName );
if( !node.isNull() && !element.isNull() ){
if( !element.text().isEmpty() ){
return c_locale.toFloat(element.text());
} else {
if ( !bCanBeEmpty ) {
//_WARNINGLOG( "Using default value in " + nodeName );
}
return defaultValue;
}
} else {
if( bShouldExists ){
//_WARNINGLOG( "'" + nodeName + "' node not found" );
}
return defaultValue;
}
}
int LocalFileMng::readXmlInt( QDomNode node , const QString& nodeName, int defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
{
QLocale c_locale = QLocale::c();
QDomElement element = node.firstChildElement( nodeName );
if( !node.isNull() && !element.isNull() ){
if( !element.text().isEmpty() ){
return c_locale.toInt( element.text() );
} else {
if ( !bCanBeEmpty ) {
//_WARNINGLOG( "Using default value in " + nodeName );
}
return defaultValue;
}
} else {
if( bShouldExists ){
//_WARNINGLOG( "'" + nodeName + "' node not found" );
}
return defaultValue;
}
}
bool LocalFileMng::readXmlBool( QDomNode node , const QString& nodeName, bool defaultValue, bool bShouldExists, bool tinyXmlCompatMode)
{
QDomElement element = node.firstChildElement( nodeName );
if( !node.isNull() && !element.isNull() ){
if( !element.text().isEmpty() ){
if( element.text() == "true"){
return true;
} else {
return false;
}
} else {
//_WARNINGLOG( "Using default value in " + nodeName );
return defaultValue;
}
} else {
if( bShouldExists ){
//_WARNINGLOG( "'" + nodeName + "' node not found" );
}
return defaultValue;
}
}
/* Convert (in-place) an XML escape sequence into a literal byte,
* rather than the character it actually refers to.
*/
void LocalFileMng::convertFromTinyXMLString( QByteArray* str )
{
/* When TinyXML encountered a non-ASCII character, it would
* simply write the character as "&#xx;" -- where "xx" is
* the hex character code. However, this doesn't respect
* any encodings (e.g. UTF-8, UTF-16). In XML, &#xx; literally
* means "the Unicode character # xx." However, in a UTF-8
* sequence, this could be an escape character that tells
* whether we have a 2, 3, or 4-byte UTF-8 sequence.
*
* For example, the UTF-8 sequence 0xD184 was being written
* by TinyXML as "&#xD1;&#x84;". However, this is the UTF-8
* sequence for the cyrillic small letter EF (which looks
* kind of like a thorn or a greek phi). This letter, in
* XML, should be saved as &#x00000444;, or even literally
* (no escaping). As a consequence, when &#xD1; is read
* by an XML parser, it will be interpreted as capital N
* with a tilde (~). Then &#x84; will be interpreted as
* an unknown or control character.
*
* So, when we know that TinyXML wrote the file, we can
* simply exchange these hex sequences to literal bytes.
*/
int pos = 0;
pos = str->indexOf("&#x");
while( pos != -1 ) {
if( isxdigit(str->at(pos+3))
&& isxdigit(str->at(pos+4))
&& (str->at(pos+5) == ';') ) {
char w1 = str->at(pos+3);
char w2 = str->at(pos+4);
w1 = tolower(w1) - 0x30; // '0' = 0x30
if( w1 > 9 ) w1 -= 0x27; // '9' = 0x39, 'a' = 0x61
w1 = (w1 & 0xF);
w2 = tolower(w2) - 0x30; // '0' = 0x30
if( w2 > 9 ) w2 -= 0x27; // '9' = 0x39, 'a' = 0x61
w2 = (w2 & 0xF);
char ch = (w1 << 4) | w2;
(*str)[pos] = ch;
++pos;
str->remove(pos, 5);
}
pos = str->indexOf("&#x");
}
}
bool LocalFileMng::checkTinyXMLCompatMode( const QString& filename )
{
/*
Check if filename was created with TinyXml or QtXml
TinyXML: return true
QtXml: return false
*/
QFile file( filename );
if ( !file.open(QIODevice::ReadOnly) )
return false;
QString line = file.readLine();
file.close();
if ( line.startsWith( "<?xml" )){
return false;
} else {
//_WARNINGLOG( QString("File '%1' is being read in "
// "TinyXML compatability mode")
// .arg(filename) );
return true;
}
}
QDomDocument LocalFileMng::openXmlDocument( const QString& filename )
{
bool TinyXMLCompat = LocalFileMng::checkTinyXMLCompatMode( filename );
QDomDocument doc;
QFile file( filename );
if ( !file.open(QIODevice::ReadOnly) )
return QDomDocument();
if( TinyXMLCompat ) {
QString enc = QTextCodec::codecForLocale()->name();
if( enc == QString("System") ) {
enc = "UTF-8";
}
QByteArray line;
QByteArray buf = QString("<?xml version='1.0' encoding='%1' ?>\n")
.arg( enc )
.toLocal8Bit();
//_INFOLOG( QString("Using '%1' encoding for TinyXML file").arg(enc) );
while( !file.atEnd() ) {
line = file.readLine();
LocalFileMng::convertFromTinyXMLString( &line );
buf += line;
}
if( ! doc.setContent( buf ) ) {
file.close();
return QDomDocument();
}
} else {
if( ! doc.setContent( &file ) ) {
file.close();
return QDomDocument();
}
}
file.close();
return doc;
}