Merge Songs

for merge song need to do next things

  1. unpack
  2. Merge song.xml
  3. check all samples
  4. pack

no problem m8 i can wait for it but .net 2.0 containe xml merge method $)

PHP5 also has XML merge, that’s not how you “merge” XRNS xml though… it won’t work like that. I tried. You should give it a try too though, it’s always fun to learn by spending hours failing :)

When my script is done i’ll outline the algorithm/procedure in human readable comments so others can implement it in other languages.

Good times.

okay)

Ok, this is still not done. I am basically using this thread as a place to store backups. My basic test case works, but more complex songs fail miserably. I won’t be able to work on this for a few days. If anyone wants to have a go, by all means, please go ahead. Otherwise, your patience is appreciated.

  
EDIT: Scroll Down...  
  

EDIT: I added padding zero left pading (03 + 2 = 05) and a non-recursive obliterate directory because I was having problem on Windows.

Code updated, yet again. Spare PHP coder and XML guru insight appreciated.

  
EDIT: Scroll Down...  
  

In case it wasn’t apparent, the above code now fully merges song2 into song1, creating song3.

My problem is that this merged song crashes Renoise.

At this point, most of the work is done. We just need to work out what I’m missing, doing wrong, etc. Why not have a try at this code?

If you are on windows download ftp://ftp.info-zip.org/pub/infozip/win32/unz552xn.exe and ftp://ftp.info-zip.org/pub/infozip/win32/zip232xn.zip then put zip.exe / unzip.exe in your Windows/System32 directory (trash the other files). I opted for external binaries because compiling ZIP support in PHP5 on OS X is not trivial. Downloading 2 tiny executables that are already on every operating system in existence (except, of course, Windows) is easier to deal with. Also, create a directory called /tmp on the partition you are working on. Or, change the variable at the top of the script.

Really, I won’t have time to look at this until next week. It would be really cool if a few of you could try merging some test songs and tell me where I am going wrong, because I can’t see it at the moment. The more eyes, the better.

Thanks.

Disclaimer 1: Above code = see previous page.
Disclaimer 2: Do not trash System32, I mean trash the superfluous files in the zip/unzip packages.
Disclaimer 3: I am not responsible for you ruining/deleting your system with your own incompetence.

Cheers.

Here is a fixed version. You just forgot one foreach in the patterntrack insertion stuff - only fixed the track counts in the first patterns…

The extra “if (count($x->Tracks->PatternSendTrack))” is for the rare (but possible) case that no send tracks are available in song2.

Also fixed a small problem in the instrument shifting code.

  
<?php <br />  
/*  
  
Requires: PHP5 and Info-Zip (http://www.info-zip.org/)  
Usage: `php xrns_merge.php /path/to/file1.xrns /path/to/file2.xrns file3.xrns`  
Will output file to current working directory  
  
*/  
  
// ----------------------------------------------------------------------------  
// Variables  
// ----------------------------------------------------------------------------  
  
$schema = '/Applications/Renoise.app/Contents/Resources/Schemas/RenoiseSong4.xsd';  
$tmp_dir = 'D:/Temp';  
$unzip_xrns = 'unzip @_SRC_@ -d @_DST_@';  
$zip_xrns = 'zip -r @_DST_@ .';  
  
// ----------------------------------------------------------------------------  
// Functions  
// ----------------------------------------------------------------------------  
  
function simplexml_append(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->appendChild($node2);  
}  
  
function simplexml_insert_before(SimpleXMLElement $parent, SimpleXMLElement $new_child, SimpleXMLElement $before){  
 $node1 = dom_import_simplexml($parent);  
 $dom_sxe = dom_import_simplexml($new_child);  
 $node2 = $node1->ownerDocument->importNode($dom_sxe, true);  
 $node1->insertBefore($node2, dom_import_simplexml($before));  
}  
  
