Location: PHPKode > projects > XRNS-PHP > _deprecated_/xrns_microtuner/xrni_microtuner.php
<?php

/*

XRNI Microtuner 

Public Domain, last modified 2007.11.21
Version: 1.03
Description: The script provide a simple way to tune Renoise XRNI instruments 
             using standard Scala tuning (*.tun) files
Usage: php xrni_microtuner.php /path/to/source.xrni /path/to/tuning.tun result.xrni
Author:	Bjørn Næsby (danoise)
Website: http://xrns-php.sourceforge.net/

Testcases:
- pentatonic.tun (standard 12-tone, octave repeating)
- mystic.tun (spans 3 octaves before repeating)
- bpg55557777.tun (doesn't repeat, has approx. 15 notes per octave)
- breed-mult29.tun (complex repeating, 58 notes per octave)
- gamelan_udan.tun (12-tone scale with duplicate tunings for 1st and 10th notes)

Todo:
- Determine automatic pitch adjustment (needed by "dark" tunings such as mixol_penta)
- low/high limit (useful with non-12 based tunings that can otherwise output a large number of samples). Insert blank sample outside the range (exlude name/file nodes)
- Support multisamples, maintain pitch for existing splits 
- semitone offset (with support for fractional values)
- use precise tuning values if available
- New parameter: if less than 12 unique tunings, provide option to expand number of tunings (enough to fill the keyboard), or insert empty slots to fit with octaves (less samples needed for the second option)

Changes 0.3
- Limit output to 120 samples
- Delete source sample after use
- Use the generated sample names
- round off finetune values
- Find positive value in tuning file when extracting unique tunings (use as offset)
- When finetune values exceed 128, adjust basenote

Changes 0.2
- Clean temp-folder before unzipping
- rename instrument to match output name 
- Fixed: registered interval '0' was reported as non-existing, causing an
	unneeded sample to be created



*/

//error_reporting(E_ALL);

// ----------------------------------------------------------------------------
// Move to xrns_functions.php?
// ----------------------------------------------------------------------------
function simplexml_replace(SimpleXMLElement $parent, SimpleXMLElement $new_child){
   $node1 = dom_import_simplexml($parent);
   $dom_sxe = dom_import_simplexml($new_child);
   $node2 = $node1->ownerDocument->importNode($dom_sxe, true);
   $node1->parentNode->replaceChild($node2,$node1);
}
// ----------------------------------------------------------------------------
// Shared functions
// ----------------------------------------------------------------------------

function createSampleName($index,$name,$ext){
	//	return string with name, formatted like this:
	//	"Sample00 (rendered selection c5).flac"
	$number = str_pad($index, 2, "0", STR_PAD_LEFT);
	return "Sample".$number." (".$name.").".$ext;
}

function getFileType($path){
	//	determine filetype (should fix reported problem)
	//	$path: full path to file, but without .suffix
	$formats = array("flac","ogg","wav");
	foreach($formats as $key){
		if(file_exists($path.".".$key)){
			$type = $key;
		}
	}
	return $type;
}

function deleteFile($path){
	unlink($path);
}

// ----------------------------------------------------------------------------
// Variables
// ----------------------------------------------------------------------------

$tmp_dir = '/tmp';

// ----------------------------------------------------------------------------
// Requires
// ----------------------------------------------------------------------------

require_once('xrns_functions.php');

// ----------------------------------------------------------------------------
// Check Variables
// ----------------------------------------------------------------------------


// get filename component of path
$argv[0] = basename($argv[0]);
if (!is_dir($tmp_dir)) {
    $tmp_dir = get_temp_dir();
    if (!$tmp_dir) die("Error: Please set \$tmp_dir in $argv[0] to an existing directory.\n");
}


// ----------------------------------------------------------------------------
// Check User Input
// ----------------------------------------------------------------------------

if ($argc != 4) {
    echo "Error: $argv[0] expects 3 parameters.\n";
    echo "Usage: `php $argv[0] /path/to/source.xrni /path/to/tuning.tun result.xrni`\n";
    die();
}

if (!file_exists($argv[1])) die("Error: The file $argv[1] was not found.\n");
if (!file_exists($argv[2])) die("Error: The file $argv[2] was not found.\n");
if (!(preg_match('/(\.zip$|\.xrni$)/i', $argv[3]))) {
    die("Error: The filename $argv[3] is invalid, use .xrni (or .zip)\n");
}

$source = $argv[1];
$destination = $argv[3];

// ----------------------------------------------------------------------------
// Unpack
// ----------------------------------------------------------------------------

