Yeah more ram cost...it sucks. I never thought about the possible race condition before but now that I think about it, we might be able to dodge it without another buffer. I am surprised I overlooked this, and it basically negates the entire design I had going. The buffer would not be circular in this case, but the main program is always writing ahead of the song player so it can't interfere with it. This avoids the secondary buffer and explicit lock and there is simply no race condition even if it gets interrupted in the middle of writing to the buffer. Every frame the song player sets a variable of how many bytes it used(if this is not reset to 0 by the main program, the song player will not advance next frame, until it is 0..this is essentially a lock, because the main program could be in the middle of shuffling things to the front), and the main program moves all unused bytes in the buffer to the beginning of the buffer before resetting this to 0; this will be less cycle cost than circular buffer producer->consumer copy logic anyway. Mainly the song player will just stall if there is not enough bytes by not processing, and avoid other problems possible with some other solutions. It is probably obvious, but if the song player gets off track by even a byte, a crash is extremely likely if it interprets incorrectly a byte as a Note On, and another one as an invalid patch; at least very messed up music and incorrect looping if not a crash.
Another thing that could potentially help efficiency by a large chunk is to simply use an alternative format as opposed to the large and fully featured MIDI stream. Essentially just compress MIDI events into a smaller stream, which is designed to only handle things that Uzebox games use, and nothing else. This would save on buffer size perhaps by 66%, and help performance in general by a similar measure. Now that I saw that, and I think it is true, it must be done.
I am just thinking this out right here in this message so bear with me. If you look at the current MIDI player, every events is actually ~3 bytes because of the way MIDI is(which can handle a much larger superset of what we do on Uzebox). These are the things that are supported in the current flash MIDI player:
- *Note On event(needs channel, note, and volume)
*Song End marker
*Loop Start marker
*Loop End marker
*Program Change(requires channel and patch number)
**(the following 4 I believe can be eliminated)
*Volume Control(needs channel and volume...this can be built into the note on events, combined with Program Changes if very intricate control is needed..)
*Expression(I think this is nearly dubious for our purposes and should be designed into multiple patches using Program Change if actually required..)
*Tremolo(I feel the same about this, I don't think anyone ever needed this in the MIDI stream for a game)
*Tremolo Rate(..I almost suspect some of this support was for the MIDI_IN keyboard feature that never really got used)
Code: Select all
0b*1*AABBBBB:
Program Change: AA = channel, BBBBB = patch(sacrifice ability for 5 channel songs...use 5th channel for effects)
0b*0*AAABBBB:
if(AAA < 5)
Note On: AAA = channel(0-4), BBBB = volume(sacrifice control of volume to 15 unit intervals..4 bit volume is NES equivalent, never noticed an Uzebox game that required full 8 bits)
else if(AAA < 6)
Marker:
if(BBBB == 0)
Song End marker
else if(BBBB == 1)
Loop Start marker
else if(BBBB == 2)
Loop End marker
else if(BBBB == 3)
else//(AAA == 7)
if(BBBB == 8)//we could support some more small commands as well...
...or we could define an extension, where the next byte is more information, allowing full 8 bit volume, 5 channel triggering etc...probably not much more twisted than the existing MIDI decoder and at worst taking the same space
...
The 2nd trade-there are only 16 volume levels from silence to full volume for note events, but patches will still sound exactly the same just that there is more granularity of the volumes they start at..in my experience most Uzebox games play notes of all the same volume anyway and let the patches handle it..and 16 levels is pretty good anyway(again doesn't affect patch commands at all).
The 3rd trade-now this might sound crazy, but the way the bitpacking works out(someone prove me wrong here, I would be grateful), there simply needs to be another bit to have all the above and also be able to specify Note On events for 5 channels. This does not seem a bad sacrifice anyway. Any 5 channel game should use 0-4 for instruments and the 5th channel for effects. Just a few music only demos required all 5, and this solution is not aimed at absorbing inefficiency for the sake of being used for demos.
Er..there is a 4th trade that midiconv output will need to run through an additional step for compression. I will simply make a conversion program to facilitate this and do the extra step;don't need another project messing with midiconv for now.
The last concept is attacking the huge inefficiency that is the delta time read after every single event. Instead of that, the first byte of every frame can be the number of commands that happen this frame. This has the added convenience that we can quickly calculate if we have all the bytes we need for this frame without decoding the data bytes(we know they are all 1 byte commands unless extensions are implemented). After the number of commands has been processed, the last byte specifies how many frames of "rest" happen before the next event, which is just 1 byte instead of multiple. This adds the restriction that there cannot be longer than 255 frames without an event(never seen this in an Uzebox game). If you needed that for some ridiculous reason, one could do a silent Note On followed by more rest to manually overcome this "limitation". I think this is massive, though a bit of a pain to implement. I guess when I was so optimistic about tiny buffers, I was thinking this direction but then going lazy and trying to do it easier.
Anyway maybe this is a bit much of a rant for this thread and it can be moved to a separate one if necessary. I do think this is possible, not that it is necessarily easy to do all that, but just running it through my head we are looking closer to those small buffers I originally quoted. I have been procrastinating on this for several years now Seeing as you saved the forums with your bot, I think the least I can do is to finally do this which I needed for several games anyway. Any concerns on those changes at all?
Edit-damn I overlooked the note part of Note On...not sure how to pack that into 1 byte after all..so 2 bytes for note on events!