Xrns_ogg.php - I Need Php Literate Testers

Beatslaughter is hard at work for his newest front end script GUI for windows (thee cheers for that guy!), and one of the key concepts that requires a proof of concept is:

- rename “zip” folder to “utils” (eg: ogg.exe could be placed there too, for an ogg sample encoder script)

So in the spirit of shitting out PHP scripts I wrote one that rips through a xrns files replacing all the flac, wav, and aiff (i doubt aiff is even possible, but just in case…) samples and convets them to OGG.

Works on OS X. I don’t have access to a windows box for at least another day so if someone could look at this before then and provide feedback, it would be greatly appreciated.

It requires oggenc (http://www.rarewares.org/ogg-oggenc.php) and assumes it to be in your path, if it’s not then change line 32. ($path_to_oggenc). My first impressions is that oggenc is called oggenc2.exe… so I’m hoping it works the same way? You’ll need to creatively jimmy it into line 32.

EDIT: Scroll down...  

So, the above was tested on windows and is working.


This script is now hosted on my webpage:


It’s also currently available in the XRNS Script Front end GUI for Windows located deep in this thread until until a final release is available.

Adapted the script also to translate xrni files, it might require some extra neating but it works.

<?php <br />  
Requires: PHP5, Info-Zip (http://www.info-zip.org/), oggenc (http://www.rarewares.org/ogg-oggenc.php)  
and optionally flac (http://flac.sourceforge.net/)  
Usage: `php xrns_ogg.php /path/to/file1.xrns file2.xrns [1|2|3|...|10]`  
Will output compressed file to current working directory  
Public Domain, last modified August 3rd, 2007  
Coded by Dac Chartrand of http://www.trotch.com/  
Special thanks for collaborative efforts to:  
Taktik / Bantai of http://www.renoise.com/  
Beatslaughter of http://www.beatslaughter.de/  
// ----------------------------------------------------------------------------  
// Variables  
// ----------------------------------------------------------------------------  
$tmp_dir = '/temp';  
// ----------------------------------------------------------------------------  
// Functions  
// ----------------------------------------------------------------------------  
function ogg_files($source, $quality = 3) {  
 $path_to_oggenc = 'oggenc'; // oggenc assumed to be in path  
 $path_to_flac = 'flac'; // flac assumed to be in path   
 $array = scandir($source);  
 foreach ($array as $file) {   
 $fullpath = $source . '/' . $file;  
 switch (preg_match('/(\.(.*)$)/i', $file, $matches)) {  
 case (strtolower($matches[2]) == 'flac') :   
 $command = $path_to_flac . ' -d --delete-input-file "'.$fullpath.'"';  
 $res = -1; // any nonzero value  
 $UnusedArrayResult = array();  
 $UnusedStringResult = exec($command, $UnusedArrayResult, $res);  
 if ($res != 0) echo "Warning: flac return_val was $res, there was a problem decompressing flac $file\n";   
 else $fullpath = str_replace(".flac", ".wav", $fullpath);  
 case (strtolower($matches[2]) == 'wav' || (strtolower($matches[2]) == 'aif') || (strtolower($matches[2]) == 'aiff')):  
 $command = $path_to_oggenc . " --quality $quality \"$fullpath\" ";   
 $res = -1; // any nonzero value  
 $UnusedArrayResult = array();  
 $UnusedStringResult = exec($command, $UnusedArrayResult, $res);   
 if ($res != 0) echo "Warning: oggenc return_val was $res, there was a problem compressing $file\n";   
 else unlink($fullpath);   
function UnzipAllFiles($zipFile, $zipDir) {  
 $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  
 $UnusedArrayResult = array();  
 $UnusedStringResult = exec($unzipCmd, $UnusedArrayResult, $res);  
 if ($res != 0) echo "Warning: UnzipAllFiles() return_val was $res\n";   
 return ($res == 0 || $res == 1); // http://www.info-zip.org/FAQ.html#error-codes   
function ZipAllFiles($zipFile, $zipDir) {  
 $zip_xrns = 'zip -r "@_DST_@" .'; // info-zip assumed to be in path  
 $tmp = getcwd();  
 chdir($zipDir); // Change dir to get relative path  
 $zipCmd = $zip_xrns;  
 $zipCmd = str_replace('@_DST_@', $zipFile, $zipCmd);  
 $res = -1; // any nonzero value  
 $UnusedArrayResult = array();  
 $UnusedStringResult = exec($zipCmd, $UnusedArrayResult, $res);  
 if ($res != 0) echo "Warning: ZipAllFiles() return_val was $res\n";   
 chdir($tmp); // Back to previous working directory  
 if (!copy($zipDir . $zipFile, $tmp . '/' . $zipFile)) {  
 die("Error: Failed to copy $zipFile...\n");  
 return ($res == 0); // http://www.info-zip.org/FAQ.html#error-codes  
function obliterate_directory($dirname, $only_empty = false) {  
 if (!is_dir($dirname)) return false;  
 if (isset($_ENV['OS']) && strripos($_ENV['OS'], "windows", 0) !== FALSE) {  
 // Windows patch for buggy perimssions on some machines  
 $command = 'cmd /C "rmdir /S /Q "'.str_replace('//', '\\', $dirname).'\\""';  
 $wsh = new COM("WScript.Shell");  
 $wsh->Run($command, 7, false);  
 $wsh = null;  
 return true;  
 else {  
 $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;  
 if (is_dir($f)) $dscan[] = $f;  
 else unlink($f);  
 $i_until = ($only_empty)? 1 : 0;  
 for ($i=count($darr)-1; $i>=$i_until; $i--) {  
 if (!rmdir($darr[$i])) echo ("Warning: There was a problem deleting a temporary file in $dirname\n");  
 return (($only_empty)? (count(scandir)<=2) : (!is_dir($dirname)));  
function get_temp_dir() {  
 // Try to get from environment variable  
 if (!empty($_ENV['TMP'])) {  
 return realpath($_ENV['TMP']);  
 else if (!empty($_ENV['TMPDIR'])) {  
 return realpath($_ENV['TMPDIR']);  
 else if (!empty($_ENV['TEMP'])) {  
 return realpath($_ENV['TEMP']);  
 else {  
 // Detect by creating a temporary file  
 $temp_file = tempnam(md5(uniqid(mt_rand(), true)), '');  
 if ($temp_file) {  
 $temp_dir = realpath(dirname($temp_file));  
 return $temp_dir;  
 else {  
 return false;  
// ----------------------------------------------------------------------------  
// 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 < 3) {  
 echo "Error: $argv[0] expects at least 2 parameters.\n";  
 echo "Usage: `php $argv[0] /path/to/file1.xrns file2.xrns [1|2|3|...|10]`\n";  
 echo "$argv[0] will output ogg compresed file (file2.xrns) to current working directory.\n";  
if (!file_exists($argv[1])) die("Error: The file $argv[1] was not found.\n");  
if (!(preg_match('/(\.zip$|\.xrns$|\.xrni$)/i', $argv[2]))) {  
 die("Error: The filename $argv[2] is invalid, use .xrns / .xrni (or .zip)\n");  
$song1 = $argv[1];  
$song2 = $argv[2];  
// Quality  
if(ctype_digit($argv[3]) && $argv[3] > 0 && $argv[3] <= 10) $quality = $argv[3];  
else $quality = 3; // default  
// ----------------------------------------------------------------------------  
// 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 unique directory  
if ((preg_match('/(\.xrni$)/i', $argv[2]))) {  
 $unzip1 = $tmp_dir . '/xrns_ogg_' . md5(uniqid(mt_rand(), true)) . '_Ins01/';  
} else {  
 $unzip1 = $tmp_dir . '/xrns_ogg_' . md5(uniqid(mt_rand(), true)) . '_Track01/';  
// Unzip song1  
$result = UnzipAllFiles($song1, $unzip1);  
if($result === FALSE) {  
 echo "Error: There was a problem unzipping the first file.\n";  
 echo "Error code: $result\n";  
 echo "Going to process samples\n";  
// ----------------------------------------------------------------------------  
// Convert samples to .ogg  
// ----------------------------------------------------------------------------  
// SampleData directory  
if (is_dir($unzip1 . '/SampleData/')) {  
 foreach(new DirectoryIterator($unzip1 . '/SampleData/') as $file) {   
 if ($file == '.' || $file == '..') continue; // Skip these files   
 if ((preg_match('/(\.xrni$)/i', $argv[2]))) {  
 $source = $unzip1 . 'SampleData';  
 } else {  
 $source = $unzip1 . '/SampleData/' . $file;  
 if (is_dir($source)) {  
 ogg_files($source, $quality);   
// ----------------------------------------------------------------------------  
// Zip song  
// ----------------------------------------------------------------------------  
// Zip song  
$result = ZipAllFiles($song2, $unzip1);  
if($result === FALSE) {  
 echo "Error: There was a problem zipping the final file.\n";  
 echo "Error code: $result\n";  
// ----------------------------------------------------------------------------  
// Remove temp directories  
// ----------------------------------------------------------------------------  
echo "---------------------------------------\n";  
echo "$argv[0] is done!\n";  
echo date("D M j G:i:s T Y\n");  
echo "---------------------------------------\n";  

Wow, awesome. I’ll put your changes on the page in the upcoming days.

Scripts page updated:


I made some minor changes to the code because it was redundantly looping through the .xrni SampleData directory for every file there. I also gave you a credit in the source code and on the scripts page Vv.


I’ve added this version to the windows frontend.


just one small thing from my experience from the past :

an ogg file has a fileheader of around 4kbyte, so ogg-compressing samples under 4k (small drums, waveforms etc.) result in a bigger instrument. maybe it would be useful to add a check for that.


Currently in CVS along with another bugfix.

  • skip files smaller than 4096 bytes,
  • fixed: switch() was broken
  • fixed: extension of filenames with dots was not beign detected

oh, thanx, that was fast. the 4k is some number out of my head, I will possibly give you a more accurate one if it should be necessary. the funny thing is that different encoders have different headers, ogg files done with the ogg-for-audition plugin have smaller headers than files done with oggdrop. anyway, thanx again.

okay, did some small testing, the header is somewhere between 4060 and 4120 bytes, so the 4k-limit is probably a good thing. if someone really needs those extra bytes (as I did for evoke tinycompetition) they can still ogg or flac the samples manually. thanx conner.

@conner: Might be better to make it an option then?

actually flac files outputted by renoise have a header of roughly 1,4k too, so the 4k might not be really right. And I am not sure anymore if this is worth the trouble.

The main reason for this remark of mine was that my entry for the tinycompo at evoke has a lot of looped waveform-samples (one cycle at 22khz mono) and when I really had to squeeze the samples to fit it to 64k I had some samples which where so small that ogging that sample did increase it’s size considerably (but again from like 2k to 5k or something. which did matter at that point, though.).

That was a very special case however and given the fact that 4096 bytes of flac at 50% compression gives you about 18 ms (!) of sampletime at 44.1/mono (if I calculated right) it’s obvious that this is a very, very special case which almost never happens in a “normal” renoise file and if I ever do another “tiny”-music I will do oggcompression by hand anyway because some samples can take 56kbit-compression and some need 128kbit.

So my advice is to kick this out again, it’s obviously pretty useless and just makes your script unnecessarily complex and vulnerable to bugs. Sorry.

It’s merely 1 line of code, it doesn’t add any complexity, if you want to tweak it, check Line 49 in xrns_ogg.php

if (filesize($fullpath) <= 4096) continue;   

In all of my tests, I encountered zero files that were smaller 4096 bytes. If the header of an OGG file is anywhere near 4Kb as explained, then there’s no point in converting the file. I think this is a reasonable feature to force on users. Most will never see such a small file, and for those who need this miniscule tweak, this is a very good default.

okay then, if it’s only one line leave it in. cheers !

Even though the used flac.exe is a bit outdated, that’s not the problem. The converted file is fine, it seems to be a problem in the script or a wrong return value from flac.exe.

I don’t have time to look at this.

The power of open source! I.E. can’t someone else do it?


Ok, so I just did a test on my OS X machine, everything works fine.

History lesson:

*) Originally, oggenc was the only command line utility used for conversion
*) Monsieur The Plug Expert had a few flac samples that turned to werid static using only oggenc
*) flac was added in the conversion, flac then passes itself off to oggenc

All I am seeing is:

Sample00 (Rendered Selection).flac: ERROR: bits per sample is 32, must be 4-24
Warning: flac return_val was 1, there was a problem decompressing flac Sample00 (Rendered Selection).flac

But it doesn’t matter, if flac fails, it passes the file off to oggenc anyway. Oggenc does the conversion fine. I see a converted file on my desktop, everying is fine.

Did you guys actually look at your screens and, like, read them?

PS: Nice track!

I pitty your non-verbose windows machines! :slight_smile:

That being said, the warnings should be more clear, but they are just that, “warnings”.

Errors, die() triggers, etc, that’s would have been a different story.

Conclusion: That’s a feature, not a bug!