Garvin: STORAGE FORMATS

There  is  no standard yet for either RAM or disk-based	 storage  for
MIDI  events.	I have heard rumors of a standard  for	disk  storage
which  would allow one manufacturer's software to read files  written
by someone else, but intermediate RAM storage is another story.	  The
storage formats used throughout the industry are diverse, and usually
so  complex  that changing internal formats would  require  extensive
rewrites.

This is out of date. See: Standard MIDI-File Format Spec. 1.1

Most internal storage methods fall into one of four categories	which
I  will	 call  'end-point-relative',  'end-point-absolute',  'single-
point-absolute',  and  'bar-and-note' storage.   All storage  formats
involve	 storing  data	in a linear  data  stream.   Relative  timing
implies	 that timing will be encoded as a distance from the  previous
event.	 Absolute  timing will use a global time reference,  such  as
beats  and  bars.   End-point  storage	refers	to  separate  storage
locations  for	NOTE-ON and NOTE-OFF events (usually with  their  own
time-stamps).	Single-point storage requires that a pointer be aimed
at the NOTE-ON record in the data stream, and when the NOTE-OFF event
is received,  it is stored at the same location (better yet, the note
DURATION  can  be  computed and	 stored).   The	 bar-and-note  method
parallels  the	way  music  is	normally  notated.  Each  method  has
advantages, and there is a lot of overlap between categories.

I  will	 try to carry MIDI's philosophy of setting MSB's  of  leading
bytes when encoding data for storage in the stream.   This will allow
resynching if a byte is missed,	 and will make data streams easier to
edit.  Many software writers use this method for internal storage.


END-POINT-RELATIVE STORAGE