echo "---------------------------------------\n";
echo "$argv[0] is working...\n";
echo date("D M j G:i:s T Y\n");
echo "---------------------------------------\n";

echo "Using temporary directory: $tmp_dir\n";

// Create a temp directory
$unzip = $tmp_dir . '/xrni/';

//	Delete it if it already exist 
//	(perhaps due to a failed previous run)
obliterate_directory($unzip);

// Unzip instrument
$result = UnzipAllFiles($source, $unzip);

if($result === FALSE) {
    echo "Error: There was a problem unzipping the file.\n";    
    die();
}

// Load XML
$xml = simplexml_load_file($unzip . 'Instrument.xml');

// ----------------------------------------------------------------------------
// Collect information about instrument
// ----------------------------------------------------------------------------

//	software limitations
$sample_basenote_min = 0;
$sample_basenote_max = 119;
$split_count = 120;

//	convert splits to an array 
$sample_count = 0;
$split_arr = array();
foreach ($xml->SplitMap->Split as $x) {
	$split_arr[count($split_arr)] = $x.'';
}  
/*
echo "<pre>split_arr:";
print_r($split_arr);
*/
//	deduce number of samples used
$sample_count = count(array_unique($split_arr));

// ----------------------------------------------------------------------------
// Load the tuning file
// ----------------------------------------------------------------------------

if (!file_exists($argv[2])) die("Error: The tuning file was not found.\n");	
$tunings = parse_ini_file($argv[2],true); 

//	determine if exact tunings are available
//$precision = (array_key_exists("Exact Tuning",$tunings)) ? "Exact Tuning" : "Tuning";
$precision = "Tuning";
/*
echo "tunings:";
print_r($tunings);
echo "\n";

echo "precision:";
echo $precision;
echo "\n";
*/


// ----------------------------------------------------------------------------
// Apply the tuning 
// ----------------------------------------------------------------------------

//	Calculate the number of different tunings required per octave
//	(an octave is exactly 1200 cents). An exact octave will be repeated 
//	across the keyboard

$unique_tunings = array();


$offset = 0;
/*
//	Look for positive value when extracting tunings, since lower values 
//	are almost not audible anyway
foreach($tunings[$precision] as $key => $value){
	if(intval($value)>=0){
		$offset = intval(substr($key,5,strlen($key)));
		break;
	}
}
echo "offset ".$offset;
echo "\n";

*/
$initial_value = intval($tunings[$precision]["note ".$offset]);
/*
echo "initial_value ".$initial_value;
echo "\n";
*/
$reached_octave = false;
while(!$reached_octave){

	if(count($unique_tunings)+$offset>-1){
		if(!isset($tunings[$precision]["note ".(count($unique_tunings)+$offset)])){
			break;
		}
	}
	$current_value = intval($tunings[$precision]["note ".(count($unique_tunings)+$offset)]);
/*
echo "current_value:";
echo $current_value;
echo "\n";
*/
	//	reached octave
	if($current_value == $initial_value + 1200){
		$reached_octave = true;
		break;
	}
	if($current_value > $initial_value + 1200){
		//echo("Warning: Not enough splits to create a single octave.\n");	
		$reached_octave = false;
	}
	$unique_tunings[count($unique_tunings)] = $current_value;
}
/*

echo "unique_tunings:";
print_r($unique_tunings);
echo "\n";

*/

/*	Extend tunings ? -------------------------------
	For example, 8 unique tunings will be multiplied by 3 (2 octaves)
	Note that it may not fit at all, in which case there will need to put a 
	limit to the number of unique_tunings
*/

/*
$tuning_count = count($unique_tunings);
if($tuning_count!=12){
	$octave = 1;

//	echo "tuning_count:".$tuning_count;
//	echo "\n";
//	echo "octave:".$octave;
//	echo "\n";

	do{
		for($i=0; $i<$tuning_count; $i++){
			$unique_tunings[count($unique_tunings)] = ($unique_tunings[$i]+($octave*1200));
		}
		$octave++;
	}while(count($unique_tunings)/12!=round(count($unique_tunings)/12));

echo "extended unique_tunings:";
print_r($unique_tunings);
echo "\n";
}
*/

/*
*/

