Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!mnetor!uunet!seismo!mimsy!oddjob!hao!boulder!sunybcs!rutgers!princeton!udel!burdvax!jtids!aspvax!eraps2 From: eraps2@aspvax.UUCP Newsgroups: comp.sys.amiga Subject: RE: MIDI Questions Message-ID: <15740@aspvax.UUCP> Date: Fri, 11-Sep-87 10:11:03 EDT Article-I.D.: aspvax.15740 Posted: Fri Sep 11 10:11:03 1987 Date-Received: Sat, 12-Sep-87 15:38:40 EDT Lines: 173 Well, thought I'd put my two cents in. Yes, there is a problem. No, it is NOT in the hardware. No it is NOT in the Amiga software. It comes from using the wrong tool to do the job. Namely, serial.device is NOT well suited to use as a MIDI interface. Older midi programs had this problem - I am told (I still use a //e for MIDI) that the newer software does not suffer from this. Nonetheless, I for one would prefer a more general (and easier [for the application programmers]) solution, since I suspect much tweaking went into getting the programs to work. I experienced the problem early on when I first hooked up a MIDI cable to the Amiga and wrote some code to drive it. Stated basically, the problem is that the serial device does not time stamp the incomming data, and since the Amiga multitasks, by the time the program gets the code, the time 'may' be wrong since another task 'may' have run in between. Note that by disabling other tasks the problem vanishes (but then you can't multitask with the program). The other problem is that the serial device outputs the data as soon as it gets it - which may not be when the program sent it, thus messing up the outgoing timing. The same semi-solution applies. To get real-time preformance in this type of enviorment, the solution as traditionally been to put the nasty timing stuff in the driver. BUT, we can't really expect the serial device to handle time stamping since that is not necessary for normal serial applications. ****** Warning - Long Winded Suggested Solution Ahead ****** The Solution: Make a new device: midi.device. Requirements: ------------- 1) Ability to grab EVERY byte as it comes in to at least 33000 baud (3300 bytes/sec). 2) Ability to know WHEN the byte came in (so we can calculate the durations between note on and note off, etc ...) 3) Ability to send out bytes at precise points in time (at least to a good enough resolution to fool the ear) Current Implementation method: ------------------------------ Below, see a diagram of typical current recording method (some methods are highly ingeneous to minimize the problems discussed, but this is the basic problem). Read a byte <----------------- ask timer.device for the time | record note and time ----------- The problem with this is that the time is NOT accurate since after the byte has been read by the serial device, the task doing this may be interrupted. Also, although the serial device can read ALL the bytes, the task may not (in the short run, due to other tasks), be able to keep up with the bytes coming in, so that if the byte came and was buffered up by the serial device, the task might not read it for another 2 seconds, and then, even if the time it got was accurate, there is still a 2 second error in the timing. These problems are CLEARLY audible. A Solution: ----------- We need to do the time-stamping in the driver (where interupts are impossible and we will be accurate to AT LEAST 1/3300 0.3 ms. This lets us record accurately. Output should be double buffered and under control of the driver as well. The sequence to use the system might go something like this: open midi.device (default everthing to MIDI parameters) while (record) read_midi_value, place in buffer clear time value on 1st MIDI code/time element to 0 to playback: write_midi(1st 1024 MIDI code/time data elements in buffer) while (playback) write_midi(next 1024 MIDI code/time data elements in buffer) write_midi(last partial data (< 1024)) data format: [MIDI code][time since last code was read] Driver Requirements: new global: miditime last_time; /* actual time of last code received */ open: same as current driver, but make all values default to those MIDI uses (as described in the RKM). Therefore, setparms won't be used input: when a byte is received, place it in the buffer (as is currently done). also add to the buffer the elapsed time since the last byte was received. this is why we need a new global (or at least a static). Thus, now each input value is represented by 2 values, the actual code and a time-stamp. output: output MUST be buffered, since the task feeding us with values may be interupted at any time (and the music would hang until it became active again). The simplest solution is to write the values in blocks. Then the driver may output them 1 at a time until the buffer is exhausted. The problem with this alone is that the task feeding us may not be active at the exact instant we (the driver) need more values. For this reason, I suggest double buffering. Thus, while 1 buffer is being output, the task feeding us has the length of the 1st buffer to write a second. When the second buffer is being output, the task has that long to write the 1st buffer. To prevent the task from overflowing our buffers (writing a buffer we are not done with), the writes can be forced to a syncronous mode (thus if the task trys to write a 3rd buffer [overwrite the 1st], the request will not return [will hang the task] until space is available). This also means we need a new (or modified status command, to see if a previous write completed). schedule flow: (driven by user requests) wiat for a write request to come in. buffer available ? no - convert request to syncronous (so continue when buffer is free) yes - continue repeat use flow: (sub-function of schedule flow) Is buffer N ready to be output? (N == 0 or 1) no - wait yes - continue output buffer N = !N; repeat output flow: (driven by data output rate) get next code/time stamp element wait time stamp amout of time do this by schedualing an interupt using a CIA timer (I think this is what the serial device uses it for now anyway - to regulate output baud rate). output the code if end-of-buffer, switch buffers repeat. loop: NEW command, tells driver to copy any incoming byte directly to the output (since the task can't do this in real time, any many MIDI programs have this feature, we might as well provide it). Questions/problems to solve. What form should the timer values take - If the TOD (time of day) clock is accurate enough (.3 ms resolution or better), this is the ideal solution, since the MIDI codes are 8 bit and the TOD clock is 24 bits (and can be read directly off of a chip register), 1 32 bit word (long) can represent each value. Is 32 bits a waste of space for this maybe? At .3 ms per bit, 8 bits only gives us 78 ms max delay time - clearly inadequate, and 16 bits gives us 20 seconds - good, but NOT good enough unless we want to add a special code that the driver will not send out (to allow LONGER) delays - also the receive part of the driver would have to padd with these. Not elegant. 24 bits gives us 85 hrs between events - plenty. Ok, so we need 32 bits to represent each code. Can we use the TOD register value to provide this? I don't know, I will have to find out the resolution of this clock (do you know what it is?) If it is not good enough, we will have to generate a synthetic time value based on the 8 bit serial timer and the TOD (not tough, but not as clean). ============================================= Well, that's it ... anybody have something to do the trick? This is #3 on my hit list, so don't hold your breath waiting from my version. - Rob Ginn eraps1@nadc.arpa ...burdvax!jtids!aspvax!eraps2 "We want information ... information" Rob Ginn - No. 2 ...burdvax!jtids!aspvax!eraps2 eraps1@nadc.arpa