mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-04 22:26:07 -05:00
Summary: * `NULL` -> `nullptr` * `gui` -> Function `getGUI()` * `pluginFactory` -> Function `getPluginFactory()` * `assert` (redefinition) -> using `NDEBUG` instead, which standard `assert` respects. * `powf` (C stdlib symbol clash) -> removed and all expansions replaced with calls to `std::pow`. * `exp10` (nonstandard function symbol clash) -> removed and all expansions replaced with calls to `std::pow`. * `PATH_DEV_DSP` -> File-scope QString of identical name and value. * `VST_SNC_SHM_KEY_FILE` -> constexpr char* with identical name and value. * `MM_ALLOC` and `MM_FREE` -> Functions with identical name and implementation. * `INVAL`, `OUTVAL`, etc. for automation nodes -> Functions with identical names and implementations. * BandLimitedWave.h: All integer constant macros replaced with constexpr ints of same name and value. * `FAST_RAND_MAX` -> constexpr int of same name and value. * `QSTR_TO_STDSTR` -> Function with identical name and equivalent implementation. * `CCONST` -> constexpr function template with identical name and implementation. * `F_OPEN_UTF8` -> Function with identical name and equivalent implementation. * `LADSPA_PATH_SEPARATOR` -> constexpr char with identical name and value. * `UI_CTRL_KEY` -> constexpr char* with identical name and value. * `ALIGN_SIZE` -> Renamed to `LMMS_ALIGN_SIZE` and converted from a macro to a constexpr size_t. * `JACK_MIDI_BUFFER_MAX` -> constexpr size_t with identical name and value. * versioninfo.h: `PLATFORM`, `MACHINE` and `COMPILER_VERSION` -> prefixed with `LMMS_BUILDCONF_` and converted from macros to constexpr char* literals. * Header guard _OSCILLOSCOPE -> renamed to OSCILLOSCOPE_H * Header guard _TIME_DISPLAY_WIDGET -> renamed to TIME_DISPLAY_WIDGET_H * C-style typecasts in DrumSynth.cpp have been replaced with `static_cast`. * constexpr numerical constants are initialized with assignment notation instead of curly brace intializers. * In portsmf, `Alg_seq::operator[]` will throw an exception instead of returning null if the operator index is out of range. Additionally, in many places, global constants that were declared as `const T foo = bar;` were changed from const to constexpr, leaving them const and making them potentially evaluable at compile time. Some macros that only appeared in single source files and were unused in those files have been removed entirely.
360 lines
9.1 KiB
C++
360 lines
9.1 KiB
C++
#include <QDomDocument>
|
|
#include <QDir>
|
|
#include <QApplication>
|
|
#include <QMessageBox>
|
|
#include <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 "BBTrack.h"
|
|
#include "BBTrackContainer.h"
|
|
#include "Instrument.h"
|
|
|
|
#include "plugin_export.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,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
} ;
|
|
|
|
}
|
|
|
|
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 = NoteKey::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 = 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;
|
|
}
|
|
|
|
// Hydrogen records MIDI notes from C-1 to B5, and exports them as a number ranging from -3 to 3
|
|
return m_key + ((nOctave + 3) * 12);
|
|
}
|
|
|
|
};
|
|
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 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] = ( 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() ) {
|
|
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 + 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 );
|
|
t->createTCO(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 ) ) );
|
|
}
|
|
|
|
|
|
}
|
|
|