USB MIDI
Dave Higton (1515) 3526 posts |
I’ve recently met someone who’s struggling to resurrect several A7000s to use as MIDI controllers. I suggested that he junk them and use Raspberry Pis instead, but he claimed that USB MIDI has timing that’s too inaccurate for good quality. It’s an interesting point. What is the timing resolution of USB MIDI in RISC OS? The inherent limitation would be 1ms for full speed or 125us for high speed, both of which ought to be good enough IMHO. But what timer drives it in RISC OS? Is it the centisecond clock? I can well believe that 10ms is too coarse. If there is additional latency, perhaps unpredictable, then it’s really not going to be good enough for serious users. |
Holger Palmroth (487) 115 posts |
I wonder if someone more competent than me could rig up a MIDI interface for the PI. The UART can obviously be tricked to generate the “odd” MIDI bitrate: https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=19908 Then it’s “just” the RMModule that need to be rewritten for that interface. Of course my idea is bare any acutal knowledge needed for these tasks. |
jan de boer (472) 78 posts |
This is the document you need: |
Rick Murray (539) 13840 posts |
1cs, and because it uses file I/O to communicate, it needs to set up a CallBack which adds an undefined (though tiny) amount of time. This is unavoidable due to how the module talks to the USB subsystem. That said, a lot of stuff on RISC OS derives from TickerV. Edit: I should add, if you’re unlucky enough to need to use the “crap interface” mode, the module will use the HAL timer to stall the entire system during MIDI transfers so as not to swamp the interface with too much data (as these crap interfaces can only cope with ~5 normal commands before they start losing data and I can see nothing in the USBMIDI spec that relates to flow control). I add this purely for completeness.
How does this work in reality though, given that MIDI supports rather more channels (and voices) then the native audio system?
There is something of a dearth of available MIDI enabled software for RISC OS 5. I test with what I have… That’s one of the reasons I was hoping for the open sourcing of MelIDI, but that seems to have fallen by the wayside. I don’t support SysEx at the moment either. Nothing needed it so nothing to test how it worked relative to other commands, so… |
Colin (478) 2433 posts |
CallBacks can have long delays – I’ve measured 25cs. |
Dave Higton (1515) 3526 posts |
Even 10ms quantisation in time can result in a run sounding a bit ragged, but 250ms is utterly unacceptable. USB MIDI needs a scheduler that can’t be pre-empted. Maybe it could use some of the techniques from isochronous mode? |
Colin (478) 2433 posts |
If the usbmidi device uses isochonous transfers you could get an interrupt every ms for usb 1 or 125us for usb2. If bulk transfers are used these are low priority transfers and are only sent when space is available on the wire after the other types of transfers are sent. Bulk transfers are also subject to error correction which may cause jitter because of retries. You can’t get isochronous accuracy with bulk transfers – you could occasionally miss usb clock ticks So in a perfect world you get input from USB at 1ms or 125us granularity. You first get to process that data in the upcallv which called from the USB interrupt. This is subject to jitter caused by the list of functions on the upcall vector changing. The problem now is can you do what you need to do with the data from the interrupt context if not you need to buffer the data down to a callback context and treat the data as if it were saved in a file. If the usbdata is time stamped then the buffer down to the callback affects latency not accuracy. If the data is time stamped by the device then that is good. If not you need to timestamp the data as you read it from usb in the interrupt context and that is subject to jitter. Serial input would have similar problems if you can’t do what you want from an interrupt context. |
Dave Higton (1515) 3526 posts |
USB MIDI uses bulk. The only reason I mentioned isochronous transfer mode is that it has some kind of scheduler. That’s the only connection – and it’s not a very good one, because isochronous is regular but MIDI isn’t. |
Colin (478) 2433 posts |
I suspect there is none. MIDI is a real time protocol, isn’t it, so delaying data won’t work. It’s like audio in that sense. |
Dave Higton (1515) 3526 posts |
Anyway, MIDI output needs to occur reliably very close to the scheduled times. MIDI input for recording can be timestamped. Input for live performance is another matter, but that really means you’d be using the RISC OS machine as a synthesiser. Does anyone want to do that when there are plenty of synths around, from cheap to very good? |
Dave Higton (1515) 3526 posts |
I can confirm that MIDI has no flow control whatsoever. |
Rick Murray (539) 13840 posts |
Colin, Dave Higton wrote a ridiculously long time ago:
Add to that, that I never got the “data in buffer” event stuff running and I talk to the MIDI device as if it was a file. So it’s all higher level than interrupts and upcalls. All of those considered, 1cs resolution is the best it’ll do. If anybody wants anything better, they’ll need to write their own driver and beat there head off the same walls that I have. I’ll give you a simple one to consider that is purely an API issue. In the buffer are two NoteOn events. These are three bytes each – channel, note, intensity. So your buffer is: The original MIDI API is a weird hodge-podge of high and low level MIDI stuff, with some other stuff thrown in too. Heck, I’m surprised it doesn’t have a method of opening and playing MIDI files for itself. |
Rick Murray (539) 13840 posts |
There’s an inherent delay in the fact that old school MIDI runs at 31250 bits per second, which translates to 0.32 milliseconds per byte sent. A three note chord and three notes of melody (not unexpected for a piano piece1) means potentially 18 bytes of data, taking 5.76 milliseconds for one instrument. A centisecond is 10ms… 1 Even this is out of the range of the cheap USB to serial MIDI adaptors, which is why some sort of flow control would have been useful… |
Colin (478) 2433 posts |
While the usb clock tick is regular you don’t necessarily get an interrupt every clock tick – the isochronous scheme I used generated interrupts every 2ms whether the device was USB1 or USB2. The clock tick is the granularity that you can receive interrupts. So when you queue a bulk transfer the controller will nak while no data is received from the device and generate an interrupt when data is transmitted. So you get data over usb within 1 usb clock tick of the event being sent by the device – subject to the load on the host controller. If you are unlucky the device will send zero length packets instead of naking This may be a problem as the upcall handler isn’t called if no data is put in the usb buffer If you think of an audio signal which was mainly silence with occasional blips thats what you have with MIDI for best timing accuracy under heavy usb loads isochronous would have been better option for the device – though it would have increased overall cpu usage – and probably been worse on a pi which has poor isochronous support. |
Dave Higton (1515) 3526 posts |
Apart from the fact that the statement above is hypothetical, I don’t think it would help because isochronous transfers are regular (well, sort-of regular when you consider video), but MIDI is not. Do all RISC OS platforms generate interrupts at 2ms period? Would it be possible to use them in a MIDI scheduler, in addition to their existing use? |
Colin (478) 2433 posts |
The device effectively resamples the 31250hz MIDI data to USB clock rate so I can’t see that there would be an overall delay. Events 1 second apart will be 1 second apart subject to the clocking accuracy of the 2 devices. You may get jitter if the clocks are not divisable meaning events may have 1 USB clock tick error. |
Colin (478) 2433 posts |
It could be thought of as a 31250hz mono 1 bit samplesize audio stream.
The interrupt is programmable for each transfer. I programmed Isochronous transfers for a 2ms interrupt to minimise overheads. For a bulk transfers if the transfer size is say 64k the driver indicates that it wants an interrupt for the transfer when the last packet has been transferred. Then the host controller will transfer 64k then issue an interrupt which is what you detect in the upcall handler. If a packet is received which is less than the usb max packet size the interrupt occurs at the point the short transfer arrived and the rest of the transfer is abandoned. |
Colin (478) 2433 posts |
Dawns on me I didn’t answer the question. If you mean to queue MIDI data for output no I don’t think it is of use. It’s dependent on transfers being queued to generate interrupts so the interrupt frequency is variable. If the output is usb, an interrupt out endpoint may work as a scheduler if the device uses one of them. Best solution I think would be for the OS to have a 1ms tickerV. The cs TickerV could be triggered from that. |
Rick Murray (539) 13840 posts |
While I wouldn’t disagree with FastTickerV, one of the issues on RISC OS is the problem of reentrancy; it often isn’t safe to do stuff in TickerV, never mind a faster one, meaning delegation to a CallBack to return in an essentially undefined duration, somewhat trashing the point of having accurate timing. Really, all I need is: connect to endpoint, is there data to be read (if so how much), read/write data, and tidy up when finished. The file methodology made this fairly simple, but any other method that might work would be worth considering. |
Dave Higton (1515) 3526 posts |
For MIDI playout, AFAICS there are these stages: 1) A non-real-time task takes MIDI commands, presumably from a file, works out time stamps for them, and puts them into a FIFO. 2) A task on a fast ticker interrupt reads the time stamps and commands from the first FIFO; it can store at least one set between interrupts. When the time falls due for one or more commands, it inserts the MIDI command(s) into a second FIFO. 3) The USB stack sees that the latter FIFO is not empty, pulls out all the bytes from it, and sends them. Have I mised anything or got anything wrong there? Does that second FIFO exist, and is it accessible via an existing API? |
Colin (478) 2433 posts |
Yes it’s the USB buffer If you are using a TickerV to issue MIDI commands I would do is this. You have 3 swis midi_open, midi_close and midi_sendCommand midi_open sets up a function on the tickerv, a buffermanager buffer(FIFO) and opens a usb endpoint with a small buffer. midi_close releases resources. midi_sendCommand inserts the command size and the command bytes into the FIFO using buffer manager functions in one insert operation – it has to be one insert operation to stop the tickerv reading partial commands. If you make the command size the last byte inserted the TickerV can read the next command size from the first byte in the fifo and remove whole commands at a time. The swi informs the caller if the command is inserted successfully. The app can then keep calling midi_sendCommand and multitask when the FIFO is full. How long a null event period would have to be depends on the length of the FIFO. The tickerV waits for entries in the FIFO, checks the first command and if it is ready to send sends the command to the USB buffer using the buffermanager commands. Again the whole command must be inserted into the USB buffer in one operation to stop USB sending the command in parts. The TickerV sends all commands that are ready to go at that point in time and removes them from the FIFO. |
Richard Walker (2090) 431 posts |
Rick said:
I have not seen the source for USB MIDI so I may be barking up the wrong tree, but my USBJoystick module does the above, except for writing. Might be a useful reference for using upcalls and devicefs buffers? The C source is included. |