Location: PHPKode > projects > XRNS-PHP > _deprecated_/xrns_reorganize_instruments_notes/class.xrns_reorganize_instruments_notes.php
<?php
	
	/*
	   _¯¯¯¯¯_____¯¯¯----         ===               ______¯¯¯¯¯
	   _¯      ¯¯           ¯¯ ¯  ___ _   ¯      ¯ ¯
	   ¯¯      ¯¯   D3R Ver3!seR  ---  ¯¯ ¯   ===  ¯¯  __¯¯ ¯
	   ¯¯  ---¯¯         ____           ¯¯     ¯¯ ¯
	   ¯¯      ¯¯ == ___    ¯¯¯¯   ----  ¯¯      ¯¯¯  _____ __
	                             
	
		Usage:          This script reorganizes all notes in a song, so that each instrument will use a separate track.
	                    Typically XM modules triggered multiple instruments in the same track,
	                    which would complicate any further work in Renoise. So this script is for xm files converted
	                    to xrns only.
		                
		                Note: 
		                1. The first slot in the instrument bar has to be blank.
		                2. Delete all instrument slots which you do not need. 
		                   (Especially all slots after the last used instrument slot)
		                3. If you want to run this script on your php-server, the zip.exe and unzip.exe have
	                       to be placed in the same folder as this script
		                
		Last modified:  December 29th, 2008
		Coded by:       Christian Messow alias vereiser of http://www.vereiser.de
		History:        Version 1.0 (April 24th)
		                Version 1.0.1 (December 29th)
		                          - fixed:  instruments with a number higher than 9 will not ignore anymore  
		                          - adding: delete unused tracks
		                Version 1.0.2 (December 30th)
		                	      - fixed:  empty notes of a not track owned instrument will be muted
		                          - adding: every line of an instrument with a C parameter setted to 00 is defined 
		                	        with a note off (needed for using VSTi)
		                	
		
	*/
	
	// ______________________________________________________________________________________________________
	// ======================================================================================================
	class xrnsReorganizeInstruments
	{
		/**
	     * Constructor
	     *
	     */              
		public function __construct( $settings )
		{
			$this->errors       = array();
			$this->messages     = array();
			$this->script_start = 0;
			$this->script_end   = 0;
			$this->settings     = $settings;
	
			if ( !isset( $this->settings["overwrite"] ) )
			{
				$this->settings["overwrite"] = "no";
			}
	
			if ( !isset( $this->settings["instruments"] ) )
			{
				$this->settings["instruments"] = "none";
			}
	
			if ( !isset( $this->settings["tracks"] ) )
			{
				$this->settings["tracks"] = "none";
			}
	
			if ( !isset( $this->settings["delete_unused_tracks"] ) )
			{
				$this->settings["delete_unused_tracks"] = "no";
			}
	
			// * Predefinition
			if ( $this->settings["overwrite"]=="yes" )
			{
				$this->settings["overwrite"] = true;
			}
			else
			{
				$this->settings["overwrite"] = false;
			}
	
			if ( $this->settings["delete_unused_tracks"] == "yes" )
			{
				$this->settings["delete_unused_tracks"] = true;
			}
			else
			{
				$this->settings["delete_unused_tracks"] = false;
			}
		}
	
		/**
	     * Get XRNS-Dumps for implementation
	     *
	     * @return xml
	     *      
	     */                   
		private function getXrnsDump( $type, $index="", $index_2="", $index_3="" )
		{
			switch ( $type )
			{
				case "SequencerTrack":
					$xrns_dump = <<<XML
	           <SequencerTrack type="SequencerTrack">
	              <Name>$index</Name> 
	              <State>Active</State> 
	              <NoteColumnStates>
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	                <NoteColumnState>Active</NoteColumnState> 
	              </NoteColumnStates>
	              <NumberOfVisibleNoteColumns>$index_2</NumberOfVisibleNoteColumns> 
	              <NumberOfVisibleEffectColumns>1</NumberOfVisibleEffectColumns> 
	              <VolumeColumnIsVisible>true</VolumeColumnIsVisible> 
	              <PanningColumnIsVisible>false</PanningColumnIsVisible> 
	              <TrackRouting>0</TrackRouting> 
	              <FilterDevices>
	                <Devices>
	                  <SequencerTrackDevice type="SequencerTrackDevice">
	                    <IsActive>true</IsActive> 
	                    <IsSelected>false</IsSelected> 
	                    <SelectedPresetName>Init</SelectedPresetName> 
	                    <Panning>
	                      <Value>0.5</Value> 
	                      <Visualization>Device only</Visualization> 
	                    </Panning>
	                    <Volume>
	                      <Value>1.0</Value> 
	                      <Visualization>Device only</Visualization> 
	                    </Volume>
	                    <Surround>
	                      <Value>0.0</Value> 
	                      <Visualization>Device only</Visualization> 
	                    </Surround>
	                    <PostPanning>
	                      <Value>0.5</Value> 
	                      <Visualization>Device only</Visualization> 
	                    </PostPanning>
	                    <PostVolume>
	                      <Value>1.0</Value> 
	                      <Visualization>Device only</Visualization> 
	                    </PostVolume>
	                  </SequencerTrackDevice>
	                </Devices>
	              </FilterDevices>
	            </SequencerTrack>
XML;
					break;

				case "Line":
					$xrns_dump = <<<XML
	            <Line index="{$index}"></Line>
XML;
					break;

				case "PatternTrack":
					$xrns_dump = <<<XML
	            <PatternTrack type="PatternTrack"/>
XML;
					break;

				case "Lines":
					$xrns_dump = <<<XML
		            <Lines></Lines>
XML;
					break;

				case "NoteColumns":
					$xrns_dump = <<<XML
		            <NoteColumns></NoteColumns>
XML;
					break;

				case "EmptyNoteColumn":
					$xrns_dump = <<<XML
		            <NoteColumn>
		            </NoteColumn>
XML;
					break;

				case "NoteColumn":
					$note_data = "";

					foreach ( $index as $dkey => $data )
					{
						$note_data .= "<$dkey>$data</$dkey>";
					}

					$xrns_dump = <<<XML
		            <NoteColumn>
		              $note_data
		            </NoteColumn>
XML;
					break;
			}
		
			return simplexml_load_string( $xrns_dump );
		}
	
		/**
	     * Delete original instrument tracks
	     * Track one will be overwritte as an empty track
	     * 
	     * function debugged 09.11.08 - line 215
	     * 
	     * @return empty
	     *      
		*/                   
		private function deleteAllOriginalInstrumentTracks( $instruments )
		{
			$size    = sizeof( $instruments );
			$numbers = $this->getNumberOfTracks();
			$b       = $numbers - $size - 1;
		
			// * Delete track for each pattern
			foreach ( $this->xml->PatternPool->Patterns->Pattern as $pattern )
			{
				$a = $numbers - $size - 1;
		
				while ( $a >= 0  )
				{
					unset( $pattern->Tracks->PatternTrack[$a] );
					$a--;
				}
				$pattern->Tracks->PatternTrack[0] = $this->getXrnsDump( "PatternTrack" );
			}
		
			// * Delete main track configuration
			while ( $b >= 0 )
			{
				unset( $this->xml->Tracks->SequencerTrack[$b] );
				$b--;
			}
		}
		
		/**
		 * Delete all none instrument matching notes 
		 *
		 * @param unknown_type $instruments
		 * @param unknown_type $instrument_positions
		 * @return empty
		 * 
		 */
		private function deleteAllNotesWhichAreNotPartOfTheInstrument( $instruments, $instrument_positions )
		{
			$ip_keys      = array_keys( $instrument_positions );

			foreach ( $instruments as $instr_nr => $instruments )
			{
				$decInstrument = strtoupper(dechex($instr_nr));
				$instrIn       = $instr_nr;		
							
				if ( strlen( $decInstrument ) == 1 )
				{
					$decInstrument = "0$decInstrument";	
				}

				if ( $instr_nr < 10 )
				{
					$instrIn = "0$instr_nr";	
				}
				
				if ( isset($instrument_positions[$instrIn] ) )
				{
					$size_columns = sizeof( $instrument_positions[$instrIn] );

					foreach ( $this->xml->PatternPool->Patterns->Pattern as $pattern )
					{
						if ( isset($pattern->Tracks->PatternTrack[$instr_nr]->Lines) )
						{
							foreach ( $pattern->Tracks->PatternTrack[$instr_nr]->Lines->Line as $line )
							{
								$a = 0;
								
								while ( $a < $size_columns )
								{
									$cleaned = false;
									
									// * if note columns exists, check for instruments, which not match the instrument track
									if ( isset( $line->NoteColumns->NoteColumn[$a] ) )
									{
										if ( !isset( $cleaningData[$decInstrument] ) )
										{
											$cleaningData[$decInstrument] = array();
										}
										
										$instrumentNumberNoteColumn = (string) $line->NoteColumns->NoteColumn[$a]->Instrument->{0};
										
										// * following notes are part of the actual instrument,
										if 
										( 
											$instrumentNumberNoteColumn === $decInstrument && 
											$decInstrument && $instrumentNumberNoteColumn !== "" 
										)
										{
											$this->is_track_instrument = true;
										}
										
										// * following notes are part of a foreign instrument
										if 
										( 
											$instrumentNumberNoteColumn !== $decInstrument && 
											$decInstrument && $instrumentNumberNoteColumn !== "" 
										)
										{
											$this->is_track_instrument = false;
										}

										// * if instrument is not part of the instrument track
										if 
										( 
											$instrumentNumberNoteColumn !== $decInstrument && 
											$decInstrument && $instrumentNumberNoteColumn !== "" 
										)
										{
											$this->muteNoteColumn( $line->NoteColumns->NoteColumn[$a] );
										}
										else
										{
											// * if no instrument note is set, check if the empty note
											//   is part of the instrument, which should not be deleted
											if ( $instrumentNumberNoteColumn === "" && !$this->is_track_instrument )
											{
												$this->muteNoteColumn( $line->NoteColumns->NoteColumn[$a] );
											}
											else 
											{
												// * To use a VSTi on an instrument, each line with the pattern command C setted to 00
												//   will be defined with a note off
												if 
												( 
													$instrumentNumberNoteColumn === "" && 
													$line->NoteColumns->NoteColumn[$a]->Volume == 0  
												)
												{
													$line->NoteColumns->NoteColumn[$a]->Note = "OFF";
												}
											}
										}
									}
									$a++;
								}
							}
						}
					}
				}
			}
		}
		
		/**
	     * Set all unused notes as note off 
	     *
	     * @return empty
	     *     
	     */                   
		private function muteNoteColumn( $note_column )
		{
			if ($note_column)
			{
				foreach ( $note_column as $dkey => $data )
				{
					$note_column->$dkey = "";
				}
		
				$note_column->Note   = "OFF";
				$note_column->Volume = "00";
			}
		}
		
		/**
	     * Writes the final file
	     *      
	     * @return boolean   
	     *             
	     */         
		private function writeDestinationFileToDisk( $xml )
		{
			$unzipped_xml_file       = "{$this->unzip}Song.xml";
			$destination_file        = $this->destination_file;
			$xml->SelectedTrackIndex = 0;                           // Prevent Renoise from crashing ...
		
			// * Check xml compatibility
			if ( !$this->xrnsXsdCheck( $xml, (int)$xml['doc_version'] ) )
			{
				$this->addErrorMessage( "XML is invalid!" );
				obliterate_directory( $this->unzip );
				return false;
			}
		
			// * Replace Song.xml
			if ( !@unlink( $unzipped_xml_file ) )
			{
				$this->addErrorMessage( "There was a problem deleting a file." );
			}
		
			// * Put data to file
			file_put_contents( $unzipped_xml_file , $xml->asXML() );
		
			if ( $this->settings["source"] == $this->destination_file )
			{
				$this->addErrorMessage( "The name of the source file {$this->settings["source"]} is the same as that of the destination filename." );
				return false;
			}
		
			// * Check if destination file is existent
			if ( file_exists( $this->destination_file ) && !$this->settings["overwrite"] )
			{
				$this->addErrorMessage( "Destination file exists. Please rename the file or use the overwrite option." );
				return false;
			}
		
			// * Delete destination song if exists
			if ( $this->settings["overwrite"] && file_exists ( $destination_file ) )
			{
				if ( !@unlink( $destination_file ) )
				{
					$this->addErrorMessage( "Error while deleting the old destination file." );
					return false;
				}
			}
		
			// * Write source XML File
			if ( $this->settings["source_xml"] )
			{
				$xml_file = "{$this->destination_file}.xml";
		
				if ( !@unlink( $xml_file ) )
				{
					$this->addErrorMessage( "Could not delete source xml file." );
				}
		
				file_put_contents( $xml_file, $xml->asXML() );
			}
		
			// * Zip song
			if( !$result = ZipAllFiles( $this->destination_file, $this->unzip ) )
			{
				$this->addErrorMessage( "There was a problem zipping the final file." );
				return false;
			}
		
			// * Remove temp directories
			obliterate_directory( $this->unzip );
		
			return true;
		}
		
		/**
	     * Qualify Headline of Console Text
	     * 
	     * @return string          
	     *
	     */              
		private function getHeadlineForProcessMessage( $headline )
		{
			switch ( $headline )
			{
				case "track_deleting":
					$text = "Deleted Tracks";
					break;
		
				case "instruments":
					$text = "Instruments";
					break;
		
				case "finalization":
					$text = "Finalization";
					break;
		
				case "initialization":
					$text = "Initialization";
					break;
		
				case "special_operations":
					$text = "Special Operations";
					break;
		
				case "reorganize":
					$text = "Reorganize Instruments";
					break;
		
				case "track_using":
					$text = "Track Using";
					break;
			}
		
			return $text;
		}
	
		// __________________________________________________________________________________________________
		// ===================================== Preparations ===============================================
		/**
	     * Get XML data of the source file
	     *      
	     * @return xml   
	     *             
	     */         
		private function getXmlDataFromSourceFile()
		{
			if ( !$result = $this->unzipAllFiles( $this->source_file, $this->unzip ) )
			{
				$this->addErrorMessage( "There was a problem unzipping the source file $this->source_file." );
				return false;
			}
			else 
			{
				if ( $xml = simplexml_load_file( $this->unzip . 'Song.xml') )
				{
					return $xml;
				}
			}

			return false;	
		}
		
		/**
		 * Enter description here...
		 *
		 * @param string $zipFile
		 * @param string $zipDir
		 * @return empty
		 * 
		 */
		function unzipAllFiles( $zipFile, $zipDir ) 
		{
		    $UnusedArrayResult  = array();
		    $unzip_xrns         = 'unzip "@_SRC_@" -d "@_DST_@"'; // info-zip assumed to be in path
		    $unzipCmd           = $unzip_xrns;
		    $unzipCmd           = str_replace('@_SRC_@' , $zipFile , $unzipCmd );
		    $unzipCmd           = str_replace('@_DST_@' , $zipDir  , $unzipCmd );
		    $res                = -1; // any nonzero value
		    $UnusedStringResult = exec( $unzipCmd, $UnusedArrayResult, $res );

		    if ($res != 0) 
		    {
		    	return false;	
			}
			else 
			{
				return true;	
			}
		}
		
		/**
	     * Prepare file operations
	     *         
	     * Argv
	     * 0 = name of the script
	     * 1 = source file
	     * 2 = destination file               
	     *
	     * @return boolean  
	     *     
	     */              
		private function prepareFileOperations(  )
		{
			// * Define temporary directory
			$dirname            = basename( $this->settings["script"] );
			$_GLOBALS["argv"][0] = $dirname;
		
			if ( is_dir( $this->settings["tmp_dir"] ) )
			{
				$tmp_dir = $this->settings["tmp_dir"];
			}
			else
			{
				if ( !$tmp_dir = get_temp_dir() )
				{
					$this->addErrorMessage( "Please set 'tmp_dir' in $dirname to an existing directory.");
					return false;
				}
			}
		
			// * Create temp directory
			$md          = md5(uniqid(mt_rand(), true));
			$this->unzip = "$tmp_dir/xrnsReorganizeInstruments_{$md}/";
		
			// * Check source file
			if ( !file_exists( $this->settings["source"] ) )
			{
				$this->addErrorMessage( "The file {$this->settings["source"]} was not found." );
				return false;
			}
		
			// * Check destination file
			if ( !( preg_match( '/(\.zip$|\.xrns$)/i', $this->settings["destination"] ) ) )
			{
				$this->addErrorMessage( "The filename {$this->settings["destination"]} is invalid, use .xrns (or .zip)" );
				return false;
			}
		
			// * Prepare variables
			$this->source_file      = realpath( $this->settings["source"] );
			$this->temp_dir         = realpath( $tmp_dir );
			$this->destination_file = $this->settings["destination"];
		
			return true;
		}
		
		/**
		 * Validate XML
	     *
		 * @param unknown_type $sx
		 * @param unknown_type $doc_version
		 * @return boolean
		 * 
		 */
		private function xrnsXsdCheck( $sx, $doc_version )
		{
			$docversion = (int)$doc_version;
			$xsd        = "RenoiseSong$docversion.xsd";
			$path       = dirname( $this->settings["script"] );
			$schema     = "$path/schemas/$xsd";
		
			if ( file_exists($schema) )
			{
				$dd = new DOMDocument;
				$dd->loadXML($sx->asXML());
		
				if ( $dd->schemaValidate( $schema ) )
				{
					$this->addProcessMessage( "Reorganized XML is valid.", "finalization" );
					return true;
				}
			}
			else
			{
				$this->addErrorMessage( "Warning: $schema not found, skipping XML validation." );
				return true;
			}
		
			return false;
		}
		
		/**
	     * Get numbers of selected tracks 
	     *
	     * @param xml-object $xml
	     * @return integer
	     *
	     */                   
		private function getNumbersOfSelectedTracks( $xml )
		{
			$size = sizeof( $xml->PatternPool->Patterns->Pattern[0]->Tracks->PatternTrack );
		
			return $size;
		}
	
		// __________________________________________________________________________________________________
		// =================================== Reorganization ===============================================
		/**
	     * Copy the original tracks as they are to their specific note colums
	     * 
	     * @return empty          
	     *
	     */              
		private function copyOriginalTracksToSeperateNoteColumns( $instruments, $instrument_positions )
		{
			$ikeys = array_keys( $instrument_positions );
		
			foreach ( $instruments as $instr_nr => $instr_name )
			{
				$size      = sizeof( $instruments );
				$copy_pos  = $instr_nr + $this->getNumberOfTracks() - $size;
		
				$instr_index = $instr_nr;
		
				if ( $instr_index < 10 )
				{
					$instr_index = "0$instr_nr";
				}
		
				if ( isset( $instrument_positions[$instr_index] ) )
				{
					$note_column_count = 0;
		
					foreach ( $instrument_positions[$instr_index] as $ikey => $instrument_position )
					{
						$pattern_count = 0;
						$instrument_position--;
		
						foreach ( $this->xml->PatternPool->Patterns->Pattern as $pattern )
						{
							if ( $org_pattern_track = $pattern->Tracks->PatternTrack[$instrument_position] )
							{
								if ( $org_pattern_track->Lines )
								{
									foreach ( $org_pattern_track->Lines->Line as $lkey => $line )
									{
										if ($copy_pattern_track = $pattern->Tracks->PatternTrack[$copy_pos])
										{
											// * Append element Lines if it is not existing
											if ( !$copy_pattern_track->Lines )
											{
												simplexml_append( $copy_pattern_track, $this->getXrnsDump( "Lines" ) );
											}
		
											// * Append element Line if it is not existing
											$attributes  = $line->attributes(  );
											$linekey     = $this->isNoteColumnLineExistent( $copy_pattern_track->Lines, $attributes["index"] );
		
											if ( $linekey === false )
											{
												simplexml_append( $copy_pattern_track->Lines, $this->getXrnsDump( "Line", $attributes["index"] ) );
											}
		
											$linekey = $this->isNoteColumnLineExistent( $copy_pattern_track->Lines, $attributes["index"] );
		
											// * Append element NoteColums if it is not existing
											if ( !$copy_pattern_track->Lines->Line[$linekey]->NoteColumns )
											{
												simplexml_append( $copy_pattern_track->Lines->Line[$linekey], $this->getXrnsDump( "NoteColumns" ) );
											}
		
											// * Append element NoteColum if it is not existing
											if ( !$copy_pattern_track->Lines->Line[$linekey]->NoteColumns->NoteColumn[$note_column_count] )
											{
												$effect_data = $this->getEffectColumnValues( $line->NoteColumns->NoteColumn[0] );
												$this->appendEmptyNoteColumns( $copy_pattern_track->Lines->Line[$linekey]->NoteColumns, $note_column_count );
												simplexml_append( $copy_pattern_track->Lines->Line[$linekey]->NoteColumns, $this->getXrnsDump( "NoteColumn", $effect_data  ) );
											}
										}
									}
								}
							}
							$pattern_count++;
						}
						$note_column_count++;
					}
				}
			}
		}
		
		/**
		 * Append empty note columns
	     *
		 * @param object $copy_note_columns
		 * @param numeric $actual_note_column
		 * @return empty
		 * 
		 */
		private function appendEmptyNoteColumns( $copy_note_columns, $actual_note_column )
		{
			for ( $a = 0; $a < $actual_note_column; $a++ )
			{
				if ( !$copy_note_columns->NoteColumn[$a] )
				{
					simplexml_append( $copy_note_columns, $this->getXrnsDump( "EmptyNoteColumn" ) );
				}
			}
		}
		
		/**
	     * Get effect column values
	     * 
	     * @return array          
	     *
	     */              
		private function getEffectColumnValues( $noteColumn )
		{
			$data = array();
		
			if ($noteColumn)
			{
				foreach ( $noteColumn as $nkeys => $values )
				{
					$data[$nkeys] = $values;
				}
			}
		
			return $data;
		}
		
		/**
	     * Is line existent
	     *
	     * @return boolean     
	     *
	     */                   
		private function isNoteColumnLineExistent( $lines, $index )
		{
			$count = 0;
		
			if ( $lines )
			{
				foreach ( $lines->Line as $line )
				{
					$attr = $line->attributes(  );
		
					if ( intval($attr["index"]) == intval($index) && $index )
					{
						return intval($count);
					}
		
					$count++;
				}
				return false;
			}
			else
			{
				return false;
			}
		}
		
		/**
	     * Prepare Instrument Tracks for Copy, while creating a new track for an instrument
	     * 
	     * @return empty          
	     *
	     */              
		private function buildEmptyNewTrackForCopyingTheOriginalInstrument( $instruments, $instrument_positions )
		{
			$ikeys  = array_keys( $instrument_positions );
			$iikeys = array_keys( $instruments );
		
			// * Append SequencerTracks and PatternTracks for each instrument
			foreach ( $instruments as $instr_nr => $instr_name )
			{
				$dec_in = strtoupper(dechex($instr_nr));
		
				if ( strlen($dec_in) < 2 )
				{
					$dec_in = "0$dec_in";
				}
		
				if ( $instr_nr < 10 )
				{
					$in = "0$instr_nr";
				}
				else
				{
					$in = $instr_nr;
				}
		
				if ( empty( $instr_name ) )
				{
					$instr_name = "unnamed ($dec_in)";
				}
		
				if ( isset( $instrument_positions[$in] ) )
				{
					$note_colums = sizeof($instrument_positions[$in] );
				}
				else
				{
					$note_colums = 1;
				}
		
				simplexml_append( $this->xml->Tracks, $this->getXrnsDump( "SequencerTrack", "$instr_name ($dec_in)", $note_colums ) );
		
				foreach ( $this->xmlc->PatternPool->Patterns->Pattern as $pattern )
				{
					simplexml_append( $pattern->Tracks, $this->getXrnsDump( "PatternTrack" ) );
		
					// * Append element Lines if it is not existing
					if ( !$pattern->Tracks->PatternTrack->Lines )
					{
						simplexml_append( $pattern->Tracks->PatternTrack, $this->getXrnsDump( "Lines" ) );
					}
				}
			}
		
			// * Reorganize SequencerMasterTrack
			simplexml_append( $this->xml->Tracks, $this->xml->Tracks->SequencerMasterTrack );
		
			// * Reorganize SequencerSendTracks
			if ( $this->xml->Tracks->SequencerSendTrack )
			{
				for ( $a = sizeof( $this->xml->Tracks->SequencerSendTrack ); $a > 0; $a-- )
				{
					simplexml_append( $this->xml->Tracks, $this->xml->Tracks->SequencerSendTrack[0] );
				}
			}
		
			// * Reorganize PatternSendTracks
			foreach ( $this->xml->PatternPool->Patterns->Pattern as $pattern )
			{
				simplexml_append( $pattern->Tracks, $pattern->Tracks->PatternMasterTrack );
		
				if ( $pattern->Tracks->PatternSendTrack )
				{
					for ( $a = sizeof( $pattern->Tracks->PatternSendTrack ); $a > 0; $a-- )
					{
						simplexml_append( $pattern->Tracks, $pattern->Tracks->PatternSendTrack[0] );
					}
				}
			}
		
		}
		
		/**
	     * Get number of selected tracks
	     *
	     * @return integer     
	     *
	     */                   
		private function getNumberOfTracks()
		{
			return sizeof( $this->xml->Tracks->SequencerTrack );
		}
		
		/**
		     * Prepare Original Instrument Tracks
		     * 
		     * @return empty          
		     *
		     */              
		private function buildEmptyTrackForEachUnusedInstrument( $instruments, $instrument_positions )
		{
			$ikeys = array_keys( $instrument_positions );
		
			foreach ( $instruments as $instr_nr => $instr_name )
			{
				$note_colums = 1;
		
				if ( isset( $ikeys[$instr_nr] ) )
				{
					if ( isset( $instrument_positions[$ikeys[$instr_nr]] ) )
					{
						$note_colums = sizeof($instrument_positions[$ikeys[$instr_nr]] );
					}
				}
		
				if ( empty( $instr_name ) )
				{
					$instr_name = "unnamed (org)";
				}
		
				if ( $this->xml->Tracks->SequencerTrack[$instr_nr] )
				{
					$this->xml->Tracks->SequencerTrack[$instr_nr]->Name->{0} = "$instr_name (org)";
				}
				else
				{
					simplexml_append( $this->xml->Tracks, $this->getXrnsDump( "SequencerTrack", "$instr_name (org)", $note_colums ) );
					simplexml_append( $this->xml->Tracks, $this->xml->Tracks->SequencerMasterTrack );
		
					if ( $this->xml->Tracks->SequencerSendTrack )
					{
						simplexml_append( $this->xml->Tracks, $this->xml->Tracks->SequencerSendTrack );
					}
		
					foreach ( $this->xmlc->PatternPool->Patterns->Pattern as $pattern )
					{
						simplexml_append( $pattern->Tracks, $this->getXrnsDump( "PatternTrack" ) );
						simplexml_append( $pattern->Tracks, $pattern->Tracks->PatternMasterTrack );
		
						if ($pattern->Tracks->PatternSendTrack)
						{
							simplexml_append( $pattern->Tracks, $pattern->Tracks->PatternSendTrack );
						}
					}
				}
			}
		}
		
		/**
		     * Set instrument message
		     * 
		     * @return empty          
		     *
		     */              
		private function setInstrumentProcessMessage( $instruments )
		{
			$instrument_message = "";
		
			foreach ( $instruments as $instid => $instrument )
			{
				if ( $instid < 10 )  { $instid = "0$instid"; }
		
				if ( empty( $instrument ) )
				{
					$instrument = "unnamed";
				}
		
				$instrument_message .= "\"Instr $instid ($instrument)\", ";
			}
		
			$instrument_message = substr( $instrument_message, 0, -2 );
		
			$this->addProcessMessage( "$instrument_message", "reorganize" );
		}

		/**
	     * Get Instruments of the song
	     *
	     * @return array
	     *      
	     */                   
		private function getInstruments( $xml )
		{
			$instruments = array();
			$instr_id    = 0;
		
			foreach ( $xml->Instruments->Instrument as $instrument )
			{
				$instrument_name        = (string) $instrument->Name;
		
				if ( $instr_id < 10 )
				{
					$new_instr_id = "0$instr_id";
				}
				else
				{
					$new_instr_id = $instr_id;
				}
		
				if ( empty( $instrument_name ) )
				{
					$instrument_name = "unnamed";
				}
		
				$instruments[$instr_id] = $instrument_name;
				$this->addProcessMessage( "Instr $new_instr_id ($instrument_name)", "instruments" );
				$instr_id++;
			}
			
			return $instruments;
		}
		
		/**
	     * This function is the original code of the delete_unused_tracks function
	     *
	     * @return xml-object
	     *          
	     */                        
		private function deleteUnusedTracks( $xml )
		{
			// * Find stuff
			$in_use = array();
		
			foreach ( $xml->PatternPool->Patterns->Pattern as $p )
			{
				$i = 0;
		
				foreach ( $p->Tracks->PatternTrack as $x )
				{
					if ( $x->Lines )
					{
						$in_use[$i] = true;
					}
					++$i;
				}
			}
		
			// * Delete stuff
			$total = count( $xml->Tracks->SequencerTrack );
		
			foreach ( $xml->PatternPool->Patterns->Pattern as $p )
			{
				for( $i = $total - 1; $i >= 0; --$i )
				{
					if ( !isset( $in_use[$i] ) )
					{
						unset( $p->Tracks->PatternTrack[$i] );
					}
				}
			}
		
			for( $i = $total - 1; $i >= 0; --$i )
			{
				if ( !isset( $in_use[$i] ) )
				{
					unset( $xml->Tracks->SequencerTrack[$i] );
					$deletedTracks[$i] = true;
				}
			}
		
			$deletedTracks = array_keys($deletedTracks);
			sort($deletedTracks);
			
			foreach ( $deletedTracks as $deletedTrack )
			{
				$this->addProcessMessage( "Track $deletedTrack deleted", "track_deleting" );	
			}
			
			return $xml;
		}
		
		/**
	     * Get the tracks an instruments uses
	     *
	     * @return xml-object     
	     *     
	     */                   
		private function getTracksOfInstruments( $xml )
		{
			$instrument_positions = array();
			$pattern_pos = 1;
		
			// * For every pattern
			foreach ( $xml->PatternPool->Patterns->Pattern as $pattern )
			{
				$track_pos = 1;
		
				// * For every tracks of a pattern
				foreach ( $pattern->Tracks->PatternTrack as $track )
				{
					if ($track->Lines->Line)
					{
						foreach ( $track->Lines->Line as $line )
						{
							if ( isset($line->NoteColumns->NoteColumn) )
							{
								if ($instr_nr  = (string) $line->NoteColumns->NoteColumn->Instrument[0] )
								{
									$instr_nr = hexdec( $instr_nr );
			
									if ( $instr_nr < 10 )
									{
										$instr_nr = "0$instr_nr";
									}
			
									if ( isset( $instrument_positions[$instr_nr] ) )
									{
										if ( !is_array( $instrument_positions[$instr_nr] ) )
										{
											$instrument_positions[$instr_nr] = array();
										}
									}
									else
									{
										$instrument_positions[$instr_nr] = array();
									}
			
									if ( !in_array( $track_pos, $instrument_positions[$instr_nr] ) )
									{
										$instrument_positions[$instr_nr][] = $track_pos;
									}
								}
							}
						}
					}
					$track_pos++;
				}
				$pattern_pos++;
			}
		
			// * Create message
			$sortedInstrumentPositions = array();
			$instrumentPositionKeys    = array_keys($instrument_positions);
			sort($instrumentPositionKeys);
			
			foreach ($instrumentPositionKeys as $instrumentPositionKey) 
			{
				$sortedInstrumentPositions[$instrumentPositionKey] = $instrument_positions[$instrumentPositionKey];
			}
			
			if ($sortedInstrumentPositions)
			{
				foreach ( $sortedInstrumentPositions as $ikey => $instrument )
				{
					$tracks = implode( ",", $instrument );
		
					$this->addProcessMessage( "Instrument $ikey uses tracks $tracks.", "track_using" );
				}
			}
			
			return $sortedInstrumentPositions;
		}
	
		// __________________________________________________________________________________________________
		// ======================================= Errors ===================================================
		/**
		     * Add message
		     *
		     * @return empty
		     *      
		     */                   
		private function addProcessMessage( $message, $headline )
		{
			$count = count($this->messages);
			$this->messages[$count]["text"] = $message;
			$this->messages[$count]["head"] = $headline;
		}
		
		/**
		     * Add error
		     * 
		     * @return empty
		     *      
		     */              
		private function addErrorMessage( $error )
		{
			$this->errors[] = $error;
		}
		
		/**
		     * Get Errors
		     *
		     * @return empty
		     *      
		     */              
		private function getErrorMessage()
		{
			return $this->errors;
		}
		
		/**
		     * Show Errors
		     *
		     * @return string
		     *      
		     */                   
		public function showErrorMessages()
		{
			$mstring = "";
			$date    = date("D M j G:i:s T Y");
		
			foreach ( $this->errors as $error )
			{
				$mstring .= "| $error\n";
			}
		
			$mstring      = substr( $mstring, 0, -2 );
			$error_string = <<<TEXT
+=============================================================================
|
| Organize Notes of Instruments by their specific track
|
| ============================================================================
| Date:             $date
| Source file:      $this->source_file
| Destination file: $this->destination_file
| ----------------------------------------------------------------------------
|
$mstring
|
| ____________________________________________________________________________
| !!! An error occurred, while running the process !!!
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=============================================================================
		
		
		
TEXT;
		
			return $error_string;
		}
		
		/**
	     * Construct message text with all message
	     *
	     * @return string
	     *      
	     */                   
		public function showProcessMessages()
		{
			date_default_timezone_set( 'Europe/Paris' );
			$date      = date("D M j G:i:s T Y");
			$mstring   = "";
			$headlines = array();
			$time      = round( $this->script_end - $this->script_start, 3 );
		
			if ( $this->messages )
			{
				foreach ( $this->messages as $message )
				{
					if ( !in_array( $message["head"], $headlines ) )
					{
						$headline = $this->getHeadlineForProcessMessage( $message["head"] );
						$length   = strlen( $headline ) + 1;
						$mstring .= "|\n";
						$mstring .= "| $headline:\n";
		
						$mstring .= "| ";
		
						for ( $a = 0; $a < $length; $a++ )
						{
							$mstring .= "~";
						}
						$mstring     .= "\n";
						$headlines[]  = $message["head"];
					}
		
					$message["text"]  = wordwrap( $message["text"], 70, "<split>" );
					$spstring         = split( "<split>", $message["text"] );
		
					foreach ( $spstring as $skey => $sp )
					{
						if ( $skey == 0 )
						{
							$mstring .= "| - {$sp}\n";
						}
						else
						{
							$mstring .= "|   {$sp}\n";
						}
					}
				}
				$mstring = substr( $mstring, 0, -2 );
			}
			else
			{
				$mstring = "| no message";
			}
		
			$message_string = <<<TEXT
+=============================================================================
|
| Organize Notes of Instruments by their specific track
|
+ ============================================================================
| Date:                   $date
| Source file:            $this->source_file
| Destination file:       $this->destination_file
| Total exececution time: $time seconds
| ----------------------------------------------------------------------------
|
$mstring
|
| ____________________________________________________________________________
| File $this->source_file reorganized successfully.
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=============================================================================
		
		
		
TEXT;
		
			return $message_string;
		}
		
		/**
	     * Set name of unnamed instruments with "unnamed (xx)"
	     * 
	     * @return empty
	     *      
	     */              
		private function setDefaultNameForUnnamedInstruments( $instruments )
		{
			foreach ( $this->xml->Instruments->Instrument as $instrument )
			{
				if ( !$instrument->Name->{0} )
				{
					$instrument->Name->{0} = "--- unnamed ---";
				}
			}
		}
		
		/**
	     * Reorganize data - The main xml transforming function
	     * 
	     * @param object $xml
	     * @return xml
	     *      
	     */              
		private function getReorganizedData( $xml )
		{
			$instr              = array();
			$instrument_message = (string) "";
			$this->xml          = $xml;
			$this->xmlc         = $xml;
		
			// * Get all used Instruments
			if ( !$instruments = $this->getInstruments( $xml ) )
			{
				$this->addErrorMessage( "No Instruments were set in the song." );
				return false;
			}
		
			// * Get each tracks an instrument uses
			if ( !$instrument_positions = $this->getTracksOfInstruments( $xml ) )
			{
				$this->addErrorMessage( "No Instruments were set in the tracks." );
				return false;
			}
		
			// * Prepare instrument notes for the copy process
			$this->setDefaultNameForUnnamedInstruments( $instruments );
			$track_number = $this->getNumbersOfSelectedTracks( $xml );
			$this->addProcessMessage( "$track_number tracks are used.", "track_using" );
			$this->setInstrumentProcessMessage( $instruments );
		
			// * Append missed instrument tracks
			$this->buildEmptyTrackForEachUnusedInstrument( $instruments, $instrument_positions );
			$this->addProcessMessage( "Append not existent original instrument tracks.", "instruments" );
		
			// * Prepare instrument tracks for copying instrument notes
			$this->buildEmptyNewTrackForCopyingTheOriginalInstrument( $instruments, $instrument_positions );
			$this->addProcessMessage( "Instrument tracks with note columns prepared for the copy process.", "instruments" );
		
			// * Copy instrument notes by tracks
			$this->copyOriginalTracksToSeperateNoteColumns( $instruments, $instrument_positions );
			$this->addProcessMessage( "Original tracks copied to instrument note colums.", "instruments" );
		
			// * Delete original instrument tracks
			$this->deleteAllOriginalInstrumentTracks( $instruments );
			$this->addProcessMessage( "All original instrument tracks deleted.", "instruments" );
			
			// * Delete not accepted instruments in the column of a track
			$this->deleteAllNotesWhichAreNotPartOfTheInstrument( $instruments, $instrument_positions );
			$this->addProcessMessage( "Not accepted instrument notes in all track columns deleted.", "instruments" );
			
			return $this->xml;
		}
		
		/**
	     * Run script - Main Actions
	     * - action: reorganize_data is fully adaptable     
	     * 
	     * @return boolean   
	     *             
	     */              
		public function run( $action = "" )
		{
			$this->script_start = microtime(true);
		
			// * Prepare file operation
			if ( !$this->prepareFileOperations() )
			{
				return false;
			}
		
			$this->addProcessMessage( "File Operations prepared.", "initialization" );
		
			// * Get source xml data
			if ( !$xml = $this->getXmlDataFromSourceFile() )
			{
				return false;
			}
		
			$this->addProcessMessage( "XRNS unzipped and XML data received from archive.", "initialization" );
		
			// * Reorganize data
			if ( !$reorganized_xml = $this->getReorganizedData( $xml ) )
			{
				return false;
			}
		
			$this->addProcessMessage( "Song data reorganized.", "finalization" );
		
			// * Delete unused tracks
			if ( $this->settings["delete_unused_tracks"] )
			{
				$reorganized_xml = $this->deleteUnusedTracks( $reorganized_xml );
				$this->addProcessMessage( "Unused tracks deleted.", "special_operations" );
			}
		
			// * Write destination file
			if ( !$this->writeDestinationFileToDisk( $reorganized_xml ) )
			{
				return false;
			}

			$this->addProcessMessage( "File $this->destination_file written successfully.", "finalization" );
		
			$this->script_end = microtime(true);
		
			return true;
		}
	}
	
?>
Return current item: XRNS-PHP