VOGONS


First post, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

I've recently been trying to find the source of a problem playing MIDI data from the Cakewalk 3.0 sequencer for DOS running in DOSBox using the MPU-401 intelligent mode. The problem is that at some point during playing the program gets stuck in a loop with a hanging note still sounding. These hangs occur at various times, but I believe it has something to do with the amount of MIDI data, because it happens right away for relatively small songs, and later on for larger ones.

Using the debugger to log commands sent to the MPU-401, I can see that the hangs are coincident with the first 0xFC command, at which point that command is sent repeatedly and rapidly, over and over. The online MIDI specs I've been able to find describe this as a "Stop" or "End" command, and DOSBox is not doing anything special with it other than generating an ACK. Unable to think of anything else to try, I modified MPU401_WriteCommand so it does not generate an ACK for the command, and the hangs no longer occur. I think Cakewalk is waiting for the queue to be empty, and keeps sending the command until it is empty, but ACKing the command prevents it from ever being empty when the status is checked, in a feedback loop.

Why Cakewalk is using the command at some point during playback is unclear to me, but I know with certainty that the program does not have the problem with a real MPU-401 interface, so I'm guessing what I've described is a minor compatibility issue. However, the issue may very well not have an impact on any game, so it might not be addressed in future versions of DOSBox; but perhaps the information will still be useful to others interested in experimenting with Cakewalk running in modified versions of DOSBox.

Reply 1 of 15, by Srecko

User metadata
Rank Member
Rank
Member

Hi,
Thanks for debugging this.

When app sends 0xFC it usually means that it tells mpu401 to stop requesting midi data. After that (after all internal tracks of mpu401 that were active were shut off this way), mpu401 will issue back 0xFC as a signal to the app that it's finished.

What happens probably is that app sends stop command at the same time (on commant port!) which is something like a byte smaller than 0x30.
After that mpu401 will queue also the 0xFE (ack), So maybe here is something messed up. So either you are right and mpu401 shouldn't que ack, or the order of those two bytes is messed up. Unfortunately I can't debug because I couldn't find this cakewalk sequencer... 😠