/*	optimize number of samples needed -----------------------------------------
	Each of the samples we use for a specific tune has a frequency. 
	Assigning the sample to a key can happen to produce an interval that 
	match another sample with (100 cents X number of splits)

	These tunings:

	note 0=0
	note 1=112	
	note 2=204
	note 3=316
	note 4=406
	note 5=498
	note 6=612		// matches note 1, 600 cents higher
	note 7=702
	note 8=812		// matches note 1, 800 cents higher
	note 9=906		// matches note 4, 500 cents higher
	note 10=1006	// matches note 4, 600 cents higher
	note 11=1061	// matches note 1, but is 100 cents too small	

	Matches are stored in the interval_groups array:

	[1] => Array
		(
			[6] => 612
			[8] => 812
		)
	[4] => Array
		(
			[9] => 906
			[10] => 1006
		)

	[0] =
			[1]	100
			[2]	200
			[3]	300
			[4]	400
			[5]	500
			[6]	600
			[7]	700
			[8]	800
			[9]	900
			[10] 1000
			[11] 1100


	Conclusion: 50% less samples are needed after optimizing
	(but the number could still increase, depending on the number of splits)

	*/

//	tunings with matching intervals 
$interval_groups = array();

//	register tunings to avoid multiple matches
$registered_intervals = array();

for($i = 0; $i< count($unique_tunings); $i++ ){
	$tuning = $unique_tunings[$i];
	for($o = 0; $o < count($tunings[$precision]); $o++){
		for($p = 0; $p < $split_count; $p++){
			if($tuning===intval($tunings[$precision]["note ".$p])){
				//	check for duplicate tuning 
				if((($p-1)>=0) && $tuning===intval($tunings[$precision]["note ".($p-1)])){
					if(in_array($tuning,$unique_tunings)){
						$interval_groups[($p)][$p] = $tuning;
						$registered_intervals[$p] = ($p);
					}
				}else if(!isset($registered_intervals[$p])){
					//	check that the difference btw. $i and $p match
					//	the number of steps 
					$matched=false;
					$diff_step = ($p-$i);
					if($diff_step>0){
						if(($tuning-$unique_tunings[$i])==($diff_step*100)){
							$matched=true;
						}
					}else{
						$matched=true;
					}
					if($matched){
						$interval_groups[$i][$p] = $tuning;
						$registered_intervals[$p] = $i;					
					}
				}
			}
		}
		$tuning = ($tuning+100);
	}
}

/*

echo "interval_groups:";
print_r($interval_groups);
echo "\n";
echo "registered_intervals:";
print_r($registered_intervals);
echo "\n";
*/
//die();

/*
*/

//	Modify the samples	-------------------------------

$dom = new DOMDocument('1.0', 'iso-8859-1');
$samples = $dom->createElement("Samples");
$dom->appendChild($samples);

//	Base on the first sample, remove all others
//	todo: support multiple samples

$first_sample = $xml->Samples->Sample;
$filetype = getFileType($unzip.'SampleData/Sample00 ('.$first_sample->Name.')');

//	clone first sample, delete others from disk
$children = $xml->xpath('Samples/Sample');
$count = 0;
foreach ($children as $node) {
	if($node->Name != $first_sample->Name){
		//	delete current sample from disk
		//$ext = 
		$file = $unzip.'SampleData/'.createSampleName($count,$node->Name,$filetype);
		unlink($file) or die ("can't delete $file: $php_errormsg");
	}
	$count++;
}

//echo "first sample:".$first_sample->Name;
//echo "\n";


//	Figure out number of samples to create, by looking
//	at unique registered intervals
$sample_count = count(array_unique($registered_intervals));
/*
echo "sample_count:".$sample_count;
echo "\n";
*/
//	count number of skipped tunings *before* accepted ones
//	(used for the splitmap and samplenames)
$skipped = 0;

//	count number of accepted tunings
//	(used for the splitmap)
$created = 0;

for($i = 0; $i < $sample_count; $i++){
	$dom_sample = dom_import_simplexml($first_sample);
	$dom_sample = $dom->importNode($dom_sample, true);

	//	apply the delta of each interval to the sample,
	//	and adjust to Renoise's 127 levels of tuning
	$finetune = ($unique_tunings[$i]-(100*$i))*1.27;

	//$finetune = $finetune + (4300*1.27);

	//	if the resulting finetune exceed acceptable values,
	//	adjust the basenote and finetune accordingly
	$basenote_adjust = floor($finetune/128);
	$finetune = round($finetune-($basenote_adjust*128));
//echo "\n";

	$nodelist = $dom_sample->getElementsByTagName("Finetune");
	$nodelist->item(0)->nodeValue = $finetune;
//	$basenote = (intval($nodelist->item(0)->nodeValue)-$basenote_adjust);
	$nodelist = $dom_sample->getElementsByTagName("BaseNote");
	$basenote = (intval($nodelist->item(0)->nodeValue)-$basenote_adjust);
echo "basenote:".$basenote;
	if($basenote>0 && $basenote<119){
		//	inside valid range
echo "\tfinetune:".$finetune;
echo "\tbasenote_adjust:".$basenote_adjust;
echo "\tfinetune:".$finetune;
echo "\n";
		$nodelist->item(0)->nodeValue = $basenote;
		//	use first sample as source
		$samplename = createSampleName(($i-$skipped),$first_sample->Name." [".($i-$skipped)."]",$filetype);
		copy ( $unzip.'SampleData/'.createSampleName(0,$first_sample->Name,$filetype), $unzip.'SampleData/'.$samplename);
		$nodelist = $dom_sample->getElementsByTagName("Name");
		$nodelist->item(0)->nodeValue = $samplename;
		$dom_sample = $samples->appendChild($dom_sample);
		$created++;
	}else{
		//	outside valid range, create an empty sample slot
		$blank_sample = true;
		if($basenote>0)
			$skipped++;
	}
}