function UnzipAllFiles($zipFile, $zipDir) {  
 global $unzip_xrns;  
 $unzipCmd = $unzip_xrns;  
 $unzipCmd = str_replace('@_SRC_@', $zipFile, $unzipCmd);  
 $unzipCmd = str_replace('@_DST_@', $zipDir, $unzipCmd);  
 $res = -1; // any nonzero value  
 $UnusedArrayResult = array();  
 $UnusedStringResult = exec($unzipCmd, $UnusedArrayResult, $res);  
 return ($res == 0);  
}  
  
function ZipAllFiles($zipFile, $zipDir) {  
 global $zip_xrns;  
  
 $tmp = getcwd(); // Change dir to get relative path  
 chdir($zipDir);  
  
 $zipCmd = $zip_xrns;  
 $zipCmd = str_replace('@_DST_@', $zipFile, $zipCmd);  
 $res = -1; // any nonzero value  
 $UnusedArrayResult = array();  
 $UnusedStringResult = exec($zipCmd, $UnusedArrayResult, $res);  
  
 chdir($tmp); // Back to previous working directory  
  
 if (!copy($zipDir . $zipFile, $tmp . '/' . $zipFile)) {  
 die("Error: Failed to copy $zipFile...\n");  
 }  
  
 return ($res == 0);  
}  
  
function dircopy($srcdir, $dstdir, $verbose = false) {  
 $num = 0;  
 if(!is_dir($dstdir)) mkdir($dstdir);  
 if($curdir = opendir($srcdir)) {  
 while($file = readdir($curdir)) {  
 if($file != '.' && $file != '..') {  
 $srcfile = $srcdir . '/' . $file;  
 $dstfile = $dstdir . '/' . $file;  
 if(is_file($srcfile)) {  
 if(is_file($dstfile)) $ow = filemtime($srcfile) - filemtime($dstfile); else $ow = 1;  
 if($ow > 0) {  
 if($verbose) echo "Copying '$srcfile' to '$dstfile'...";  
 if(copy($srcfile, $dstfile)) {  
 touch($dstfile, filemtime($srcfile)); $num++;  
 if($verbose) echo "OK\n";  
 }  
 else die("Error: File '$srcfile' could not be copied.\n");  
 }  
 }  
 else if(is_dir($srcfile)) {  
 $num += dircopy($srcfile, $dstfile, $verbose);  
 }  
 }  
 }  
 closedir($curdir);  
 }  
 return $num;  
}  
  
function obliterate_directory($dirname, $only_empty = false) {  
 if (!is_dir($dirname)) return false;  
 $dscan = array(realpath($dirname));  
 $darr = array();  
 while (!empty($dscan)) {  
 $dcur = array_pop($dscan);  
 $darr[] = $dcur;  
 if ($d = opendir($dcur)) {  
 while ($f=readdir($d)) {  
 if ($f == '.' || $f == '..') continue;  
 $f=$dcur.'/'.$f;  
 if (is_dir($f)) $dscan[] = $f;  
 else unlink($f);  
 }  
 closedir($d);  
 }  
 }  
 $i_until = ($only_empty)? 1 : 0;  
 for ($i=count($darr)-1; $i>=$i_until; $i--) {  
 if (!rmdir($darr[$i])) die("Error: There was a problem deleting a temporary file.\n");  
 }  
 return (($only_empty)? (count(scandir)<=2) : (!is_dir($dirname)));  
}  
  
  
// ----------------------------------------------------------------------------  
// Check User Input  
// ----------------------------------------------------------------------------  
  
if ($argc != 4) die("Error: This script expects 3 parameters.");  
if (!file_exists($argv[1])) die("Error: The file $argv[1] was not found.");  
if (!file_exists($argv[2])) die("Error: The file $argv[2] was not found.");  
  
$song1 = $argv[1];  
$song2 = $argv[2];  
$song3 = $argv[3];  
  
// ----------------------------------------------------------------------------  
// Unpack  
// ----------------------------------------------------------------------------  
  
echo "Working...\n";  
  
