It is the 1746th of March 2020 (aka the 10th of December 2024)
You are 18.97.9.171,
pleased to meet you!
mailto:blog-at-heyrick-dot-eu
MIDI revisited
I said a few days ago that I had provided a test MIDI module to David Feugey. He got back in touch to say that it did not work. A bit perplexed, I went to discover why. I was running a perfectly good count down loop, basically:
timeout = (some complicated calculation logic)
do
{
timeout--;
} while (timeout > 0);
The problem was, the compiler was spotting that the outcome of this is that timeout is zero. So it just set timeout to be zero and skipped doing the loop. Very clever, Norcroft, very clever.
This did give me the chance, and the impetous, to dig a little bit deeper. Looking at a waveform of sending a never-ending cycle of MIDI events, and zooming right in, I can see a little bit of bounce and a slight curve, and there's a small amount of ripple. That said, it's not a bad signal at the MIDI plug end. I've seen worse.
I think this signal may be inverted. That isn't important for what we are doing.
The next step was to set a stable waveform on the oscilloscope. This was achieved by the trick of calling SYS "MIDI_TxNoteOn", 75, 75 repeatedly, with a few WAIT statements afterwards, and then asking the scope to trigger on a rising edge (as the MIDI interface is silent when no data is passing). The WAIT was not for the benefit of the interface, it was to waste time so the signal would go quiet and the trace would go off the end of the screen. At this point, the automatic triggering would become active. It ran in a loop fast enough to give a stable display.
The serial data should be: 0 1 0 0 1 0 0 0 0 1 | 0 0 1 0 0 1 0 1 1 1 | 0 0 1 0 0 1 0 1 1 1
If I recall correctly, this trace was 10µs/div with the voltage at 2V/div. This means the output voltage is just under 5V, as would be expected if it was derived from the USB supply.
I then switched the timebase to half as fast (20µs/div) and it held steady:
So, I added another TxNoteOn command:
The delay between the notes is because the USB system (that does not run async) returns to the MIDI module, which exits. BASIC then interprets the next line, collects the data, and calls the MIDI module, which parses the data, builds a four byte MIDI packet, then 'writes' it to the file that represents the MIDI out port, and RISC OS does whatever it needs to do to actually put the data on the USB wire, which is then received by the chip in the USB-MIDI interface, decoded, and sent out as serial data. Doing all of this takes about 2/3 of a square, or around 13µs.
This process repeated until we had stepped down enough to be able to visualise seven note-on events. There was no problem with five notes (20 bytes in, 15 bytes out). With six, the sixth one one would flicker suggesting that frequently it was dropped (24 bytes in, 18 bytes out). It shows up here because the Mavica has a slow shutter. Seven notes (28 bytes in, 21 bytes out) plain didn't work.
I am guessing that perhaps the serial UART has a buffer of around 16 bytes, and the reason that sometimes the sixth note played depended upon the relative speed by which the data emptied from the serial output buffer related to the input data. Remember, the MIDI port is running at 31250bps, while the USB port is running considerably faster (like around 300× faster!).
Essentially, these cheap MIDI interfaces are mostly suitable for permitting a person to play notes on a keyboard and getting a response appear on a computer. Anything more challenging is, well, challenging for them. I guess I should count myself lucky that my interface can cope with five notes (that's just a chord and two melody notes).
It was also clear that the built in delay was doing nothing at all, which is when I disassembled the code and had a laugh at seeing the compiler optimised out the entire loop. So I recoded the loop logic to something simple to defeat the optimisation, and then worked on what to do.
RISC OS, with a heritage in the mid '80s, runs its system events timed to a centisecond clock. A lot of it is also not thread-safe in terms of re-entrancy. This can make all sorts of annoying conditions as you can get RISC OS to run callbacks as often as every two centiseconds (if you need centisecond, hook into EventV for the 100Hz ticker), but you cannot do much here. Instead, if you want to do file operations and the like, you should ask RISC OS for a slightly different type of callback that happens when the superstack is empty which means RISC OS is threaded out and there's nothing "busy" (user mode programs don't count). The caveat? There is no specific timer upon which these callbacks can occur. "When RISC OS isn't busy" is as good as you get.
Specifically, there is no high resolution timer (a third party "HALTimer" module provides one) that can call your program when it is safe to "do stuff". As RISC OS was born from Arthur, and that had a lot in common with being an ARM port of the BBC Master MOS, there is a lot of stuff in RISC OS that is not re-entrant. This is why my OLED project utterly failed when I tried to use it as a display for an MP3 player. The OLED worked, of course. However the IIC code in the kernel disables interrupts while IIC data is being sent (re-entrancy again), and sending data for 128×64 pixels (about a kilobyte), even at 400Hz, took longer than the sound buffer had data buffered for. The sound buffer couldn't be refilled in time (interrupts disabled) so the music just stopped playing. Annoying as hell, but that's how things are at the moment.
Anyway, since there is no high resolution callback, and a bunch of reentrancy problems, the solution that I picked was to busy-wait while in a null loop. It's a horrible horrible way to waste time, and I would welcome better options, but I'm not sure an option exists that plays well with RISC OS's inherent cruftiness.
Anyway... MIDI itself takes about 320µs to send a byte of data, which means it should take 1.92ms to send two notes. Therefore, we should attempt to delay by just slightly longer than it would take to send the data. The data will go out immediately on the USB side, it's the slow serial side that we are waiting for.
The logic is to take a counter value and multiply it by the number of MIDI bytes, plus one. With experimentation on a RaspberryPi, it seems as if 19,500 is a good value - but as this is a software timing loop, different computers (Iyonix, Beagle xM, Panda...) will require different values. I may, in time, look to see if I can busy-wait on HALtimer to waste a millisecond per command, which should be sufficient. But for now, it's this method.
With this in mind, here is a test program:
With the delay value set to 20,000 the notes all appeared on the oscilloscope. This photo shows eleven notes, as the timebase for more was harder to photograph.
So... How do do this?
First of all - you will need the MIDI module alpha release 6 (or later). Link just below. This shows up in the module information with "; dev#6" after the copyright.
When you first start the MIDI module, it will default to "fast mode". This will work with decent (read: expensive) devices that connect USB directly into an internal microcontroller or MPU without the need to pass through actual MIDI serial. For instance, my Yamaha PSR e-333 copes with complex data in fast mode just fine.
If you have a USB to old-style MIDI interface, you may need to apply the delay. To do this, simply call:
SYS "MIDI_Options", -1, 19500
to set a delay value of 19,500. Don't worry about what the value actually means. This is, again, for a 700MHz Pi. You will need to adjust it for faster/slower machines. In addition, complex music with many notes to play at a time may suffer from jitter. If this is the case, you'll need to fiddle around to find a value appropriate for your system that permits the music to play with minimal (preferably no) jitter, and no loss of data (missing notes). Please inform me of your machine and the value that you choose, so I can put it into the user guide as a suggested value.
You can check the current delay value programmatically with:
SYS "MIDI_Options", -1, -1 TO , delay%
PRINT delay%
The delay value, if set, will also show up with the command *MIDIUSBInfo:
Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.
Zerosquare, 26th November 2014, 01:30
If you want to make sure an empty loop is not optimized away by the C compiler, use the "volatile" keyword in the loop counter declaration, e.g. "volatile int timeout = ..."
What's missing in your USB-MIDI interface is flow control, i.e. a way for the computer to know when the output buffer is almost full, and to stop sending data until there is more space available. I see several possiblities: - your interface supports this, but for some reason it's not working right in RiscOS - your interface supports this, but in a non-standard way, so it requires a custom driver - your interface doesn't support this at all and it's a piece of junk. Get a better one, or build one yourself using the serial port :)
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.