MIDI data is received as a stream of bytes with high bits (MSB'S) set
on commands and reset on data.	 Why not store the bytes just as they
are  received?	 Embedded  MIDI	 clock messages provides  the  proper
spacing for NOTE-ON,  NOTE-OFF and other events.   Replaying the data
requires setting up a series of 'play pointers' into the data  stream
(one  for  each	 track	to be played).	 When the  start  command  is
received,  the	data  is  sent to the output UART's just  as  it  was
received,  waiting for a 24th of a beat every time a clock command is
encountered  in	 the stream.   Unfortunately,  this method  uses  RAM
storage	 even  if  no events  are  being  transmitted,	and  multiple
channels store multiple copies of all unnecessary timing bytes.

 Ex:
- F8h --------- F8h 94h 46h 32h -------------- F8h 84h 46h 16h ---------- F8h
| wait 24th  | wait 24th, then		    | wait 24th, then
|	     | output a NOTE-ON on ch.4	    | output a NOTE-OFF on ch.4
|	     | for note no. 46h		    | for note no. 46h
|	     | velocity = 32h		    | OFF velocity = 16h

In  this example,  it is assumed that an external MIDI clock provides
the  timing.   Even if an internal timer is used,  0F8h bytes can  be
inserted into the input queue to simulate the MIDI software clock.  A
refinement of this method conserves storage by counting all  received
clock  bytes.	When an event is received,  the accumulated count  is
stored BEFORE the event and then the counter is reset.


END-POINT-ABSOLUTE STORAGE

Time-stamping  requires a system timer that is incremented every time
a  MIDI	 clock or timer interrupt is received.	 When  MIDI  data  is
enqueued,  the	system	timer is copied into the queue as  the	time-
stamp.	 When  the  data is dequeued for storage,  the time-stamp  is
stored	with the data record to provide an accurate  absolute  timing
reference.   Unlike  relative timing,  this allows the user to locate
any  spot  in the data stream without counting	all  embedded  timing
bytes.	 To  replay time-stamped data,	restart the system timer  and
wait  until it matches the timing bytes of the first item in the data
stream.	  Then send the data (without the time-stamp) and advance the
stream pointers.

  Ex:
---- 94h 09h 03h 46h 32h -------------- 84h 08h 04h 46h 16h -------
|    output a NOTE-ON on ch.4	   |	output a NOTE-OFF on ch.4
|    on the ninth clock tick	   |	on the eighth clock tick
|    of the third beat		   |	of the fourth beat
|    for note no. 46h		   |	for note no. 46h
|    velocity = 32h		   |	OFF velocity = 16h

The example uses only two bytes for the time-stamp and for the system
timer.	 More  practical systems use three bytes,  to allow over  two
hours  recording  time before the timer overflows (at 96  pulses  per
quarter).  The low-order byte is incremented until it reaches 96, (or
24  for	 MIDI clock timing).   Then the byte is reset and  the	count
propagates  through the other two timer bytes.	 The top timer	bytes
'turn over' at 127 so the MSB's are always zero.


SINGLE-POINT-ABSOLUTE STORAGE

Single-point  storage  requires	 maintaining a list  of	 pointers  to
access active notes in the data stream.	  When a NOTE-ON is received,
it is enqueued and stored in the data stream in the same way as	 end-
point-absolute	storage,   but	zero's  are  stored  afterward  in  a
compartment reserved to keep track of the note's duration.  A pointer
to  the	 zero-bytes  is	 stored	 in a list to  allow  access  to  the
duration.   Every  time	 a  MIDI clock byte  or	 timer	interrupt  is
received, the list is checked, and the pointers are used to increment
duration bytes.	  When the NOTE-OFF event is received, the pointer is
removed from the list -- no other action is necessary.	 The duration
will  be  frozen as part of the note  record.	Single-point-absolute
storage	 provides advantages in editing (notes can be moved  easily),
and  display  (it is easy to tell whether a note is a  quarter	note,
half note, etc.).

Ex:
-- 94h 09h 03h 46h 32h -------------- 04h 01h ---------------------
|  output a NOTE-ON on ch.4	  These are duration bytes which
|  on the ninth clock tick	  travel with the note record and are
|  of the third beat		  incremented by each timer tick
|  for note no. 46h		  until NOTE-OFF is received
|  velocity = 32h

During playback,  NOTE-ON's are derived in the usual way (wait  until
the system timer reaches the embedded time-stamp),  but this time the
duration  bytes are retrieved and stored in a 'timeout'	 list.	 They
are  decremented with every timer tick,	 and when they reach zero,  a
NOTE-OFF  is transmitted.   The timeout list must keep a copy of  the
note number so that the proper note can be turned off.	 The  single-
point  method  affords another advantage:  it is unlikely that	notes
will get stuck.	  NOTE-OFF events cannot be missed.   Any  reasonable
value in the timeout list will eventually decrement to zero and cause
a NOTE-OFF to be sent.


BAR-AND-NOTE STORAGE

So far,	 I have discussed playing notes,  but not rests.   Of course,
rests  are simply the spaces between notes,  but the storage  formats
outlined  do  not  provide a way of tracking down  these  spaces  for
correlation with written music.	  Rests can be stored in the same way
as notes, but note numbers and velocities are not needed.  This seems
to  be	a reversion to the original relative timing method  when  you
consider  that rests are similar to the old embedded relative  timing
markers.   Now	the NOTE-ON time-stamps become redundant -- where one
note-or-rest  stops,  the  next will start.   Without some  frame  of
reference,  though,  it is difficult to find a designated spot in the
data stream.   I have borrowed another device from written music: bar
lines.	 There	is no MIDI equivalent for a bar line,  so I will  use
0BAh.  The bar marker will be followed by one-or-two-byte bar numbers
to allow absolute locations to be found.   This combines some of  the
better features from all the methods outlined above.  I will use 0A0h
as the token for a rest.

Ex:
- 0BAh 32h ---- 0A0h 02h 14h ---------- 94h 46h 32h -------------- 01h 02h---
| At bar #32h | rest		     | output a NOTE-ON on ch.4	  duration
|	      | for two beats	     | for note no. 46h		  is 1 beat
|	      | and 14h ticks	     | velocity = 32h		  and 2 ticks

Some  storage  formats	are better suited to  certain  approaches  to
editing	 or  to certain looping constructs or display  formats,	 etc.
Choose a format that suits your application,  but remember to take as
general an approach as possible.  You will undoubtedly want to expand
later to incorporate new ideas.