// Create a unique directory  
$unzip1 = $tmp_dir . '/xrns_merge_' . md5(uniqid(mt_rand(), true)) . '_Track01/';  
$unzip2 = $tmp_dir . '/xrns_merge_' . md5(uniqid(mt_rand(), true)) . '_Track02/';  
  
// Unzip song1  
$result = UnzipAllFiles($song1, $unzip1);  
if($result === FALSE) {  
 die("Error: There was a problem unziping the first file.\n");  
}  
  
// Unzip song2  
$result = UnzipAllFiles($song2, $unzip2);  
if($result === FALSE) {  
 die("Error: There was a problem unziping the second file.\n");  
}  
  
// Load XML  
$sx1 = simplexml_load_file($unzip1 . 'Song.xml');  
$sx2 = simplexml_load_file($unzip2 . 'Song.xml');  
  
// ----------------------------------------------------------------------------  
// Append the total tracks and sends in song2 as empty offsets to song1,  
// prepend the total tracks and sends in song1 as empty offsets to song2.  
// ----------------------------------------------------------------------------  
  
$offset = count($sx1->Tracks->SequencerTrack);  
$offset2 = count($sx2->Tracks->SequencerTrack);  
for ($i = 0; $i < $offset; ++$i) {  
 foreach ($sx2->PatternPool->Patterns->Pattern as $x) {  
 simplexml_insert_before($x->Tracks,  
 simplexml_load_string('<patterntrack type="PatternTrack"></patterntrack>'),  
 $x->Tracks->PatternTrack[0]);  
 }  
}  
for ($i = 0; $i < $offset2; ++$i) {  
 foreach ($sx1->PatternPool->Patterns->Pattern as $x) {  
 simplexml_insert_before($x->Tracks,  
 simplexml_load_string('<patterntrack type="PatternTrack"></patterntrack>'),  
 $x->Tracks->PatternMasterTrack);  
 }  
}  
  
$offset = count($sx1->Tracks->SequencerSendTrack);  
$offset2 = count($sx2->Tracks->SequencerSendTrack);  
for ($i = 0; $i < $offset; ++$i) {  
 foreach ($sx2->PatternPool->Patterns->Pattern as $x) {  
 if (count($x->Tracks->PatternSendTrack)){  
 simplexml_insert_before($x->Tracks,  
 simplexml_load_string('<patternsendtrack type="PatternSendTrack"></patternsendtrack>'),  
 $x->Tracks->PatternSendTrack[0]);  
 }   
 else {  
 simplexml_append($x->Tracks,  
 simplexml_load_string('<patternsendtrack type="PatternSendTrack"></patternsendtrack>'));  
 }  
  
 }  
}  
for ($i = 0; $i < $offset2; ++$i) {  
 foreach ($sx1->PatternPool->Patterns->Pattern as $x) {  
 simplexml_append($x->Tracks,  
 simplexml_load_string('<patternsendtrack type="PatternSendTrack"></patternsendtrack>'));  
 }  
}  
  
// ----------------------------------------------------------------------------  
// Patterns in song2 are shifted the total patterns in song1, adjust the  
// PatternSequence pointer offsets accordingly.  
// ----------------------------------------------------------------------------  
  
$offset = count($sx1->PatternPool->Patterns->Pattern);  
for ($i = 0; $i < count($sx2->PatternSequence->PatternSequence->Pattern); ++$i) {  
 $shift = str_pad(($sx2->PatternSequence->PatternSequence->Pattern[$i] + $offset), 2, '0', STR_PAD_LEFT);  
 $sx2->PatternSequence->PatternSequence->Pattern[$i] = $shift;  
}  
  
// ----------------------------------------------------------------------------  
// Instruments and VstiAutomationDevices in song2 are shifted the total  
// instruments in song1, adjust the pointer offsets accordingly.  
// Copy the instruments with these same offsets  
// ----------------------------------------------------------------------------  
  