What is the byte that program sends before 0xFC (that's a timing delay meaning when mpu401 will process a midi message)? If app sends "stop" before 0xFC is processed, it could cause a problem. You could try to empty the buffer at

case  0x4:	/* Stop */ 

(put mpu.queue_used=0; there)

Reply 2 of 15, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

It is not very straightforward how the problem comes about, so it may be the case that preventing the ACK merely works around the problem, and isn't the source of it. However, I've played a fair amount of MIDI data through it now with no problems, so it is at least an effective workaround.

The 0xFC command isn't directly written to the command port by Cakewalk, it is written to the data port, and gets placed in the "conductor" buffer. When Cakewalk reads an 0xF9 (command request) from the data port, DOSBox's MPU-401 implementation calls MPU401_WriteCommand with the 0xFC from the conductor buffer, which in turn generates the ACK.

Reply 3 of 15, by Srecko

User metadata
Rank Member
Rank
Member

That makes it clearer 😀
So for example 0xFC sent after a data track request means that mpu401 should stop requesting data for that track. Now it seems that same applies to conductor, but that isn't implemented, so it breaks with the cakewalk. I will try to come up with a patch.

In the meantime, something that should fix the behavior; putting this just at the beginning of UpdateConductor(void):

if (mpu.condbuf.value[1]==0xfc) {mpu.state.conductor=false;return;}

Still, strange that removing ack from WriteCommand helps, because the ack is already prevented when command is run from conductor (that "block_ack" boolean does it, you can see in QueueByte() function). But good that it works for you.

Reply 4 of 15, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

I implemented the changes you describe, but the hangs still occur with them.

I see what you are saying about block_ack, so it appears the change I made is having a more complicated effect than I thought. I put a "case 0xfc: return;" in MPU401_WriteCommand, which prevents QueueByte from being called, but I can now see that it also leaves block_ack=true to block the *next* queued byte (blocks anything, not just ACKs), which is an 0xF9 according to log messages I put in. Heh, I can kind of see why that breaks the loop, but I really don't think that's the right way to fix the problem. 😅

Edit: The blocked 0xF9 (command request) comes from the EOIHandler

Reply 5 of 15, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

Your fix actually does work, but you had one little mistake: it should be checking value[0]==0xfc, not value[1] ... sorry for not noticing sooner.

My change was inadvertently blocking the subsequent 0xF9; yours prevents it entirely by avoiding the critical line:

mpu.state.req_mask|=(1<<9);

Reply 6 of 15, by Srecko

User metadata
Rank Member
Rank
Member

Yes, just noticed that it should've been [0] 😀
That change should be safe to put in dosbox CVS as well.

I also added better handling of the "finished" message (0xFC returned on data port), which now also checks if conductor is done or not. Attached.

Attachments

  • Filename
    fixcakewalk.diff
    File size
    840 Bytes
    Downloads
    281 downloads
    File comment
    "0xFC" bugfix
    File license
    Fair use/fair dealing exception

Reply 8 of 15, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

Srecko,

There might be a bit of an issue with your patch. I have a long-ish MIDI song file that changes tempo frequently (i.e. it has a tempo map in it). I noticed that although the tempos are used on the first play through in Cakewalk, they are ignored on subsequent plays. I'm guessing that Cakewalk uses the conductor for tempo changes, and it seems that once it has been shut down by your patch because of an 0xFC, it isn't being re-activated unless I restart DOSBox.

The change that I made before (putting "case 0xfc: return;" in MPU401_WriteCommand), although it only worked by dumb luck, doesn't have the same issue. That's probably because it works around Cakewalk getting stuck in a loop by blocking a single 0xF9, but it doesn't shut down the conductor.

I didn't do any research into this, but maybe Cakewalk is issuing a conductor command at the start of playback that should re-activate it? However, if Cakewalk causes the conductor to get shut down with an 0xFC, there's no guarantee that it will be the next program using the MPU401, so maybe there is a standard "start of playback" signal that should re-activate the conductor? Anyway, now that I've explained what is happening, I'm sure you'll have a good idea of what is going on in the code.

Reply 9 of 15, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

I looked into it a bit, and Cakewalk is indeed writing a lot of 0xE0 (set tempo) to the data port. The logic of it seems to be that once an 0xFC is in the conductor buffer, the only way to write something else in there is when a command request sets cond_req=true, but command requests don't happen when an 0xFC is in the buffer, so it's stuck. It might not be the right way to fix it, but removing the 0xFC works:

static void UpdateConductor(void) {
if (mpu.condbuf.value[0]==0xfc) {
+ mpu.condbuf.value[0]=0;
mpu.state.conductor=false;
...

Cakewalk writes a 0xB8 (clear play counters) to the command port when starting playback, and similarly zeroing the conductor buffer when processing that command works as well, which might be a better choice. Either way it's probably not a good fix, but I think I've narrowed down where the problem is.

Reply 10 of 15, by Srecko

User metadata
Rank Member
Rank
Member

Yes, as counductor couldn't be turned off before this patch, it was working on repeated playback.

Currently only way to re-enable it is "clear play counters" and "clear play map" (or 0xff reset and whole sequence of commands). Obviously cakewalk isn't doing any of this between two playbacks. I will look at device documentation, maybe there is something mentioning behavior of conductor on/off in more detail (and check if I implemented something similar already for the input patch).

Reply 11 of 15, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author
Srecko wrote:

Currently only way to re-enable it is "clear play counters" and "clear play map" (or 0xff reset and whole sequence of commands). Obviously cakewalk isn't doing any of this between two playbacks.

I think you missed the bit I wrote about how Cakewalk *is* doing a 0xB8 (clear play counters) at the start of every playback; but it does not re-activate the conductor. I tried putting "mpu.condbuf.value[0]=0;" in the 0xB8 command processing code, and that works, but again it might not be the best way to resolve the problem. I wonder if 0xB8 is a standard thing done by programs when starting playback with MPU-401, or if it's just something Cakewalk happens to do.

Reply 12 of 15, by Srecko

User metadata
Rank Member
Rank
Member

Yes, right 😀 Your fix seems good, although it can go already in UpdateConductor when it is 0xfc, that will remove the problematic condition next time conductor is used.

static void UpdateConductor(void) {
if (mpu.condbuf.value[0]==0xfc) {
+ mpu.condbuf.value[0]==0;
mpu.state.conductor=false;

I will check if we still have to enable conductor for next playback, even if 0xb8 isn't issued.

Reply 14 of 15, by Srecko

User metadata
Rank Member
Rank
Member

I tried putting "mpu.condbuf.value[0]=0;" in the 0xB8 command processing code, and that works,

Yeah, sorry 😀 Got confused by the above and I probably didn't even look carefully enough at your previous code block...