QUANTIZATION

Quantization  was first mentioned in the context of scaling down  the
system	clock.	 If a section of music was recorded using a 96	pulse
per quarter note clock,	 the system clock can simply be slowed to  24
pulses	per quarter note.   Each timer interrupt would then increment
the system timer by 4 instead of 1.  This method works but it has one
main  drawback.	  If there is no frame of reference  (an  unquantized
track,	for  instance)	it  may not be noticed,	 but all  notes	 will
effectively be shifted LATE by the quantize interval.	This is	 like
truncating a number when the real intention is to round it.

To  quantize  events so that the notes fall ON the beat	 rather	 than
AFTER  the  beat,  the timing target must be ANTICIPATED by half  the
amount	of  the	 quantization  period.	 This  causes  events  to  be
processed in the center of a 'quantize window'.	  To accomplish this,
the  system  timer contents are copied to a  'look-ahead  timer'  and
incremented  so	 that  it LEADS the actual system time	by  half  the
quantize interval.  Using look-ahead timers for compares will trigger
events	ahead of time.	 Remember just to run the clock routine every
Nth clock tick and to add N/2 to the current time to derive the look-
ahead timer.

The  same  result  can be accomplished by  first  running  the	clock
routine	 N/2  times  consecutively (This  accounts  for	 look-ahead).
After  this initial look-ahead,	 the clock cycle consists of  waiting
for  N	clock  periods	and then running the clock  routine  N	times
consecutively.

Quantization will clean up notes whose timing is slightly 'frayed  at
the  edges',  but some mistakes can actually be accentuated.   If the
mistake	 is  severe enough to fall outside of the  intended  quantize
window, note timing will be rounded in the wrong direction as in NOTE
#3 of fig. 2.

It  was mentioned that the trailing edges of notes are	usually	 much
less  timing-critical  than the leading edges.	 One of	 my  favorite
techniques  for avoiding a mechanical sound is quantizing the leading
edges  of  notes  but  leaving	the  lengths  intact.	This  can  be
accomplished by using a look-ahead on the clock which pulls the NOTE-
ON event and NOTE-LENGTH from the data stream.	 The unprocessed note
length	is stored in a timeout list where it is decremented  on	 each
tick  of the HI-RESOLUTION clock.   The appropriate NOTE-OFF is	 sent
when it reaches zero.

The  timing  of recorded music is easily altered  or  quantized,  but
random	timing information from human input is difficult to add later
(you COULD try it).  Some synthesizer systems, such as Fairlight, use
high-performance  hardware  to derive timing clocks as	high  as  384
clocks	per quarter note,  but be aware of micro-based software which
claims this level of accuracy.	 By attempting performance beyond the
capability  of	the  machine,  the software  can  actually  sacrifice
accuracy.


PILOT TRACK

Most  musical compositions have verses,	 choruses and other types  of
sections.  Sections are usually recorded or written separately.	 When
all the sections are completed,	 they are rearranged,  repeated, etc.
by the use of what I will call a 'pilot' track.	  The pilot track  in
this  type of system will operate 'outside' of the time frame of  the
composition.  In fact it may be the only timing stream which moves in
a  linear fashion.   Macro commands,  such as 'play section  3,  five
times'  will pilot the interpreter through the appropriate series  of
pointer loads, plays and reloads to accomplish the task.


The start of each section now becomes the main point of reference for
note  timing.	At  the end of a section the system timer is  usually
reset and the pilot track is consulted to find the next section to be
played.	  Each	section	 will  be  treated as if  it  is  a  separate
composition, so each may continue up to the maximum length allowed by
the system timer.

Some  pilot track schemes use a FORTH-type stack to hold  reiterative
constructs  and	 section  or data references.	I have	even  seen  a
sequencer which allowed English statements such as 'SECTION 3 = VERSE
1 + CHORUS'.   In any case,  the section is treated as a  subroutine,
with  control  returning  to  the pilot track when  the	 section  has
completed.
