USB MIDI?
|
So mom has been in hospital and I may have some form of bronchitis. As you can imagine, this week sucked. So getting this done took a lot longer than expected when my mind wasn’t on much of anything at all for the last… <counts on fingers, runs out of fingers> days. Anyway. This iteration is here now. Actually, I lie, it is here: http://www.heyrick.co.uk/random/usbmidi_002_alpha7.zip (23KiB) Here’s what’s worth knowing (copy-pasted from the Versions file):
R0 = 0 - Reset internal state of device bit 1 - Clear RX buffer bit 2 - Clear TX buffer bit 4 - Clear current error ('B' and 'D' yes, 'X' and '/' no) bit 30- Place Real Time messages into RX buffer bit 31- Don't do special actions on Real Time messages (other bits not supported or not relevant)
MIDI_IgnoreTiming R0 = 0 (receive), 1 (ignore) R1 = "RICK" (&4B434952) R2 = Which MIDI port to apply this to [will currently fault ports > 0] MIDI_Init R0 = Bits 30 (RealTime->buffer) or 31 (RealTime ignore) set R1 = "RICK" (&4B434952) R2 = Which MIDI port to apply this to [will currently fault ports > 0] [when used in this way, all other bits of R0 are ignored] MIDI_FastClock Clock type applies to the module as a whole. It is not possible to have one port timing by MIDI clock and another by ms/cs ticker.
SYS "MIDI_Options", -1, -1 TO options%, delay% options% = options% OR 1 SYS "MIDI_Options", options%, delay% This change is NOT persistive. If the clock type is altered (MIDI_FastClock with R0 >= 0), then you would need to do this again to select the Ticker mode of operation instead of the default fake millisecond clock. In other words: SYS "MIDI_FastClock", 0 (or do nothing, it's the default) will use the MIDI beat clock timing SYS "MIDI_FastClock", 1 (or any positive number) will use the fake millisecond timing derived from ticker SYS "MIDI_Options" with R0 bit 1 set will use the centisecond ticker [you don't technically need to call MIDI_FastClock first in order to choose the ticker clock; just call MIDI_Options] Edit: Just in case anybody asks, &4C475859 is “YXGL” (for Yamaha XGLite). The procedure should work with other extensions to General MIDI that uses bank selection to permit a greater range of available voices… |
|
Rick, I did get hold of a half decent usb keyboard (M-Audio Keystation) -good pdf for it too. I jotted down some notes to get the miditestY program playing ball.
It’s been a while, I think the last change is semantic, but allowed things to work. Should have emailed you really, but just wanted to acknowledge you for your midi work also. I dont know when I’ll get a chance to try your new stuff. |
|
Not what I had planned to do today, but life’s like that… It is a small change. The naff-device option (R1 to MIDI_Options SWI) is now either zero or non-zero, and the delay that is used is fixed at 320 microseconds per byte transmitted, plus an extra 320µs to allow for delays in the adaptor. This value, 320µs, is how long it takes for a byte to be sent via serial MIDI. http://www.heyrick.co.uk/random/usbmidi_003_alpha8.zip (23KiB) I decided upon this course of action after looking at a bit of Linux code that appears to imply a bunch of hardwired delays for some USB devices; and it seems that there is simply no obvious way for a USB MIDI device to signal “hold off a mo, I’m busy”. This, I guess, is the forward march of technology. One step forward, one step back… The method used relies upon HAL calls, so if you managed to get the MIDI module working on something that wasn’t running RISC OS 5… then this delay won’t be available to you. It will throw an error when you try to set it via MIDI_Options SWI in this case. The “delay factor” reported in the device information is no longer meaningful. What you see is how many ticks per second your hardware timer runs at. For RPCEmu (and I presume RiscPC real), this is 2000000 (2MHz). For the Pi, it is 1000000 (1MHz). Other devices? Something along these lines… The delay itself is fixed at 320µs per byte (plus one) and it is calculated on the fly as necessary so nothing to report. ;-) Oh, and miditestY has not (yet!) been fixed as per Ronald’s description above… |
|
There isn’t. There isn’t with serial MIDI either. |
|
With a UART (or hardware serial chip) you have the ability to know when a byte has been sent so you can then send the next one; plus there is no issue of overrunning capacity as the serial hardware is running at the serial speed. With USB, we have a buffer of indeterminate size (testing implies about 20 bytes) and a link that is running many times faster than the serial speed and no apparent way of detecting how much space is available in the buffer or anything like that. |
|
Isn’t flow control performed by the MIDI device? I would have thought IN endpoint data flow would be limited by serial speed and OUT endpoint data flow would be NAK’d by the MIDI device until room was available in its buffer. I suppose you are saying that the MIDI device just loses OUT data if it is overrun – odd. |
|
My MIDI module takes data sent to it from Maestro and formats it in MIDI packets then sends it to the file handle for DevicesFS (etc) to process.
|
|
An endpoint for flow control doesn’t help unless the device has a big buffer. Thats why USB serial devices do their own flowcontrol so I don’t think that is the problem. Limiting the output to 20bytes per millisec because the serial buffer is 20 bytes doesn’t sound a good solution either as you need to be able to send 32bytes per millisec for it to work properly. If you wait to see if the usb buffer is empty before sending more then it’s too late you will have missed the next millisec slot. You would expect the USB MIDI device to have at least a 32byte USB buffer – it probably double buffers with 2 32byte buffers to get transfers every millisec. At least that’s how microchip cpus with USB work. You say notes were missed. Were they missed or could they have been delayed a millisec? |
|
I’m not. I’m asking the machine to delay 320uS for each byte transferred plus one to account for time taken within the adaptor. In technical terms, this would be 0.00128 seconds per three byte “NoteOn”, which might seem slow, but remember it would physically require 0.00096 seconds to put on the wire as serial MIDI isn’t very fast, plus however much time the controller chip (some sort of MCU?) needs to actually convert USB packets to raw MIDI. This isn’t too say it is definitive. I have not yet tested this at all as my keyboard accepts USB directly and hasn’t (as far as I’m aware) lost data in normal use. I’ll need to hook up the old Roland keyboard to the serial adaptor and see how it behaves. But… where did I put it? Hmmm! And where is the power lead adaptor I made? Hmmm!
That is what is claimed in USBInfo (http://heyrick.co.uk/random/pics/midi_dongle.jpeg), but practical experience suggests either the buffer is smaller or the assembly buffer (where outgoing serial data is assembled) is the problem. I suspect the latter due to data just being lost.
David has removed his broken version of FollowYou, so I’ll need to see if I still have a copy. If basically played with the bass line mostly missing. You can, alternatively, see the data here (http://www.heyrick.co.uk/blog/index.php?diary=20141122) as spikes on the scope. Throwing data at the device would output five commands, sometimes six. Adding a delay meant…well, it is all explained in the blog post. ;-) I rather suspect this is but a workaround for some extremely shoddy hardware, so we perhaps shouldn’t be surprised at needing to do weird things for it. <sigh> |
|
As I see it the device has to have the capability to store 1 packet (32 bytes) otherwise it can’t work and I can’t see how the serial end can be overrun either. I’d much rather believe the problem is with RISC OS USB. I don’t see that delaying the byte input does anything. Data is only sent over USB every 1ms (USB1 device) it is not sent immediately. So if you start with an empty buffer and put 1 byte in it a transfer of 1 byte is queued with the USB controller the transfer of this data happens in the background. In the mean time you buffer more bytes – you may for example buffer 32 bytes in the millisec. When the first one byte transfer finishes a new transfer is queued made up of all the bytes now in the buffer which in my example is 32. While that is being processed by the controller you are filling the buffer again and so on. I don’t know how MIDI data is formatted but if it doesn’t contain any timing information I would be inclined to buffer data separately in 32byte chunks and have a multiple of 32byte USB buffer ie only insert 32byte chunks into the USB buffer. |
|
32 bytes would be 8 MIDI messages. That’s too many. Delaying is intended to slow down how fast data is sent out. It works out just a little over 1ms per NoteOn command so rather than dumping a bunch on to the device at once, the delay will spread it out over several transmissions, just a little slower than real serial MIDI speed. I should stress – I do not observe these issues with my Yamaha keyboard, so I’m inclined to believe the device is the problem and not RISC OS (and a quick look at Google will come up with enough material to support that!). For reference: every serial MIDI command is converted into a four byte USB MIDI command. There is no “running status” (a shortcut where multiple NoteOn commands could be one NoteOn followed only by a sequence of note values) and System messages cannot interrupt normal data transfer. Everything is converted to a four byte value where the first byte tells the message type and which MIDI port this is, the second byte is the MIDI command byte and the third and fourth bytes are the parameters (or zero if not used). Basically it is zero padded MIDI data with a prefix byte. |
|
Looks like it might be even more fun trying to get sensible data going the other way… https://audiodestrukt.wordpress.com/2012/11/18/inexpensive-usb-midi-interfaces/ |
|
A few years ago I wrote the embedded code for a commercial 4 channel USB-serial MIDI converter. I believe there are now several devices from that manufacturer that use or used my code. I recall the USB bulk endpoint as being 64 bytes (16 MIDI commands other than SysEx), and each buffer was several hundred bytes long. MIDI should be capable of handling a chord on several instruments simultaneously. Don’t get confused by the rate at which serial MIDI operates. It’s irrelevant. Buffering should take care of everything. If the converter you’re using drops MIDI commands, throw it away. Don’t compromise your code by downgrading it to handle a defective-by-design device. |
|
That’s because you “did it properly”. ;-)
Indeed. The “about five” notes that I’m seeing is a chord plus two notes of melody on a single instrument. Which is about sufficient to replay your daughter’s piano recital…if she’s six and not a prodigy…
…provided the buffering is worth a damn, that is. If not, this is when the rate that serial MIDI runs becomes important.
I would love to be able to say “your converter is fetid poo, hit it with a large hammer and then chuck it in the bin”; but unfortunately we run into the “works with Windows” problem. I don’t have a USB analyser so I don’t know if Windows throttles output to these devices or what, but if something appears to work on Windows but fails on RISC OS, the average person isn’t going to think that their hardware is crap and couldn’t sensibly buffer, they will instead think RISC OS is crap. It seems that there are a number of non-compliant devices that appear unable to correctly buffer MIDI data. So much so that FreeBSD has taken the step of implementing a USB “quirk” for the device where it will only send one command per millisecond slice, which is effectively what my module will do for such a device (as the calculated delay works out to be slightly over a millisecond for a NoteOn). http://fxr.watson.org/fxr/source/dev/usb/quirk/usb_quirk.c?v=FREEBSD9#L452
This is not the default behaviour. To my mind, anything that doesn’t behave like my Yamaha keyboard (an unexciting PSR e-333 with USB input) is broken. As it uses USB MIDI directly, it does it correctly. SysEx and all. However it is necessary to have a compatibility option that can be enabled for these crappy Chinese pieces of rubbish because telling a user “your hardware sucks in fundamental ways” is never going to be the popular option. Perhaps the only reason this isn’t more of an issue is because our only MIDI software at the moment is Maestro and, well, who can forget this utter train wreck (and that’s not even MIDI!)? |
|
Did anything happen with the MelIDI open sourcing mentioned earlier in the thread? I ask because I saw a question about MIDI sequencing software on another RISC OS forum. |
|
That would be great. And perhaps some 32bit port of Anthem too Even if the best is probably MidiWorks |
|
I have a part developed multi-track clips on up to 16 track recorder and player written in Joe Taylor’s AppBasic It used to reliably play and record 3 or 4 tracks but rest not so reliable. If someone would like to test and see if fills a gap, it is available. Stephen Foord |
|
Hi Stephen. I’ll test it with pleasure (expect a little delay) |
|
Hi, shoot me a copy….and if it doesn’t work with USB MIDI, I can figure out why. ;-) heyrick1973 |
|
Update of the MIDI module. Entirely recoded the USB device detection. |
|
Code review (half-assed excuse to not go out and mow something) got some bugs fixed. And I even had time to mow for an hour before sundown while listening to Chihiro Onitsuka… https://www.heyrick.co.uk/random/usbmidi_005_alpha10.zip Here’s the blurb: 0.05 2017/04/18 Now outputs the correct event numbers for receiving data (0) and error occurred (1). Now correctly cancels any outstanding callback on module exit (though the chance of this situation happening is pretty slim). Removed everything to do with delaying data for crappy hardware interfaces. The requested delay is kicked over to the HAL to do with the high resolution timer(s). Fixed a typo that stopped MIDI_TxByte SWI and *MIDIUSBSend from working (was discarding events due to not recognising the MIDI command because the command part was being extracted twice). Numerous tweaks and tidyups to the code. Just to clarify the third one down – the original code tried to time and calibrate a busy-wait loop of stacking and unstacking a bunch of registers a few tens of thousands of times to cause a delay, but while it worked, the delay was very variable so it sort-of-worked at best. Then, rummaging around the HAL documentation, I discover a call that can delay for much shorter periods than possible with the 100Hz clock. The sort of periods I am looking for. So I just use the HAL method and took out the now-unused code. Yeah, it sucks to delay the entire machine for microseconds at a time while sending data…but that’s the price you pay if you choose to purchase a USB to MIDI interface that costs less than a Happy Meal… |