1 /*
2  * Hydrogen
3  * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4  *
5  * http://www.hydrogen-music.org
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY, without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22 
23 #include "hydrogen/version.h"
24 #include <hydrogen/basics/adsr.h>
25 #include <hydrogen/hydrogen.h>
26 #include <hydrogen/h2_exception.h>
27 #include <hydrogen/basics/drumkit_component.h>
28 #include <hydrogen/basics/instrument.h>
29 #include <hydrogen/basics/instrument_list.h>
30 #include <hydrogen/basics/instrument_component.h>
31 #include <hydrogen/basics/instrument_layer.h>
32 #include <hydrogen/LocalFileMng.h>
33 #include <hydrogen/basics/note.h>
34 #include <hydrogen/basics/pattern.h>
35 #include <hydrogen/basics/pattern_list.h>
36 #include <hydrogen/basics/playlist.h>
37 #include <hydrogen/Preferences.h>
38 #include <hydrogen/timeline.h>
39 #include <hydrogen/basics/song.h>
40 #include <hydrogen/basics/drumkit.h>
41 #include <hydrogen/basics/sample.h>
42 #include <hydrogen/helpers/filesystem.h>
43 #include <hydrogen/automation_path_serializer.h>
44 #include <hydrogen/fx/Effects.h>
45 
46 #include <algorithm>
47 #include <cassert>
48 #include <cstdlib>
49 #include <ctype.h>
50 #include <sys/stat.h>
51 
52 #include <QDir>
53 //#include <QCoreApplication>
54 #include <QVector>
55 #include <QDomDocument>
56 #include <QLocale>
57 
58 namespace H2Core
59 {
60 
61 const char* LocalFileMng::__class_name = "LocalFileMng";
62 
LocalFileMng()63 LocalFileMng::LocalFileMng()
64 	: Object( __class_name )
65 {
66 	//	infoLog("INIT");
67 }
68 
69 
70 
~LocalFileMng()71 LocalFileMng::~LocalFileMng()
72 {
73 	//	infoLog("DESTROY");
74 }
75 
getDrumkitNameForPattern(const QString & patternDir)76 QString LocalFileMng::getDrumkitNameForPattern( const QString& patternDir )
77 {
78 	QDomDocument doc = openXmlDocument( patternDir );
79 
80 	QDomNode rootNode = doc.firstChildElement( "drumkit_pattern" );	// root element
81 	if (  rootNode.isNull() ) {
82 		ERRORLOG( "Error reading Pattern: Pattern_drumkit_infonode not found " + patternDir);
83 		return nullptr;
84 	}
85 
86 	QString dk_name = LocalFileMng::readXmlString( rootNode,"drumkit_name", "" );
87 	if ( dk_name.isEmpty() ) {
88 		dk_name = LocalFileMng::readXmlString( rootNode,"pattern_for_drumkit", "" );
89 	}
90 	return dk_name;
91 }
92 
93 /* New QtXml based methods */
94 
processNode(QDomNode node,const QString & nodeName,bool bCanBeEmpty,bool bShouldExists)95 QString LocalFileMng::processNode( QDomNode node, const QString& nodeName, bool bCanBeEmpty, bool bShouldExists )
96 {
97 	QDomElement element = node.firstChildElement( nodeName );
98 
99 	if ( !node.isNull() && !element.isNull() ) {
100 		QString text = element.text();
101 		if( !text.isEmpty() ) {
102 			return text;
103 		} else {
104 			if ( !bCanBeEmpty ) {
105 				_WARNINGLOG( "node '" + nodeName + "' is empty" );
106 			}
107 		}
108 	} else {
109 		if (  bShouldExists ) {
110 			_WARNINGLOG( "node '" + nodeName + "' is not found" );
111 		}
112 	}
113 	return nullptr;
114 }
115 
readXmlString(QDomNode node,const QString & nodeName,const QString & defaultValue,bool bCanBeEmpty,bool bShouldExists,bool tinyXmlCompatMode)116 QString LocalFileMng::readXmlString( QDomNode node , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
117 {
118 	QString text = processNode( node, nodeName, bCanBeEmpty, bShouldExists );
119 	if ( text == nullptr ) {
120 		_WARNINGLOG( QString( "\tusing default value : '%1' for node '%2'" ).arg( defaultValue ).arg( nodeName ) );
121 		return defaultValue;
122 	} else {
123 		return text;
124 	}
125 }
126 
readXmlFloat(QDomNode node,const QString & nodeName,float defaultValue,bool bCanBeEmpty,bool bShouldExists,bool tinyXmlCompatMode)127 float LocalFileMng::readXmlFloat( QDomNode node , const QString& nodeName, float defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
128 {
129 	QString text = processNode( node, nodeName, bCanBeEmpty, bShouldExists );
130 	if ( text == nullptr ) {
131 		_WARNINGLOG( QString( "\tusing default value : '%1' for node '%2'" ).arg( defaultValue ).arg( nodeName ));
132 		return defaultValue;
133 	} else {
134 		return QLocale::c().toFloat( text );
135 	}
136 }
137 
readXmlInt(QDomNode node,const QString & nodeName,int defaultValue,bool bCanBeEmpty,bool bShouldExists,bool tinyXmlCompatMode)138 int LocalFileMng::readXmlInt( QDomNode node , const QString& nodeName, int defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode)
139 {
140 	QString text = processNode( node, nodeName, bCanBeEmpty, bShouldExists );
141 	if ( text == nullptr ) {
142 		_WARNINGLOG( QString( "\tusing default value : '%1' for node '%2'" ).arg( defaultValue ).arg( nodeName ));
143 		return defaultValue;
144 	} else {
145 		return QLocale::c().toInt( text );
146 	}
147 }
148 
readXmlBool(QDomNode node,const QString & nodeName,bool defaultValue,bool bShouldExists,bool tinyXmlCompatMode)149 bool LocalFileMng::readXmlBool( QDomNode node , const QString& nodeName, bool defaultValue, bool bShouldExists, bool tinyXmlCompatMode)
150 {
151 	QString text = processNode( node, nodeName, bShouldExists, bShouldExists );
152 	if ( text == nullptr ) {
153 		_WARNINGLOG( QString( "\tusing default value : '%1' for node '%2'" ).arg( defaultValue ? "true" : "false" ).arg( nodeName ) );
154 		return defaultValue;
155 	} else {
156 		if ( text == "true") {
157 			return true;
158 		} else {
159 			return false;
160 		}
161 	}
162 }
163 
164 
writeXmlString(QDomNode parent,const QString & name,const QString & text)165 void LocalFileMng::writeXmlString( QDomNode parent, const QString& name, const QString& text )
166 {
167 	QDomDocument doc;
168 	QDomElement elem = doc.createElement( name );
169 	QDomText t = doc.createTextNode( text );
170 	elem.appendChild( t );
171 	parent.appendChild( elem );
172 }
173 
174 
175 
writeXmlBool(QDomNode parent,const QString & name,bool value)176 void LocalFileMng::writeXmlBool( QDomNode parent, const QString& name, bool value )
177 {
178 	if ( value ) {
179 		writeXmlString( parent, name, QString( "true" ) );
180 	} else {
181 		writeXmlString( parent, name, QString( "false" ) );
182 	}
183 }
184 
185 /* Convert (in-place) an XML escape sequence into a literal byte,
186  * rather than the character it actually refers to.
187  */
convertFromTinyXMLString(QByteArray * str)188 void LocalFileMng::convertFromTinyXMLString( QByteArray* str )
189 {
190 	/* When TinyXML encountered a non-ASCII character, it would
191 	 * simply write the character as "&#xx;" -- where "xx" is
192 	 * the hex character code.  However, this doesn't respect
193 	 * any encodings (e.g. UTF-8, UTF-16).  In XML, &#xx; literally
194 	 * means "the Unicode character # xx."  However, in a UTF-8
195 	 * sequence, this could be an escape character that tells
196 	 * whether we have a 2, 3, or 4-byte UTF-8 sequence.
197 	 *
198 	 * For example, the UTF-8 sequence 0xD184 was being written
199 	 * by TinyXML as "&#xD1;&#x84;".  However, this is the UTF-8
200 	 * sequence for the cyrillic small letter EF (which looks
201 	 * kind of like a thorn or a greek phi).  This letter, in
202 	 * XML, should be saved as &#x00000444;, or even literally
203 	 * (no escaping).  As a consequence, when &#xD1; is read
204 	 * by an XML parser, it will be interpreted as capital N
205 	 * with a tilde (~).  Then &#x84; will be interpreted as
206 	 * an unknown or control character.
207 	 *
208 	 * So, when we know that TinyXML wrote the file, we can
209 	 * simply exchange these hex sequences to literal bytes.
210 	 */
211 	int pos = 0;
212 
213 	pos = str->indexOf("&#x");
214 	while( pos != -1 ) {
215 		if( isxdigit(str->at(pos+3))
216 				&& isxdigit(str->at(pos+4))
217 				&& (str->at(pos+5) == ';') ) {
218 			char w1 = str->at(pos+3);
219 			char w2 = str->at(pos+4);
220 
221 			w1 = tolower(w1) - 0x30;  // '0' = 0x30
222 			if( w1 > 9 ) w1 -= 0x27;  // '9' = 0x39, 'a' = 0x61
223 			w1 = (w1 & 0xF);
224 
225 			w2 = tolower(w2) - 0x30;  // '0' = 0x30
226 			if( w2 > 9 ) w2 -= 0x27;  // '9' = 0x39, 'a' = 0x61
227 			w2 = (w2 & 0xF);
228 
229 			char ch = (w1 << 4) | w2;
230 			(*str)[pos] = ch;
231 			++pos;
232 			str->remove(pos, 5);
233 		}
234 		pos = str->indexOf("&#x");
235 	}
236 }
237 
checkTinyXMLCompatMode(const QString & filename)238 bool LocalFileMng::checkTinyXMLCompatMode( const QString& filename )
239 {
240 	/*
241 		Check if filename was created with TinyXml or QtXml
242 		TinyXML: return true
243 		QtXml: return false
244 	*/
245 
246 	QFile file( filename );
247 
248 	if ( !file.open(QIODevice::ReadOnly) ) {
249 		return false;
250 	}
251 
252 	QString line = file.readLine();
253 	file.close();
254 	if ( line.startsWith( "<?xml" )){
255 		return false;
256 	} else  {
257 		_WARNINGLOG( QString("File '%1' is being read in "
258 							 "TinyXML compatibility mode")
259 					 .arg(filename) );
260 		return true;
261 	}
262 
263 
264 
265 }
266 
openXmlDocument(const QString & filename)267 QDomDocument LocalFileMng::openXmlDocument( const QString& filename )
268 {
269 	bool TinyXMLCompat = LocalFileMng::checkTinyXMLCompatMode( filename );
270 
271 	QDomDocument doc;
272 	QFile file( filename );
273 
274 	if ( !file.open(QIODevice::ReadOnly) ) {
275 		return QDomDocument();
276 	}
277 
278 	if( TinyXMLCompat ) {
279 		QString enc = QTextCodec::codecForLocale()->name();
280 		if( enc == QString("System") ) {
281 			enc = "UTF-8";
282 		}
283 		QByteArray line;
284 		QByteArray buf = QString("<?xml version='1.0' encoding='%1' ?>\n")
285 				.arg( enc )
286 				.toLocal8Bit();
287 
288 		while( !file.atEnd() ) {
289 			line = file.readLine();
290 			LocalFileMng::convertFromTinyXMLString( &line );
291 			buf += line;
292 		}
293 
294 		if( ! doc.setContent( buf ) ) {
295 			file.close();
296 			return QDomDocument();
297 		}
298 
299 	} else {
300 		if( ! doc.setContent( &file ) ) {
301 			file.close();
302 			return QDomDocument();
303 		}
304 	}
305 	file.close();
306 
307 	return doc;
308 }
309 
310 //-----------------------------------------------------------------------------
311 //	Implementation of SongWriter class
312 //-----------------------------------------------------------------------------
313 
314 const char* SongWriter::__class_name = "SongWriter";
315 
SongWriter()316 SongWriter::SongWriter()
317 	: Object( __class_name )
318 {
319 	//	infoLog("init");
320 }
321 
322 
323 
~SongWriter()324 SongWriter::~SongWriter()
325 {
326 	//	infoLog("destroy");
327 }
328 
329 
330 // Returns 0 on success, passes the TinyXml error code otherwise.
writeSong(Song * pSong,const QString & filename)331 int SongWriter::writeSong( Song * pSong, const QString& filename )
332 {
333 	INFOLOG( "Saving song " + filename );
334 	int rv = 0; // return value
335 
336 	// FIXME: has the file write-permssion?
337 	// FIXME: verificare che il file non sia gia' esistente
338 	// FIXME: effettuare copia di backup per il file gia' esistente
339 
340 
341 	QDomDocument doc;
342 	QDomProcessingInstruction header = doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"");
343 	doc.appendChild( header );
344 
345 	QDomNode songNode = doc.createElement( "song" );
346 
347 	LocalFileMng::writeXmlString( songNode, "version", QString( get_version().c_str() ) );
348 	LocalFileMng::writeXmlString( songNode, "bpm", QString("%1").arg( pSong->__bpm ) );
349 	LocalFileMng::writeXmlString( songNode, "volume", QString("%1").arg( pSong->get_volume() ) );
350 	LocalFileMng::writeXmlString( songNode, "metronomeVolume", QString("%1").arg( pSong->get_metronome_volume() ) );
351 	LocalFileMng::writeXmlString( songNode, "name", pSong->__name );
352 	LocalFileMng::writeXmlString( songNode, "author", pSong->__author );
353 	LocalFileMng::writeXmlString( songNode, "notes", pSong->get_notes() );
354 	LocalFileMng::writeXmlString( songNode, "license", pSong->get_license() );
355 	LocalFileMng::writeXmlBool( songNode, "loopEnabled", pSong->is_loop_enabled() );
356 	LocalFileMng::writeXmlBool( songNode, "patternModeMode", Preferences::get_instance()->patternModePlaysSelected());
357 
358 	LocalFileMng::writeXmlString( songNode, "playbackTrackFilename", QString("%1").arg( pSong->get_playback_track_filename() ) );
359 	LocalFileMng::writeXmlBool( songNode, "playbackTrackEnabled", pSong->get_playback_track_enabled() );
360 	LocalFileMng::writeXmlString( songNode, "playbackTrackVolume", QString("%1").arg( pSong->get_playback_track_volume() ) );
361 
362 
363 	if ( pSong->get_mode() == Song::SONG_MODE ) {
364 		LocalFileMng::writeXmlString( songNode, "mode", QString( "song" ) );
365 	} else {
366 		LocalFileMng::writeXmlString( songNode, "mode", QString( "pattern" ) );
367 	}
368 
369 	LocalFileMng::writeXmlString( songNode, "humanize_time", QString("%1").arg( pSong->get_humanize_time_value() ) );
370 	LocalFileMng::writeXmlString( songNode, "humanize_velocity", QString("%1").arg( pSong->get_humanize_velocity_value() ) );
371 	LocalFileMng::writeXmlString( songNode, "swing_factor", QString("%1").arg( pSong->get_swing_factor() ) );
372 
373 	// component List
374 	QDomNode componentListNode = doc.createElement( "componentList" );
375 	for (std::vector<DrumkitComponent*>::iterator it = pSong->get_components()->begin() ; it != pSong->get_components()->end(); ++it) {
376 		DrumkitComponent* pCompo = *it;
377 
378 		QDomNode componentNode = doc.createElement( "drumkitComponent" );
379 
380 		LocalFileMng::writeXmlString( componentNode, "id", QString("%1").arg( pCompo->get_id() ) );
381 		LocalFileMng::writeXmlString( componentNode, "name", pCompo->get_name() );
382 		LocalFileMng::writeXmlString( componentNode, "volume", QString("%1").arg( pCompo->get_volume() ) );
383 
384 		componentListNode.appendChild( componentNode );
385 	}
386 	songNode.appendChild( componentListNode );
387 
388 	// instrument list
389 	QDomNode instrumentListNode = doc.createElement( "instrumentList" );
390 	unsigned nInstrument = pSong->get_instrument_list()->size();
391 
392 	// INSTRUMENT NODE
393 	for ( unsigned i = 0; i < nInstrument; i++ ) {
394 		Instrument * pInstr = pSong->get_instrument_list()->get( i );
395 		assert( pInstr );
396 
397 		QDomNode instrumentNode = doc.createElement( "instrument" );
398 
399 		LocalFileMng::writeXmlString( instrumentNode, "id", QString("%1").arg( pInstr->get_id() ) );
400 		LocalFileMng::writeXmlString( instrumentNode, "name", pInstr->get_name() );
401 		LocalFileMng::writeXmlString( instrumentNode, "drumkit", pInstr->get_drumkit_name() );
402 		LocalFileMng::writeXmlString( instrumentNode, "volume", QString("%1").arg( pInstr->get_volume() ) );
403 		LocalFileMng::writeXmlBool( instrumentNode, "isMuted", pInstr->is_muted() );
404 		LocalFileMng::writeXmlString( instrumentNode, "pan_L", QString("%1").arg( pInstr->get_pan_l() ) );
405 		LocalFileMng::writeXmlString( instrumentNode, "pan_R", QString("%1").arg( pInstr->get_pan_r() ) );
406 		LocalFileMng::writeXmlString( instrumentNode, "gain", QString("%1").arg( pInstr->get_gain() ) );
407 		LocalFileMng::writeXmlBool( instrumentNode, "applyVelocity", pInstr->get_apply_velocity() );
408 
409 		LocalFileMng::writeXmlBool( instrumentNode, "filterActive", pInstr->is_filter_active() );
410 		LocalFileMng::writeXmlString( instrumentNode, "filterCutoff", QString("%1").arg( pInstr->get_filter_cutoff() ) );
411 		LocalFileMng::writeXmlString( instrumentNode, "filterResonance", QString("%1").arg( pInstr->get_filter_resonance() ) );
412 
413 		LocalFileMng::writeXmlString( instrumentNode, "FX1Level", QString("%1").arg( pInstr->get_fx_level( 0 ) ) );
414 		LocalFileMng::writeXmlString( instrumentNode, "FX2Level", QString("%1").arg( pInstr->get_fx_level( 1 ) ) );
415 		LocalFileMng::writeXmlString( instrumentNode, "FX3Level", QString("%1").arg( pInstr->get_fx_level( 2 ) ) );
416 		LocalFileMng::writeXmlString( instrumentNode, "FX4Level", QString("%1").arg( pInstr->get_fx_level( 3 ) ) );
417 
418 		assert( pInstr->get_adsr() );
419 		LocalFileMng::writeXmlString( instrumentNode, "Attack", QString("%1").arg( pInstr->get_adsr()->get_attack() ) );
420 		LocalFileMng::writeXmlString( instrumentNode, "Decay", QString("%1").arg( pInstr->get_adsr()->get_decay() ) );
421 		LocalFileMng::writeXmlString( instrumentNode, "Sustain", QString("%1").arg( pInstr->get_adsr()->get_sustain() ) );
422 		LocalFileMng::writeXmlString( instrumentNode, "Release", QString("%1").arg( pInstr->get_adsr()->get_release() ) );
423 
424 		LocalFileMng::writeXmlString( instrumentNode, "randomPitchFactor", QString("%1").arg( pInstr->get_random_pitch_factor() ) );
425 
426 		LocalFileMng::writeXmlString( instrumentNode, "muteGroup", QString("%1").arg( pInstr->get_mute_group() ) );
427 		LocalFileMng::writeXmlBool( instrumentNode, "isStopNote", pInstr->is_stop_notes() );
428 		switch ( pInstr->sample_selection_alg() ) {
429 			case Instrument::VELOCITY:
430 				LocalFileMng::writeXmlString( instrumentNode, "sampleSelectionAlgo", "VELOCITY" );
431 				break;
432 			case Instrument::RANDOM:
433 				LocalFileMng::writeXmlString( instrumentNode, "sampleSelectionAlgo", "RANDOM" );
434 				break;
435 			case Instrument::ROUND_ROBIN:
436 				LocalFileMng::writeXmlString( instrumentNode, "sampleSelectionAlgo", "ROUND_ROBIN" );
437 				break;
438 		}
439 
440 		LocalFileMng::writeXmlString( instrumentNode, "midiOutChannel", QString("%1").arg( pInstr->get_midi_out_channel() ) );
441 		LocalFileMng::writeXmlString( instrumentNode, "midiOutNote", QString("%1").arg( pInstr->get_midi_out_note() ) );
442 		LocalFileMng::writeXmlString( instrumentNode, "isHihat", QString("%1").arg( pInstr->get_hihat_grp() ) );
443 		LocalFileMng::writeXmlString( instrumentNode, "lower_cc", QString("%1").arg( pInstr->get_lower_cc() ) );
444 		LocalFileMng::writeXmlString( instrumentNode, "higher_cc", QString("%1").arg( pInstr->get_higher_cc() ) );
445 
446 		for (std::vector<InstrumentComponent*>::iterator it = pInstr->get_components()->begin() ; it != pInstr->get_components()->end(); ++it) {
447 			InstrumentComponent* pComponent = *it;
448 
449 			QDomNode componentNode = doc.createElement( "instrumentComponent" );
450 
451 			LocalFileMng::writeXmlString( componentNode, "component_id", QString("%1").arg( pComponent->get_drumkit_componentID() ) );
452 			LocalFileMng::writeXmlString( componentNode, "gain", QString("%1").arg( pComponent->get_gain() ) );
453 
454 			for ( unsigned nLayer = 0; nLayer < InstrumentComponent::getMaxLayers(); nLayer++ ) {
455 				InstrumentLayer *pLayer = pComponent->get_layer( nLayer );
456 				if ( pLayer == nullptr ) {
457 					continue;
458 				}
459 
460 				Sample *pSample = pLayer->get_sample();
461 				if ( pSample == nullptr ) {
462 					continue;
463 				}
464 
465 				bool sIsModified = pSample->get_is_modified();
466 				Sample::Loops lo = pSample->get_loops();
467 				Sample::Rubberband ro = pSample->get_rubberband();
468 				QString sMode = pSample->get_loop_mode_string();
469 
470 
471 				QDomNode layerNode = doc.createElement( "layer" );
472 				LocalFileMng::writeXmlString( layerNode, "filename", Filesystem::prepare_sample_path( pSample->get_filepath() ) );
473 				LocalFileMng::writeXmlBool( layerNode, "ismodified", sIsModified);
474 				LocalFileMng::writeXmlString( layerNode, "smode", pSample->get_loop_mode_string() );
475 				LocalFileMng::writeXmlString( layerNode, "startframe", QString("%1").arg( lo.start_frame ) );
476 				LocalFileMng::writeXmlString( layerNode, "loopframe", QString("%1").arg( lo.loop_frame ) );
477 				LocalFileMng::writeXmlString( layerNode, "loops", QString("%1").arg( lo.count ) );
478 				LocalFileMng::writeXmlString( layerNode, "endframe", QString("%1").arg( lo.end_frame ) );
479 				LocalFileMng::writeXmlString( layerNode, "userubber", QString("%1").arg( ro.use ) );
480 				LocalFileMng::writeXmlString( layerNode, "rubberdivider", QString("%1").arg( ro.divider ) );
481 				LocalFileMng::writeXmlString( layerNode, "rubberCsettings", QString("%1").arg( ro.c_settings ) );
482 				LocalFileMng::writeXmlString( layerNode, "rubberPitch", QString("%1").arg( ro.pitch ) );
483 				LocalFileMng::writeXmlString( layerNode, "min", QString("%1").arg( pLayer->get_start_velocity() ) );
484 				LocalFileMng::writeXmlString( layerNode, "max", QString("%1").arg( pLayer->get_end_velocity() ) );
485 				LocalFileMng::writeXmlString( layerNode, "gain", QString("%1").arg( pLayer->get_gain() ) );
486 				LocalFileMng::writeXmlString( layerNode, "pitch", QString("%1").arg( pLayer->get_pitch() ) );
487 
488 
489 				Sample::VelocityEnvelope* velocity = pSample->get_velocity_envelope();
490 				for (int y = 0; y < velocity->size(); y++){
491 					QDomNode volumeNode = doc.createElement( "volume" );
492 					LocalFileMng::writeXmlString( volumeNode, "volume-position", QString("%1").arg( velocity->at(y)->frame ) );
493 					LocalFileMng::writeXmlString( volumeNode, "volume-value", QString("%1").arg( velocity->at(y)->value ) );
494 					layerNode.appendChild( volumeNode );
495 				}
496 
497 				Sample::PanEnvelope* pan = pSample->get_pan_envelope();
498 				for (int y = 0; y < pan->size(); y++){
499 					QDomNode panNode = doc.createElement( "pan" );
500 					LocalFileMng::writeXmlString( panNode, "pan-position", QString("%1").arg( pan->at(y)->frame ) );
501 					LocalFileMng::writeXmlString( panNode, "pan-value", QString("%1").arg( pan->at(y)->value ) );
502 					layerNode.appendChild( panNode );
503 				}
504 
505 				componentNode.appendChild( layerNode );
506 			}
507 			instrumentNode.appendChild( componentNode );
508 		}
509 
510 		instrumentListNode.appendChild( instrumentNode );
511 	}
512 	songNode.appendChild( instrumentListNode );
513 
514 
515 	// pattern list
516 	QDomNode patternListNode = doc.createElement( "patternList" );
517 
518 	unsigned nPatterns = pSong->get_pattern_list()->size();
519 	for ( unsigned i = 0; i < nPatterns; i++ ) {
520 		const Pattern *pPattern = pSong->get_pattern_list()->get( i );
521 
522 		// pattern
523 		QDomNode patternNode = doc.createElement( "pattern" );
524 		LocalFileMng::writeXmlString( patternNode, "name", pPattern->get_name() );
525 		LocalFileMng::writeXmlString( patternNode, "category", pPattern->get_category() );
526 		LocalFileMng::writeXmlString( patternNode, "size", QString("%1").arg( pPattern->get_length() ) );
527 		LocalFileMng::writeXmlString( patternNode, "info", pPattern->get_info() );
528 
529 		QDomNode noteListNode = doc.createElement( "noteList" );
530 		const Pattern::notes_t* notes = pPattern->get_notes();
531 		FOREACH_NOTE_CST_IT_BEGIN_END(notes,it) {
532 			Note *pNote = it->second;
533 			assert( pNote );
534 
535 			QDomNode noteNode = doc.createElement( "note" );
536 			LocalFileMng::writeXmlString( noteNode, "position", QString("%1").arg( pNote->get_position() ) );
537 			LocalFileMng::writeXmlString( noteNode, "leadlag", QString("%1").arg( pNote->get_lead_lag() ) );
538 			LocalFileMng::writeXmlString( noteNode, "velocity", QString("%1").arg( pNote->get_velocity() ) );
539 			LocalFileMng::writeXmlString( noteNode, "pan_L", QString("%1").arg( pNote->get_pan_l() ) );
540 			LocalFileMng::writeXmlString( noteNode, "pan_R", QString("%1").arg( pNote->get_pan_r() ) );
541 			LocalFileMng::writeXmlString( noteNode, "pitch", QString("%1").arg( pNote->get_pitch() ) );
542 			LocalFileMng::writeXmlString( noteNode, "probability", QString("%1").arg( pNote->get_probability() ) );
543 
544 			LocalFileMng::writeXmlString( noteNode, "key", pNote->key_to_string() );
545 
546 			LocalFileMng::writeXmlString( noteNode, "length", QString("%1").arg( pNote->get_length() ) );
547 			LocalFileMng::writeXmlString( noteNode, "instrument", QString("%1").arg( pNote->get_instrument()->get_id() ) );
548 
549 			QString noteoff = "false";
550 			if ( pNote->get_note_off() ) noteoff = "true";
551 			LocalFileMng::writeXmlString( noteNode, "note_off", noteoff );
552 			noteListNode.appendChild( noteNode );
553 
554 		}
555 		patternNode.appendChild( noteListNode );
556 
557 		patternListNode.appendChild( patternNode );
558 	}
559 	songNode.appendChild( patternListNode );
560 
561 	QDomNode virtualPatternListNode = doc.createElement( "virtualPatternList" );
562 	for ( unsigned i = 0; i < nPatterns; i++ ) {
563 		const Pattern *pat = pSong->get_pattern_list()->get( i );
564 
565 		// pattern
566 		if (pat->get_virtual_patterns()->empty() == false) {
567 			QDomNode patternNode = doc.createElement( "pattern" );
568 			LocalFileMng::writeXmlString( patternNode, "name", pat->get_name() );
569 
570 			for (Pattern::virtual_patterns_it_t  virtIter = pat->get_virtual_patterns()->begin(); virtIter != pat->get_virtual_patterns()->end(); ++virtIter) {
571 				LocalFileMng::writeXmlString( patternNode, "virtual", (*virtIter)->get_name() );
572 			}//for
573 
574 			virtualPatternListNode.appendChild( patternNode );
575 		}//if
576 	}//for
577 	songNode.appendChild(virtualPatternListNode);
578 
579 	// pattern sequence
580 	QDomNode patternSequenceNode = doc.createElement( "patternSequence" );
581 
582 	unsigned nPatternGroups = pSong->get_pattern_group_vector()->size();
583 	for ( unsigned i = 0; i < nPatternGroups; i++ ) {
584 		QDomNode groupNode = doc.createElement( "group" );
585 
586 		PatternList *pList = ( *pSong->get_pattern_group_vector() )[i];
587 		for ( unsigned j = 0; j < pList->size(); j++ ) {
588 			const Pattern *pPattern = pList->get( j );
589 			LocalFileMng::writeXmlString( groupNode, "patternID", pPattern->get_name() );
590 		}
591 		patternSequenceNode.appendChild( groupNode );
592 	}
593 
594 	songNode.appendChild( patternSequenceNode );
595 
596 
597 	// LADSPA FX
598 	QDomNode ladspaFxNode = doc.createElement( "ladspa" );
599 
600 	for ( unsigned nFX = 0; nFX < MAX_FX; nFX++ ) {
601 		QDomNode fxNode = doc.createElement( "fx" );
602 
603 #ifdef H2CORE_HAVE_LADSPA
604 		LadspaFX *pFX = Effects::get_instance()->getLadspaFX( nFX );
605 		if ( pFX ) {
606 			LocalFileMng::writeXmlString( fxNode, "name", pFX->getPluginLabel() );
607 			LocalFileMng::writeXmlString( fxNode, "filename", pFX->getLibraryPath() );
608 			LocalFileMng::writeXmlBool( fxNode, "enabled", pFX->isEnabled() );
609 			LocalFileMng::writeXmlString( fxNode, "volume", QString("%1").arg( pFX->getVolume() ) );
610 			for ( unsigned nControl = 0; nControl < pFX->inputControlPorts.size(); nControl++ ) {
611 				LadspaControlPort *pControlPort = pFX->inputControlPorts[ nControl ];
612 				QDomNode controlPortNode = doc.createElement( "inputControlPort" );
613 				LocalFileMng::writeXmlString( controlPortNode, "name", pControlPort->sName );
614 				LocalFileMng::writeXmlString( controlPortNode, "value", QString("%1").arg( pControlPort->fControlValue ) );
615 				fxNode.appendChild( controlPortNode );
616 			}
617 			for ( unsigned nControl = 0; nControl < pFX->outputControlPorts.size(); nControl++ ) {
618 				LadspaControlPort *pControlPort = pFX->outputControlPorts[ nControl ];
619 				QDomNode controlPortNode = doc.createElement( "outputControlPort" );
620 				LocalFileMng::writeXmlString( controlPortNode, "name", pControlPort->sName );
621 				LocalFileMng::writeXmlString( controlPortNode, "value", QString("%1").arg( pControlPort->fControlValue ) );
622 				fxNode.appendChild( controlPortNode );
623 			}
624 		}
625 #else
626 		if ( false ) {
627 		}
628 #endif
629 		else {
630 			LocalFileMng::writeXmlString( fxNode, "name", QString( "no plugin" ) );
631 			LocalFileMng::writeXmlString( fxNode, "filename", QString( "-" ) );
632 			LocalFileMng::writeXmlBool( fxNode, "enabled", false );
633 			LocalFileMng::writeXmlString( fxNode, "volume", "0.0" );
634 		}
635 		ladspaFxNode.appendChild( fxNode );
636 	}
637 
638 	songNode.appendChild( ladspaFxNode );
639 	doc.appendChild( songNode );
640 
641 
642 	//bpm time line
643 	Timeline * pTimeline = Hydrogen::get_instance()->getTimeline();
644 
645 	QDomNode bpmTimeLine = doc.createElement( "BPMTimeLine" );
646 
647 	if(pTimeline->m_timelinevector.size() >= 1 ){
648 		for ( int t = 0; t < static_cast<int>(pTimeline->m_timelinevector.size()); t++){
649 			QDomNode newBPMNode = doc.createElement( "newBPM" );
650 			LocalFileMng::writeXmlString( newBPMNode, "BAR",QString("%1").arg( pTimeline->m_timelinevector[t].m_htimelinebeat ));
651 			LocalFileMng::writeXmlString( newBPMNode, "BPM", QString("%1").arg( pTimeline->m_timelinevector[t].m_htimelinebpm  ) );
652 			bpmTimeLine.appendChild( newBPMNode );
653 		}
654 	}
655 	songNode.appendChild( bpmTimeLine );
656 
657 	//time line tag
658 	QDomNode timeLineTag = doc.createElement( "timeLineTag" );
659 	if(pTimeline->m_timelinetagvector.size() >= 1 ){
660 		for ( int t = 0; t < static_cast<int>(pTimeline->m_timelinetagvector.size()); t++){
661 			QDomNode newTAGNode = doc.createElement( "newTAG" );
662 			LocalFileMng::writeXmlString( newTAGNode, "BAR",QString("%1").arg( pTimeline->m_timelinetagvector[t].m_htimelinetagbeat ));
663 			LocalFileMng::writeXmlString( newTAGNode, "TAG", QString("%1").arg( pTimeline->m_timelinetagvector[t].m_htimelinetag  ) );
664 			timeLineTag.appendChild( newTAGNode );
665 		}
666 	}
667 	songNode.appendChild( timeLineTag );
668 
669 	// Automation Paths
670 	QDomNode automationPathsTag = doc.createElement( "automationPaths" );
671 	AutomationPath *pPath = pSong->get_velocity_automation_path();
672 	if (pPath) {
673 		QDomElement pathNode = doc.createElement("path");
674 		pathNode.setAttribute("adjust", "velocity");
675 
676 		AutomationPathSerializer serializer;
677 		serializer.write_automation_path(pathNode, *pPath);
678 
679 		automationPathsTag.appendChild(pathNode);
680 	}
681 	songNode.appendChild( automationPathsTag );
682 
683 	QFile file(filename);
684 	if ( !file.open(QIODevice::WriteOnly) ) {
685 		rv = 1;
686 	}
687 
688 	QTextStream TextStream( &file );
689 	doc.save( TextStream, 1 );
690 
691 	if( file.size() == 0) {
692 		rv = 1;
693 	}
694 
695 	file.close();
696 
697 	if( rv ) {
698 		WARNINGLOG("File save reported an error.");
699 	} else {
700 		pSong->set_is_modified( false );
701 		INFOLOG("Save was successful.");
702 	}
703 
704 	pSong->set_filename( filename );
705 
706 	return rv;
707 }
708 
709 };
710 
711