if(isset($blank_sample)){
	//	create the empty sample slot
	$dom_sample = dom_import_simplexml($first_sample);
	$dom_sample = $dom->importNode($dom_sample, true);
	$node = $dom_sample->getElementsByTagName('Name')->item(0);
	$dom_sample->removeChild($node);
	$node = $dom_sample->getElementsByTagName('FileName')->item(0);
	$dom_sample->removeChild($node);
	$node = $dom_sample->getElementsByTagName('FileModificationDate')->item(0);
	$dom_sample->removeChild($node);
	$node = $dom_sample->getElementsByTagName('FileModificationDayTime')->item(0);
	$dom_sample->removeChild($node);
	$dom_sample = $samples->appendChild($dom_sample);
}
echo "\n";
echo "created ".$created." samples";
echo "\n";
echo "\n";
/*
echo "skipped:".$skipped;
echo "\n";
echo "unique_tunings:";
print_r($unique_tunings);
echo "\n";
*/

//	Remove source sample ------------------------------

$filetype = getFileType($unzip.'SampleData/Sample00 ('.$first_sample->Name.')');
unlink($unzip.'SampleData/Sample00 ('.$first_sample->Name.').'.$filetype);


//	Create the splitmap	-------------------------------

$splitmap = new SimpleXMLElement("<SplitMap/>");
$count = 0;
for($i = 0; $i < $split_count; $i++){
	//	skip tunings outside range
	$inside = true;
	if($skipped!=0){
		if($i<$skipped){
			$inside = false;
		}else{
			if($i>($skipped+$created+1)){
				$inside = false;
			}
		}
	}
/*
echo "[$i] inside:".$inside;
echo "\n";
*/
	if($inside){
		if(array_key_exists(($count-$skipped),$registered_intervals)){
			//	this tuning has a match, assign that sample slot
			$split = $splitmap->addChild('Split',$registered_intervals[($count-$skipped)]);
		}else{
			//	assign rotating slot number based on #tunings 
			$split = $splitmap->addChild('Split',$count);
		}
		if($count==count($unique_tunings)-1)
			$count=0;
		else 
			$count++;
	}else{
		//	insert empty slot's above the range
		$split = $splitmap->addChild('Split',($created));
	}
}


// ----------------------------------------------------------------------------
// Rename instrument to match output name (excluding the suffix)
// ----------------------------------------------------------------------------

$name_arr = explode(".",$argv[3]);
$count = 0;
$name = "";

while(isset($name_arr[$count+1])){
	$name.=$name_arr[$count];
	$count++;
}
$xml->Name = $name;


// ----------------------------------------------------------------------------
// Replace XML data
// ----------------------------------------------------------------------------

$samples2 = new SimpleXMLElement($dom->saveXML());
simplexml_replace($xml->Samples,$samples2);
simplexml_replace($xml->SplitMap,$splitmap);

// ----------------------------------------------------------------------------
// Compress files
// ----------------------------------------------------------------------------

unlink($unzip . 'Instrument.xml') or die("Error: There was a problem deleting a file.\n");

file_put_contents($unzip . 'Instrument.xml', $xml->asXML());

// Zip song
$result = ZipAllFiles($destination, $unzip);
if($result === FALSE) {    
    echo "Error: There was a problem zipping the final file.\n";        
    die();    
}

// ----------------------------------------------------------------------------
// Remove temp directories
// ----------------------------------------------------------------------------

obliterate_directory($unzip);

echo "---------------------------------------\n";
echo "$argv[0] is done!\n";
echo date("D M j G:i:s T Y\n");
echo "---------------------------------------\n";

?>
Return current item: XRNS-PHP