----------------------------------------------------------------------------- MED/OctaMED MMD0 and MMD1 file formats written by Teijo Kinnunen (25.4.1992) Revision 1 ----------------------------------------------------------------------------- Background ---------- A couple of years ago, when programming MED V2.1, I needed a file format for MED modules. The only "module" format in MED V2.0 was the Sng+samples format. Although it produced compact files, it was very difficult and tricky to read in. Therefore, I designed a new file format, that would be easy to use in module player programs etc. This file format was named 'MMD0' (Med MoDule 0). The limitations in MMD0 block format forced me to create a new file format for OctaMED Professional, this format is 'MMD1'. It's mostly the same as MMD0, except the block structure is different. At the time of this writing (when OctaMED Pro is not even released yet), MMD0's are absolutely more common than MMD1's. Design concepts --------------- One of the main goals was to make MMD's (MED modules) as extensible as possible. This would have been easy to implement with IFF-style chunks. However, this method is obviously not the best for play-routine use. Therefore, MMD's are implemented in quite an extraordinary way. They consist of structures (similar to C structs), and pointers. In a module file, pointers are defined as offsets from the beginning of the module. This way, a particular structure can be read just by Seek()'ing using the pointer as the offset from the beginning of the file. When a module has been read into memory, it has to be relocated before it can be used (the relocation is done simply by adding the address of the module to the pointers). As with the Amiga OS, a MMD file does not contain absolute addresses. There's a module header structure at the beginning of the file. This structure contains pointers to different parts of the module. And you *MUST* use these pointers. You may NOT expect that the song structure is at offset $00000034, for example. Although it usually is, this may change in future releases. In addition, it's possible that a structure even doesn't exist (the structure pointer is NULL). Therefore, you *MUST* check the structure pointer before accessing the structure. Finally, when writing MMD's you *MUST* set undefined/reserved fields to zeros. More finally, you *MUST* align all structures to even boundaries! (I forgot the alignment in MED V3.00 save routine, resulting Guruing modules under some conditions :-( The module header ----------------- This structure must exist at the beginning of each MED module file. Each of the structure members are described below. In multi-modules, there are header structs for each song. (The subsequent header pointers can be found from expdata structure. Multi-modules should have the same smplarr pointer in every header.) Older MEDs which don't recognize multi-modules consider a multi-module as an ordinary module (only the first song is loaded). The numbers enclosed in /* */ at the beginning of each line are (decimal) offsets of each member (for assembly programmers). ----------------------------------------------------------------------------- struct MMD0 { /* 0 */ ULONG id; /* 4 */ ULONG modlen; /* 8 */ struct MMD0song *song; /* 12 */ ULONG reserved0; /* 16 */ struct MMD0Block **blockarr; /* 20 */ ULONG reserved1; /* 24 */ struct InstrHdr **smplarr; /* 28 */ ULONG reserved2; /* 32 */ struct MMD0exp *expdata; /* 36 */ ULONG reserved3; /* 40 */ UWORD pstate; /* some data for the player routine */ /* 42 */ UWORD pblock; /* 44 */ UWORD pline; /* 46 */ UWORD pseqnum; /* 48 */ WORD actplayline; /* 50 */ UBYTE counter; /* 51 */ UBYTE extra_songs; /* number of songs - 1 */ }; /* length = 52 bytes */ ----------------------------------------------------------------------------- id -- This longword is used to identify the MMD and its version. Currently defined MMD types are MMD0 (0x4D4D4430) and MMD1 (0x4D4D4431). MMD2 and upwards are reserved for future versions. In multi-modules, the following modules usually contain id MCNT, or MCN1. The first module always has MMD0 or MMD1 as an id. modlen ------ This longword contains the length of the entire module. song ---- Pointer to a MMD0song structure. This structure MUST ALWAYS EXIST! blockarr -------- Pointer to a table of block pointers. For example: blockarr: $00003000 block 0 ptr block 1 ptr block 2 ptr offset $00003000: $00002000, $00002400, $00002800 .... offset $00002000: block 0 data... offset $00002400: block 1 data... .. The size of the table is MMD0song.numblocks longwords. smplarr ------- Pointer to a table of instrument pointers. The size of the table is MMD0song.songlen longwords. This pointer is zero in OctaMED Pro MMD1 songs. In this case, OctaMED Pro loads the instruments from disk(s). expdata ------- Pointer to an expansion structure. The expansion structure contains a lot of extra information. The exp. structure does not exist in all MMD's. (Be sure to check the pointer before using it.) pstate, pblock, pline, pseqnum, actplayline, counter ---------------------------------------------------- These are variables for the play routine. You can read these fields to get the current song position (not all versions of the play routine use these fields, however). When writing a MMD, you should leave all fields to zero, except the 'actplayline', which ought to be -1 ($FFFF). extra_songs ----------- This field contains the number of songs in the current module. For non-multi-modules, this is 0. If there are two songs, extra_songs contains 1, and so on. reserved0,1,2,3 --------------- Not currently defined. Set to zero. The song structure (MMD0song) ----------------------------- This structure contains the basic information about the song. It must exist on every module file. ----------------------------------------------------------------------------- struct MMD0song { struct MMD0sample sample[63]; /* 63 * 8 bytes = 504 bytes */ UWORD numblocks; /* offs: 504 */ UWORD songlen; /* offs: 506 */ UBYTE playseq[256]; /* offs: 508 */ UWORD deftempo; /* offs: 764 */ BYTE playtransp; /* offs: 766 */ UBYTE flags; /* offs: 767 */ UBYTE flags2; /* offs: 768 */ UBYTE tempo2; /* offs: 769 */ UBYTE trkvol[16]; /* offs: 770 */ UBYTE mastervol; /* offs: 786 */ UBYTE numsamples; /* offs: 787 */ }; /* length = 788 bytes */ ----------------------------------------------------------------------------- sample ------ Contains some basic info about each sample. The structure looks like this: ----------------------------------------------------------------------------- struct MMD0sample { UWORD rep,replen; /* offs: 0(s), 2(s) */ UBYTE midich; /* offs: 4(s) */ UBYTE midipreset; /* offs: 5(s) */ UBYTE svol; /* offs: 6(s) */ BYTE strans; /* offs: 7(s) */ }; ----------------------------------------------------------------------------- rep repeat start offset, shifted right one bit (as in Protracker). replen repeat length, shifted right one bit. midich MIDI channel for current instrument, 0 if not MIDI. midipreset MIDI preset number for current instrument, 0 if no preset. svol default volume for current instrument (0 - 64). strans instrument transpose value. More information is defined in expdata structure. numblocks --------- Number of blocks in current song. This field also indicates the length of the blockarr table in longwords. songlen ------- Song length (number of sequence numbers in the play sequence list). playseq ------- This is the play sequence list. deftempo -------- Default song tempo (the leftmost tempo slider in MED/OctaMED). If BPM mode is on, this value indicates BPM. playtransp ---------- The global play transpose value for current song. flags ----- Contains many single-bit flags: FLAG_FILTERON 0x1 the hardware audio filter is on FLAG_JUMPINGON 0x2 mouse pointer jumping on (not in OctaMED Pro) FLAG_JUMP8TH 0x4 jump every 8th line (not in OctaMED Pro) FLAG_INSTRSATT 0x8 sng+samples indicator (not useful in MMD's) FLAG_VOLHEX 0x10 volumes are HEX FLAG_STSLIDE 0x20 use ST/NT/PT compatible sliding FLAG_8CHANNEL 0x40 this is OctaMED 5-8 channel song (bit 0x80 is not defined, and must be set to zero) flags2 ------ More flags, currently only BPM stuff: FLAG2_BMASK 0x1F (bits 0-4) BPM beat length (in lines) 0 = 1 line, $1F = 32 lines. (The rightmost slider in OctaMED Pro BPM mode.) FLAG2_BPM 0x20 BPM mode on (bits 0x40 and 0x80 are not defined, and must be set to zero) tempo2 ------ This is the "secondary tempo" (the rightmost MED/OctaMED tempo slider), indicating the number of timing pulses per line. trkvol[16] ---------- The relative track volumes (1 - 64) for each track. mastervol --------- The relative master volume (1 - 64). numsamples ---------- Number of instruments (samples/synthsounds) in current song. Also indicates the size of the smplarr table in longwords. The block format ---------------- As described above, MMD0 header structure contains a pointer (blockarr) to a table of block pointers. These block pointers point to the actual block data structures. The format of these data structures differ in MMD0 and MMD1 file formats. MMD0 block format ----------------- At the beginning of each block, there's a small header: ----------------------------------------------------------------------------- struct MMD0Block { UBYTE numtracks,lines; }; ----------------------------------------------------------------------------- numtracks number of tracks (4, 8, 12 or 16) on this block lines number of lines on this block; 0 = 1 line, 255 = 256 lines Following this header, there is the actual note data, consisting of 3-byte structures containing a note and its command. The data is arranged sequentially a line at a time, i.e. in the following order: line 0 track 0 line 0 track 1 line 0 track 2 line 0 track 3 line 1 track 0 line 1 track 1 ... The 3-byte structure looks like this (each letter corresponds to one bit): xynnnnnn iiiicccc dddddddd n = note number (0 - $3F). 0 = ---, 1 = C-1, 2 = C#1... i = the low 4 bits of the instrument number x = the 5th bit (#4) of the instrument number y = the 6th bit (#5) of the instrument number c = command number (0 - $F) d = databyte ($00 - $FF) MMD1 block format ----------------- MMD1 block format can contain a lot more information than MMD0's. The block header looks like this: ----------------------------------------------------------------------------- struct MMD1Block { UWORD numtracks; UWORD lines; struct BlockInfo *info; }; ----------------------------------------------------------------------------- numtracks Number of tracks in this block (4, 8, 12, or 16). lines Number of lines in this block (0 = 1 line etc.). OctaMED Pro can handle upto 3200 lines/block, so this is obviously the practical upper limit. info Pointer to structure containing extra information. (Can be NULL, if no BlockInfo struct exists). The BlockInfo structure is: ----------------------------------------------------------------------------- struct BlockInfo { ULONG *hlmask; UBYTE *blockname; ULONG blocknamelen; ULONG reserved[6]; }; ----------------------------------------------------------------------------- hlmask Pointer to an array of longwords, containing info about line highlighting (TAB key on MED). The number of longwords depend on the number of lines on the block. (E.g: 1 line -> 1 longword, 32 lines -> 1 lw, 33 lines -> 2 lws, 256 lines -> 4 lws) The bits in the longwords are arranged in reversed order (e.g. bit #0 = line 0, bit #31 = line 31). blockname Pointer to the name of the block. Must be null- terminated. blocknamelen Length of the block name, including the terminating zero. OctaMED Pro currently has the maximum length of 41 chars (+ zero). However, this may change in the future. Don't read blocknames longer than you are able to handle! reserved[6] These are reserved for future extensions. Must be set to zero. The note structures, which are 4 bytes long in MMD1 modules, are arranged exactly as in MMD0 modules. xnnnnnnn xxiiiiii cccccccc dddddddd n = note number (0 - $7F, 0 = ---, 1 = C-1...) i = instrument number (0 - $3F) c = command ($00 - $FF) d = databyte ($00 - $FF) x = undefined, reserved for future expansion. MUST BE SET TO ZERO, AND MASKED OUT WHEN READING THE NOTE OR INSTRUMENT NUMBER. The instrument format --------------------- The MMD0 header structure contains a pointer (smplarr) to a table of instrument pointers. These pointers point to the actual instrument data structures. If an instrument pointer is zero, there's no instrument in that slot. Every instrument has a six-byte header structure, which is the same for all instrument types (sample/synth/hybrid). ----------------------------------------------------------------------------- struct InstrHdr { ULONG length; WORD type; /* Followed by actual data */ }; ----------------------------------------------------------------------------- length indicates the length of the instrument (the six byte header data length is not included) type instrument type - currently the following types are defined: HYBRID -2 SYNTHETIC -1 SAMPLE 0 (an ordinary 1-octave sample) IFF5OCT 1 (5 octaves) IFF3OCT 2 (3 octaves) (The following ones are recognized by OctaMED Pro only) IFF2OCT 3 (2 octaves) IFF4OCT 4 (4 octaves) IFF6OCT 5 (6 octaves) IFF7OCT 6 (7 octaves) The sample-type instruments (>= 0) contain the actual sample data straight after the header structure. Synthetic instruments --------------------- Synthsounds have a special structure of their own. They also contain waveforms and pointers to them. Therefore, relocation is required. However, there's an important difference: pointers are expressed as an offset from the beginning of the synthsound, NOT the beginning of the module. The 'reloc.a' routine provided with MED/OctaMED automatically handles this. The synthsound structure is as follows (note that this structure contains the header structure): ----------------------------------------------------------------------------- struct SynthInstr { ULONG length; /* length of this struct */ WORD type; /* -1 or -2 (offs: 4) */ UBYTE defaultdecay; UBYTE reserved[3]; UWORD rep; UWORD replen; UWORD voltbllen; /* offs: 14 */ UWORD wftbllen; /* offs: 16 */ UBYTE volspeed; /* offs: 18 */ UBYTE wfspeed; /* offs: 19 */ UWORD wforms; /* offs: 20 */ UBYTE voltbl[128]; /* offs: 22 */ UBYTE wftbl[128]; /* offs: 150 */ struct SynthWF *wf[64]; /* offs: 278 */ }; ----------------------------------------------------------------------------- defaultdecay = the default decay of the current synthsound. This is NOT used in modules. It's significant only when saving a single synthsound. reserved[3] = set to zero. rep, replen = repeat/rep. length for hybrid sounds. Used only when saving a single hybrid sound. voltbllen = the length of the volume sequence table. wftbllen = the length of the waveform sequence table. volspeed = the initial volume table execution speed. wfspeed = the initial waveform table execution speed. wforms = the number of waveforms in the current synthsound. voltbl = the actual volume sequence table. Values $00-$7F are volumes or command arguments. Values >= $80 are commands. The following commands are currently defined: $FF END $F4 EN1 $FE JMP $F3 CHU $FB HLT $F2 CHD $FA JWS $F1 WAI $F6 EST $F0 SPD $F5 EN2 wftbl = the actual waveform sequence table. Values $00-$7F are waveform numbers or command arguments. Values >= $80 are commands. The following commands are currently defined: $FF END $F6 RES $FE JMP $F5 VBS $FD ARE $F4 VBD $FC ARP $F3 CHU $FB HLT $F2 CHD $FA JVS $F1 WAI $F7 VWF $F0 SPD wf = pointers to waveforms. (Once again: relative to the beginning of the synthsound!) A waveform structure is as follows: struct SynthWF { UWORD length; /* length in words */ BYTE wfdata[xx]; /* the waveform */ }; (where xx = length in bytes) In hybrid sounds, however, wf[0] is different. Hybrid instruments ------------------ Hybrid sounds use the same structure as synthsounds, except that the first waveform (wf[0]) pointer points to a sample. (The sample header structure exists, as usual.) MMD0exp - the key to future expansions -------------------------------------- For possible future expansions, I designed a structure for carrying the miscellaneous things that were added to MED/OctaMED now and then. (MED V3.00 was the first version, which wrote this structure.) Most of its fields are in use now, but it's possible to even expand this structure (things will get a bit more tricky, though). In multi-modules, you should extract all data from the expansion structure of the first song. The only exceptions are currently the 'nextmod' and 'songname' fields, which are song-specific. Also, there has been need for extending the MMD0sample structure. Both InstrExt and MMDInstrInfo provide extra information about the instruments. These are defined as structure arrays (exp_smp and iinfo point to the first structure). The extension structures don't have a constant size, instead you have to read s_ext_entrsz or i_ext_entrsz to get the structure sizes. When reading, you have to check the entrsz fields to see which structure members do exist. The difference between InstrExt and MMDInstrInfo is that InstrExt contains information the play-routine is interested in (e.g. finetune). MMDInstrInfo contains "secondary" information, which is of no use to the player routine (e.g. instrument name). The expansion structure follows: ----------------------------------------------------------------------------- struct MMD0exp { struct MMD0 *nextmod; struct InstrExt *exp_smp; UWORD s_ext_entries; UWORD s_ext_entrsz; UBYTE *annotxt; ULONG annolen; struct MMDInstrInfo *iinfo; UWORD i_ext_entries; UWORD i_ext_entrsz; ULONG jumpmask; UWORD *rgbtable; UBYTE channelsplit[4]; struct NotationInfo *n_info; UBYTE *songname; ULONG songnamelen; struct MMDDumpData *dumps; ULONG reserved2[7]; }; ----------------------------------------------------------------------------- nextmod = pointer to the next module (or zero). (Only used in multi-modules!) exp_smp = pointer to InstrExt. Currently the first four bytes of InstrExt have been defined: struct InstrExt { UBYTE hold; UBYTE decay; UBYTE suppress_midi_off; BYTE finetune; }; hold, decay = hold/decay values of the instrument suppress_midi_off = 0 (FALSE) or not (TRUE) finetune = instrument finetune (-8-+7) s_ext_entries = the size of InstrExt structure array (i.e. the number of InstrExt structures). s_ext_entrsz = the size of each InstrExt structure (in bytes). annotxt = pointer to the annotation text (null-terminated). annolen = length of 'annotxt', including the terminating \0. iinfo = pointer to MMDInstrInfo. Currently the first forty bytes have been defined: struct MMDInstrInfo { UBYTE name[40]; }; name = null-terminated instrument name i_ext_entries = the size of the MMDInstrInfo struct array (i.e. the number of MMDInstrInfo structures). i_ext_entrsz = the size of each MMDInstrInfo struct in bytes. jumpmask = a mask controlling which instruments cause the mouse pointer to jump. E.g. bit #1 = instr. #1. This field has become obsolete in OctaMED Pro. rgbtable = pointer to eight UWORDs (screen colors) to be passed to LoadRGB4() routine. channelsplit = this longword is divided to four boolean bytes, controlling channel splitting in OctaMED 5 - 8 chnl modes. (A non-zero byte means that the channel is NOT splitted.) Currently only the following combinations should be used: 0x00000000 (8 channels (or normal 4 channel mode)) 0x000000FF (7 channels) 0x0000FFFF (6 channels) 0x00FFFFFF (5 channels) n_info = pointer to NotationInfo structure (used only in OctaMED V2.0 and later). It contains some info for the notation editor. struct NotationInfo { UBYTE n_of_sharps; UBYTE flags; WORD trksel[5]; UBYTE trkshow[16]; UBYTE trkghost[16]; BYTE notetr[63]; UBYTE pad; }; n_of_sharps = number of sharps or flats (0 - 6) flags = misc. bits, these are defined: NFLG_FLAT 1 (= use flats instead of sharps) NFLG_3_4 2 (= display 12 lines instead of 16) trksel = the number of the selected track, for each display preset (-1 = no track selected) trkshow = tracks shown (five bits used in each byte, bit #0 = preset 1, etc.) trkghost = tracks ghosted (as in 'trkshow') notetr = note transpose value for each instrument (-24 - +24). If bit 6 is negated, the instrument is hidden. pad = possibly holding info about struct expansions in the future. Don't touch! songname = song name of the current song (0-terminated). Each song of a multi-module can have a different name. songnamelen = song name length (including the terminating zero). dumps = MIDI dump data (created using OctaMED Pro MIDI message editor). The 'dumps' field points to the following struct: struct MMDDumpData { UWORD numdumps; UWORD reserved[3]; }; Immediately after this struct, there are 'numdumps' pointers to the following struct: struct MMDDump { ULONG length; UBYTE *data; UWORD ext_len; /* if ext_len >= 20: */ UBYTE name[20]; }; length = length of the MIDI message dump. data = pointer to the actual MIDI dump data. ext_len = MMDDump struct extension length. For flexible future expansion. (if ext_len >= 20, the following fields exist) name = name of the dump. reserved2 = future expansion fields, that MUST be zero now. ----------------------------------------------------------------------------- Finally, here is a collection of the most important rules you should obey when handling MMD's: * ALWAYS USE POINTERS, NOT ABSOLUTE OFFSETS. * CHECK THAT A POINTER IS NONZERO BEFORE ACCESSING ANYTHING IT POINTS TO. * WHEN WRITING, SET UNDEFINED/RESERVED BITS AND BYTES TO ZERO. WHEN READING, MASK OUT UNDEFINED BITS, AND DON'T USE UNDEFINED FIELDS. * WHEN WRITING, ALWAYS ALIGN EVERYTHING TO EVEN BOUNDARIES. * WHEN WRITING, ALWAYS WRITE THE SONG STRUCTURE. * REMEMBER TO HANDLE ERROR SITUATIONS CORRECTLY (IN ALL PROGRAMS, NOT ONLY WHEN HANDLING MMDs ;^) If you don't understand some part of this file completely, try saving a module using MED, and then examine the file with a file editor. This way you can learn easily about the file format of MED/OctaMED. ----------------------------------------------------------------------------- I hope this document will help programmers who wish to be able to handle modules in their programs. NOTE! This text file is PUBLIC DOMAIN. All distribution of this file,via the pd is strongly encouraged. Thank you! Teijo Kinnunen Oksantie 19 SF-86300 OULAINEN FINLAND ----------------------------------------------------------------------------