I notice that the MIDI Class uses the 0.9 MusicXML schema. If I wanted to replace this with the latest version (2.0), would I need to edit the MIDI class, or XRNS2MIDI?
OK. I thought that might be the case. Just starting to get my head around MIDI/MUSIC XML. Bit of a learning curve ahead of me for the foreseeable future.
If you’re interested. the MIDI XML 2.0 schema is here:
Haha, yeah, I haven’t had time to actually test it. I’ve just been looking at the code. Will do so on OS X soon, currently at PC dominated work.
Your code had me writing a “bug-report” to a plugin developer to for Jedit. The correspondence ended up in me upgrading to Jedit pre10 because static variables was not supported by the PHPParser plug-in for the 4.2 version, which I’ve been using for a couple of years now. I’ve not seen a lot of static variables out there yet. Static methods, yes, but static variables… very new stuff in PHP.
Cool, I use MDB2 which has singleton built in and I never actually implemented (or looked at it) myself. Also, it’s a method…
Anyway, it’s nice to finally run into a static variable and take it personally. I learned something new. Bottom of page 213 and 214 in PHP Cookbook 2nd edition.
Ok, snuck in some OS X time, everything seems to work great, but there are some optimization issues.
set_time_limit(30) is too optimistic. My 1.8 GHZ G5 fails to convert Kaneel - Sink Sink Sink under 30 seconds. Botb’s entry from BB5 is taking over 15 minutes. (doesn’t seem to want to finish) I’m running debugging code, i.e. i’ve inserted an echo command in getMsg(), so I know it’s not hanging. I know my original script is not as clean or thought out, but it ripped through the songs much faster, never more than 30 seconds, so something needs improving.
The first thing I looked at was getMsg() function. So in case you didn’t know, in historical PHP, the order of processing in the following lines of code (all identical) is from slowest to fastest:
I do not think this is the significant bottleneck though. In fact, because as all cases have variables that must be parsed, it falls under “Useless Optimizations” in the link I will give in the second part. But from personal experience, if you have “a sentence” vs ‘a sentence’ and neither have any variables running through a loop, the second one will run significantly faster.
PHP OO, Inside a method:
1. Incrementing a local variable in a method is the fastest. Nearly the same as calling a local variable in a function.
2. Incrementing a global variable is 2 times slow than a local var.
3. Incrementing a object property (eg. $this->prop++) is 3 times slower than a local variable.
4. Incrementing an undefined local variable is 9-10 times slower than a pre-initialized one.
5. Just declaring a global variable without using it in a function also slows things down (by about the same amount as incrementing a local var). PHP probably does a check to see if the global exists.
6. Method invocation appears to be independent of the number of methods defined in the class because I added 10 more methods to the test class (before and after the test method) with no change in performance.
7. Methods in derived classes run faster than ones defined in the base class.
8. A function call with one parameter and an empty function body takes about the same time as doing 7-8 $localvar++ operations. A similar method call is of course about 15 $localvar++ operations.
Again, could be useless, I just remember it solving a problem several years ago. But, as you stated, PHP5 may have solved it on it’s end by now.
You must have a really fast computer or something. It took 15 minutes for me to do BOTB’s track, if you could run it and tell me how long it takes on your computer it would be appreciated. I’ll keep an eye out for script updates and try to optimize tonight it when I have more free time. For now, the bottleneck is between foreach($midiList as $miditrack) { … } There is also the chance that the version of PHP i use at home is not the latest (5.2.3) and the speed is due to a bug fixed in an update.
function getPreviousGlobal($pos, $param) {
if (isset($this->globals[$param][$pos])) {
return new Globals($pos, $this->globals[$param][$pos]);
}
else if ($pos > 0) {
$haystack = $this->globals[$param];
$haystack[$pos] = 'needle';
$prev_key = 0;
foreach ($haystack as $key => $val) {
if ($val == 'needle') {
if (isset($this->globals[$param][$prev_key])) {
return new Globals($prev_key, $this->globals[$param][$prev_key]);
}
break;
}
$prev_key = $key;
}
}
return false;
}
Old algorithm:
Kaneel BB5 =~ 147588 ms
Botb BB5 =~ 986415 ms
New algorithm:
Kaneel BB5 =~ 5579 ms
Botb =~ 111049 ms
Both algorithms fail to saveMidFile with Demosong - ThePath2005. I get a lot of Notice: Trying to get property of non-object when converting Demosong - Rotu, which takes =~ 215194 ms with the new algorithm, then fails saveMidFile as well.
I had ksort($haystack); between $haystack[$pos] = ‘needle’ and $prev_key = 0; at one point but this was redundant. Assuming the keys are always numeric, $haystack[$pos] = ‘needle’ will position the needle / finder node (?) in the correct place at all times. If you put ksort() back, there will be negligible difference. The speed comes mostly from eliminating everything that isn’t a possibility and reducing the size of the array to only the desired elements, placed in the correct numeric order.
If you think the needle will always be near the end, you could technically reverse the $hastack and return the $next_key instead of the $prev_key. I was doing print_r on the data and I never saw a big array, so I don’t think it will make a difference. In a $haystack of 10 items (I never saw one bigger than 4 items in my tests) 8 iterations vs 2 will not matter. It was mostly the 5000 checks against nothing for every function call in Botb’s case that was slowing everything down.
Anyway, always room for improvement. I spend several hours on it so, barring any misunderstanding as to what the code actually does and introducing bugs, I’m happy to have spent time on that nerdy problem.
32 : second loop
4752 Tempo 9375000
4752
32 : t = 4752
32 : time = 3192
32 : dt = 1560
32 : time = 4752
1560
33 : second loop
4776 Tempo 781250
4776
33 : t = 4776
33 : time = 4752
33 : dt = 24
33 : time = 4776
24
34 : second loop
3240 Tempo 1562500
3240
34 : t = 3240
34 : time = 4776
34 : dt = -1536
34 : time = 3240
-1536
[... script freezes here ...]
The problem is that a negative $dt is passed to _writeVarLen() which freezes the script. This negative number happens because the order of the timestamps is wrong.
This can be fixed in the midi class by replacing the line with if ($dt >= 0) $midStr .= _writeVarLen($dt);
This will prevent freezing, but will skip info and screw up the final midi file. You will need to verify and sort the timestamps before converting to midi.
We should probably blame IT-Alien for writing such a buggy song then i guess… the timestamp and note-off protocols in MIDI seem to be various and outlawed following the recent discussion between Bantai & Taktik about this matter.
I have tested this script during this evening with many of my .rns files and imported the .mid:s into Sonar 6.2, Reason 3.04, Fruity Loops 7, Sibelius 5, Acid 6 and Orion Platinum without finding anything to report! I have tested only with the speeds I use most (F106 and F103), but in all softwares the notes are perfectly placed out, the velocities seem to be right, and the instruments are mapped out right too. Even though the script doesn’t seem to allow for the very useful Fx values in volume/pan column, it’s no big deal as the OFF:s work perfect.
Wow, Bantai, you write history now. This superb script makes a lot of people, me included, very happy: thank you! I even noticed that Wikipedia had your achievement covered a couple of days ago. So I guess the snowball is rolling… this is the Big News. Can we spread the word around or should we wait a bit longer?
The CVS best practices document you linked says “Check-in Often” and I agree, based on experience.
If your computer breaks or your house catches fire, we lose the code
CVS doesn’t have to be working code, CVS has nothing to do with releases.
The concept of branching in CVS sucks, it is the driving reason for the existence of Perforce/Subversion/Anything else, we should not use branching as defined by CVS.
In my opinion CVS is supposed to be “busy” until a release is decided. Upon that decision, the developers collectively clean their messes, someone checks out the head revision based on that cleanup, and releases that, independent of any versions in CVS, by packaging a zip file.
Our project is not big enough to justify the overhead of branching, especially using CVS. But it is not small enough to justify losing code to negligence?
Again, this is juts my opinion. I’m open to alternative methodologies.
But without even having it seen yet there is something it is surely missing and which would me very happy …
the thing is, if you program drums in renoise you end up with all those long notes (because you don’t set note-offs for drums) in an exported midi (no matter if using the energyXT-method or your script) which looks very bulky in a sequencer (expecially if you used several note-colums) and is infact bad because editing such a drumtrack is hell (If you want two basedrums instead of one you can’t just add one but have to “resize” or delete the existing one first).
On the other hand, if you do a drumtrack in a sequencer you usually just paint 16th notes in there for easy editing later on and because all drum-related vsti’s that I know dont care for notelengths (except for open hi-hats maybe).
So what I propose is some kind of way to output fixed notelengths to the midi for “selected tracks” (lets say 16ths or something). With selected I mean something like having a trackname contain something like “drums” or “###” (some simple string) and the scripts converts such named tracks to midi with fixed notelengths. This would be a good way to make sure batch-processing is still possible.
Anyway, I currently think drum-tracks would benefit from this, maybe other types of tracks too.
I don’t think it’s very intrusive because nothing would really have to change. for users not interested in the script nothing is different, if you want to use the script you just have to rename some tracks, that’s it. and this way you can avoid to scan the tracks and don’t have to process user input etc.
and ofcouse cutting notes if in one column is nice, but a different example is the last basedrum in front of a 4-pattern break, you will get this loooong note which atleast looks awkward and again requires alot of work to “fix”.
All I say is it would be great if you could think about adding something like this, let’ see if I get some +1’s for this at all.
PS:Saw this screenshot too late : Right, but again, when I do drumtracks I usually give every part it’s own notecolumn for an easy overview. And drumtracks with no note-offs and 4 or more columns look really, really horrible
Renoise note columns are purposely circumvented, otherwise the conversion makes no sense, where in every mainstream MIDI sequencer one “row” is one “instrument”.
As long as your drumkit is a single multi-timbral Renoise instrument, no matter what colunms you put the notes using Renoise, it will be grouped in one MIDI track using this conversion script.
That being said, please file a Feature request at sourceforge. The idea is a decent one.
…like, very very very long. I used Kaneel’s BB5 track as my test. I tried putting the references back on $len = _readVarLen($binStr, &$p); and it went faster by 80 seconds for Kaneel’s track. But still, not significant by any means.
As a side note, objects are now passed by reference by default in PHP5, but as far as I know &$p is not defunct, as $p in the context of the code is just a plain variable, and can be either a copy or a reference, depending on the use of &, so &$p is fine.
Anyway, I don’t understand how it could have gotten slower. Specifically since the parts of the code it is slow on are calling midi.class.php which has not really changed? I’m at a loss…