Rick's b.log - 2014/11/22 |
|
It is the 18th of December 2024 You are 18.119.165.105, pleased to meet you! |
|
mailto:
blog -at- heyrick -dot- eu
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.
The next step was to set a stable waveform on the oscilloscope. This was achieved by the trick of calling
I then switched the timebase to half as fast (20µs/div) and it held steady:
So, I added another TxNoteOn command:
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.
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.
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.
With this in mind, here is a test program:
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 "
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.
You can check the current delay value programmatically with:
The delay value, if set, will also show up with the command
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.
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
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).
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.
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.
; dev#6
" after the copyright.
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.
SYS "MIDI_Options", -1, -1 TO , delay%
PRINT delay%
*MIDIUSBInfo
:
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 :)
© 2014 Rick Murray |
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. |