💾 Archived View for runjimmyrunrunyoufuckerrun.com › src › foreign › abcmidi › doc › programming › m… captured on 2021-12-17 at 13:26:06.
View Raw
More Information
-=-=-=-=-=-=-
Notes on the midi2abc code
--------------------------
written by Seymour Shlien
midi2abc.txt - last updated December 5 1999
Introduction
Midi2abc is a program for converting a midi files into an abc file.
A midi file merely consists of a list of commands to turn on
and off a set of voices at specific times. The time units are
expressed in pulses where the number of pulses per second can
be deduced from the information in the midi file. Pitch is
specified in semitone units ranging from 0 to 127 where middle C
is assigned a value of 60.
There are two types of midi in general use. In type 0, all the
voices are interleaved in time in one track. Each voice goes
to a specific channel which is mapped to a particular musical
instrument or program. There is a limit of 16 voices since only
4 bits are assigned to indicate the channel number. In type 2,
the voices are placed in separate tracks. This format allows
handling more than 16 voices.
In order to create an abc file, midi2abc must determine the
length of a musical unit (for example an eighth note) in terms
of midi pulses, determine the key signature, and, finally map the
midi commands into musical notes.
Though the midi file specifies the number of pulses per quarter note
(PPQN) in the Mthd header chunk, this is not necessarily an
accurate indication. It is primarily used together with the tempo
indication to determine the number of pulses per second. Unless
the midi file was created by a computer program, the actual length
of a quarter note in midi pulses can depart from the nominal indication.
Midi2abc tries to determine the length using its own heuristic
algorithm.
The sources of the program are contained in two files: midi2abc.c and
midifile.c. This document does not try to describe completely how
this program works, but is more of a general road map to the software.
Algorithmic Description
The conversion is done in multiple passes. In the first pass, the
midi file is read and an internal representation of the file is
created and stored in global dynamic memory. In each subsequent pass,
other information is added to the internal representation and finally
it is written to the output abc file.
After parsing the input run parameters and performing various
initializations, the main program calls the procedure mfread
which is found in the midifile.c module. Mfread parses the different
midi components of the file and calls the appropriate functions
midi2abc to process these components. These functions begin with
the txt_ prefix, some of which are listed here.
txt_header midi header chunk
txt_trackstart start of midi track
txt_trackend end of midi track
txt_noteon note on channel command
txt_noteoff note off channel command
txt_program midi program definition or change
txt_metatext midi textual information
txt_keysig midi key signature indication
txt_tempo midi tempo indication
Many other functions such as txt_pressure and txt_parameter
corresponding to other midi commands, do nothing here.
These functions build up an internal representation of the midi file
in a global structure referenced through the track[64] structure.
Up to 64 midi tracks can be stored in the internal representation.
Each track structure (atrack structure) contains single and double
linked lists of notes or text strings which are described below.
In addition there are other linked lists (referenced by playinghead
and chordhead), used for keeping track of notes currently playing
and musical chords.
Each note is represented by an anote structure which stores various
parameters of the note. The anote structure is allocated dynamically
as the midi file is read. The first four entries of the structure
are filled in while the structures are being created by mfread.
These are entries are listed below.
anote.pitch pitch in midi units
anote.chan midi channel number
anote.vel midi velocity (corresponding to loudness)
anote.time time in midi pulses.
The other entries in the anote structure are determined in later passes.
The anote structures are linked together into a a listx structure which
is included in the atrack structure. The head and tail of the list are
contained in this structure to facilitate the construction of this list.
In addition there is tlistx structure for storing all the textual
information (for example words in a Karaoke midi file) which may be found
in the track.
There is a doubly linked list structure of anotes (dlistx,
- playinghead) which is used as a work space for matching the note off
command with the corresponding note on command. This is needed in order
to determine the length of time the note was turned on (called anote.tplay).
The internal representation is mainly constructed by the important
functions txt_noteon and txt_noteoff called by mfread. These functions
in turn call the functions addnote and notestop. The midi file contains
separate commands for turning a note on or off so that in order to
determine the length of time that a note has been on, it is necessary to
match a note-off command with the corresponding note-on command.
Every time a note is turned on, it is also added to the playhead (tail)
structure. The procedure notestop finds the corresponding note-on
command in the playhead structure, removes it, and records the
duration of the note which was turned on.
At the end of the first pass, the number of tracks is counted and each
track is processed by the function postprocess which computes the entry
anote.dtnext for each anote structure. This entry contains the time
interval between the current and the next note in the linked list of
notes.
The abc time unit length is either 1/8 or 1/16 depending on the time
signature. A time signature of 4/4 is assumed unless it is specified
by the run time parameters (-m or -xm). (If -xm is specified, then the
program uses the time signature given by the midi meta command if it
exists in the file.)
The next step involves the quantization of the midi time units
expressed in pulse counts into abc time units. It is first necessary
to estimate the length of an abc time unit in terms of midi time
units. This either is estimated using a heuristic algorithm,
guesslength, or is determined from the run time parameters (-Q or -b).
The function guesslength makes an initial guess by dividing the
total number of pulse counts in the track by the total number
of notes. It then tries 100 different lengths in the neighbourhood
of this initial guess and chooses the one which leads to the smallest
quantization error. The quantization error is determined by the function
quantizer which keeps track of the total deviation of the time
a note starts (in pulse counts) from the expected time the note
starts assuming the standard musical intervals. This deviation
can either drift positively or negatively with time. The total
error is determined by summing the absolute values of these
deviations for each bar.
Once the unit length has been determined, all the notes in all
tracks are quantized by the function quantizer. This function
assigns values to the anote entries, anote.xnum, anote.playnum
and anote.denom.
anote.xnum interval between the current note and following note
also called the gap.
anote.playnum note duration.
anote.denom always has the value of 2.
A musical part does not necessarily begin at the start of a bar line,
but may have some leading notes. This is called anacrusis.
There are two methods to estimate the anacrusis. The function findana
searches for the first upbeat by examining the velocities (loudness) of
the notes. The function guessana, chooses the anacrusis which minimizes
the number of tied notes across a bar line which is determined by the
function testtrack.
At this point the key signature of the tune must be determined.
The procedure findkey computes the frequency distribution of all the
notes in the piece and stores it in the local array n[]. The key
signature is determined by transposing the distribution by each
of the 12 keys and counting the number of sharp or flat notes. The
key signature is determined from the key which leads to the minimum
number of black keys on the piano. The mode of the scale (major,
minor, Dorian, etc.) is determined by looking at the final note of
the piece.
Once the key signature is determined, the assumed flats or sharps
are determined by the procedure setupkey. The program is now ready
for its final pass where the musical parts (or voices) are printed
in the output abc file.
The procedure printtrack processes the internal representation
of each midi track producing a separate voice for each track.
In order to handle chords which may be present in an individual
track, printtrack maintains a structure referenced by chordhead
by calling the support functions addchord(), advancechord(),
removechord() and printchord(). These functions handle single
notes as well as chords. Another function, dospecial(), handles
the triplets and broken rhythms (eg. dotted notes followed by
half sized note or vice versa) whenever they are detected. The
printchord() function is supported by the functions printfraction()
and printpitch().
After the track is printed, the memory allocated to the structures
for the internal representation of the tracks is freed.
The option -splitvoice was introduced to handle polyphonic chords.
Without this option polyphonic chords appear in the abc file
like this.
[DF-A-][FA-]Az|
this will be represented by three voices
V: split1A
D2 z6|
V: split1B
F4 z4|
V: split1C
A6 z2|
This option is implemented by the function printtrack_split_voice().
The function label_split_voices() is called to assign all the notes
in the track to their split voice. This assignment of each note is
stored in note->splitnum. This is a complex function since it needs
to try to match the onset and end of each note in the chord. If
it is unable to do this for a particular note, then the note is
assigned to a different split. At the end, nsplits were created.
The notes in each split are printed in a separate voice by the
function printtrack_split(). The function printtrack_split(splitnumber)
was derived from printtrack(); however, it only processes the
notes belonging to a particular splitnumber.
It was necessary to determine and store the offset of the first
note in each split using the function set_first_gaps() prior
to calling printtrack_split().