$offset = count($sx1->Instruments->Instrument);  
  
// Instruments  
foreach ($sx2->PatternPool->Patterns->Pattern as $p) {  
 foreach ($p->Tracks->PatternTrack as $x) {  
 if ($x->Lines->Line) {  
 foreach ($x->Lines->Line as $y) {  
 if ($y->NoteColumns->NoteColumn) {  
 foreach ($y->NoteColumns->NoteColumn as $z) {  
 if ($z->Instrument) {  
 list($instr) = sscanf($z->Instrument, '%x');  
 $z->Instrument = sprintf("%02X", $instr + $offset);  
 }  
 }  
 }  
 }  
 }  
 }  
}  
  
// VstiAutomationDevices  
foreach ($sx2->Tracks->SequencerTrack as $x) {  
 if ($x->FilterDevices->Devices->VstiAutomationDevice) {  
 foreach ($x->FilterDevices->Devices->VstiAutomationDevice as $y) {  
 list($instr) = sscanf($y->LinkedInstrument, '%x');  
 $y->LinkedInstrument = sprintf("%02X", $instr + $offset);  
 }  
 }  
}  
  
// SampleData directory  
if (is_dir($unzip2 . '/SampleData/')) {  
 foreach(new DirectoryIterator($unzip2 . '/SampleData/') as $file) {  
  
 if ($file == '.' || $file == '..') continue; // Skip these files  
  
 // Source  
 $source = $unzip2 . '/SampleData/' . $file;  
  
 // Destination  
 $dest = preg_replace('/(^\D+)(\d+)/', '$1@_REPLACE_@', $file);  
 preg_match('/\d+/', $file, $matches);  
 $shift = str_pad(($matches[0] + $offset), 2, '0', STR_PAD_LEFT);  
 $dest = str_replace('@_REPLACE_@', $shift, $dest);  
 $dest = $unzip1 . '/SampleData/' . $dest;  
  
 // Copy  
 dircopy($source, $dest);  
  
 }  
}  
  
  
// ----------------------------------------------------------------------------  
// Send devices in song2 are shifted the total send tracks in song1, adjust  
// the pointer offsets accordingly.  
// ----------------------------------------------------------------------------  
  
$offset = count($sx1->Tracks->SequencerSendTrack);  
foreach ($sx2->Tracks->SequencerTrack as $x) {  
 if ($x->FilterDevices->Devices->SendDevice) {  
 foreach ($x->FilterDevices->Devices->SendDevice as $y) {  
 $shift = str_pad(($y->DestSendTrack->Value + $offset), 2, '0', STR_PAD_LEFT);  
 $y->DestSendTrack->Value = $shift;  
 }  
 }  
}  
  
  
// ----------------------------------------------------------------------------  
// Append all the instruments, tracks, sends, patterns, and sequences from  
// song2 into song1.  
// ----------------------------------------------------------------------------  
  
// Instruments  
foreach ($sx2->Instruments->Instrument as $x) {  
 simplexml_append($sx1->Instruments, $x);  
}  
  
// Tracks  
foreach ($sx2->Tracks->SequencerTrack as $x) {  
 simplexml_insert_before($sx1->Tracks, $x, $sx1->Tracks->SequencerMasterTrack);  
}  
  
// SendTracks  
foreach ($sx2->Tracks->SequencerSendTrack as $x) {  
 simplexml_append($sx1->Tracks, $x);  
}  
  
// Patterns  
foreach ($sx2->PatternPool->Patterns->Pattern as $x) {  
 simplexml_append($sx1->PatternPool->Patterns, $x);  
}  
  
// PatternSequence  
foreach ($sx2->PatternSequence->PatternSequence->Pattern as $x) {  
 simplexml_append($sx1->PatternSequence->PatternSequence, $x);  
}  
  
  
// ----------------------------------------------------------------------------  
// Validate  
// ----------------------------------------------------------------------------  
  
if (file_exists($schema)) {  
 $dd = new DOMDocument;  
 $dd->loadXML($sx1->asXML());  
 if(!$dd->schemaValidate($schema)) {  
 die("Error: XML is invalid!\n");  
  
 }  
}  
  
// ----------------------------------------------------------------------------  
// Replace Song.xml  
// ----------------------------------------------------------------------------  
  
unlink($unzip1 . 'Song.xml') or die("Error: There was a problem deleting a file.\n");  
file_put_contents($unzip1 . 'Song.xml', $sx1->asXML());  
  
// Zip song  
$result = ZipAllFiles($song3, $unzip1);  
if($result === FALSE) {  
 die("Error: There was a problem zipping the final file.\n");  
}  
  
// ----------------------------------------------------------------------------  
// Remove temp directories  
// ----------------------------------------------------------------------------  
  
obliterate_directory($unzip1);  
obliterate_directory($unzip2);  
  
echo "Done...\n";  
  
?>  
  

Holy crap, thanks Taktik. It works!

ok
now i can start rewrite it to c#
to taktik
if i will create lib (dll) file can you add it to renoise as new feature?

thank god we have such excellent people using renoise! this will be a fantastic addition to renoise, and as far as i know, not possible in any other music software… yes? no? :)

cheers
klaus

as i know FL have feature that you can write track by internet) with other guys)

Belkin, I recieved your PM but will not be adding you to MSN/ICQ. My free timne is mine, not everyone elses. Look for me in the Renoise #irc if you need me, or post your questions in this thread. I can’t help you with your C# because I run off OS X. In fact, f**** C# :) Renoise is multi-platform, I don’t see your .dll making it in the final release. How would I run it on OS X?

With that said, you should go ahead and write an app. It would be cool for Windows users. The algorithm is outlined in the working code/shell script above.

Good luck!

PS: A question fo team Renoise / Bantai: Is my script tool worthy?

i will create dll file.
and small application for test it after this public it here.
all members who wnat can test it after this i can send dll file to ya with sources and class descriptions.

after this all bugs we can fix here)

Probably not. But it worked for me. But, yeah, it warrants a few weeks of wait time.

The probs (missing shifts/edits) I can think of are:

  • Offsets in VSTi alias indices
    (RenoiseSong->Instruments->Instrument->VSTiProperties->AutoAssignedTrack, when not -1)

  • Offsets in VST autoassign track refs (RenoiseSong->Instruments->Instrument->VSTiProperties->AliasInstrumentIndex, when not -1)

  • Offsets in the MIDI CC Device (like the VST automation Device)

  • The patterns support up to FF instruments only

Ever tried Mono? With Mono you can run C# and other .NET-apps on Linux, Solaris, Mac OS X, Windows, and Unix. Even if Microsoft refuse to make their implementation work with any other platform than windows, fortunately other people care to do such an implementation. Mono rocks, and C# is indeed a pretty nice language (and that is said by a person that usually doesn’t have much good to say about things that comes out of Microsoft ;)) …

Well, it’s available in PHP5, now…

But yeah, no real criticisms about C# other than if it’s so great, then where’s the C# merge application people? :wink:

I’m not going to write an app that was easy to make in PHP using a language I don’t know. The fact that it took me a few days and a review by Taktik, who was able to read/fix my code, speaks for itself. Obviously not everyone rocks the command prompt with PHP5 in their bin dir, but when you need to get things done, and you don’t have a Perl developer around, PHP gets it done too.

It’s all good.

Btw, should I solve/add the remaining missing stuff (as noted in my previous reply) or will you? Would be great to get it finished to add it to the tools download section…

Yeah, you are right. I have a bit more time now. Give me the weekend and I will add those fixes/safeties.

What does this mean?

  
if ((count($sx1->Instruments->Instrument) + count($sx2->Instruments->Instrument)) > 255) die("Unsuported"); // ?  
  

Or does it literally mean that if I have more than 255 instruments per pattern, it can’t be done?