/* loadmod.c * * Generic Module Player Protracker Module loader * * $Id: loadmod.c,v 1.8 1997/01/25 15:23:16 pekangas Exp $ * * Copyright 1996,1997 Housemarque Inc. * * This file is part of the MIDAS Sound System, and may only be * used, modified and distributed under the terms of the MIDAS * Sound System license, LICENSE.TXT. By continuing to use, * modify or distribute this file you indicate that you have * read the license and understand and accept it fully. */ #include "lang.h" #include "mtypes.h" #include "errors.h" #include "mglobals.h" #include "mmem.h" #include "file.h" #include "sdevice.h" #include "gmplayer.h" #ifndef NOEMS #include "ems.h" #endif #include "mutils.h" RCSID(const char *modload_rcsid = "$Id: loadmod.c,v 1.8 1997/01/25 15:23:16 pekangas Exp $";) /* Macro for endianness-swap. DANGEROUS - references the argument x twice */ #define SWAP16(x) ( ((x << 8) & 0xFF00) | ( (x >> 8) & 0x00FF) ) /* Pass error code in variable "error" on, used in gmpLoadMOD(). */ #define PASSERR() { LoadError(); PASSERROR(ID_gmpLoadMOD) } /****************************************************************************\ * struct modInstHdr * ----------------- * Description: Protracker module instrument header. Note that all 16-bit * fields are big-endian. \****************************************************************************/ typedef struct { char iname[22]; /* instrument name */ ushort slength; /* sample length */ uchar finetune; /* sample finetune value */ uchar volume; /* sample default volume */ ushort loopStart; /* sample loop start, in words */ ushort loopLength; /* sample loop length, in words */ } modInstHdr; /****************************************************************************\ * struct modHeader * ---------------- * Description: Protracker module file header \****************************************************************************/ typedef struct { char songName[20]; /* song name */ modInstHdr instruments[31]; /* instrument headers */ uchar songLength; /* song length */ uchar restart; /* unused by Protracker, song restart position in some modules */ uchar songData[128]; /* pattern playing orders */ char sign[4]; /* module signature */ } modHeader; /* Period table for Protracker octaves 0-5: */ static unsigned ptPeriods[6*12] = { 1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907, 856,808,762,720,678,640,604,570,538,508,480,453, 428,404,381,360,339,320,302,285,269,254,240,226, 214,202,190,180,170,160,151,143,135,127,120,113, 107,101,95,90,85,80,75,71,67,63,60,56, 53,50,47,45,42,40,37,35,33,31,30,28 }; /* Conversion table from Protracker command number to GMP ones: */ static uchar ptCommands[16] = { gmpcArpeggio, /* 0 - Arpeggio */ gmpcSlideUp, /* 1 - Portamento up */ gmpcSlideDown, /* 2 - Portamento down */ gmpcTonePortamento, /* 3 - Tone portamento */ gmpcVibrato, /* 4 - Vibrato */ gmpcTPortVSlide, /* 5 - Tone portamento + volume slide */ gmpcVibVSlide, /* 6 - Vibrato + volume slide */ gmpcTremolo, /* 7 - Tremolo */ gmpcSetPanning, /* 8 - Set panning (extension) */ gmpcSampleOffset, /* 9 - Sample offset */ gmpcVolumeSlide, /* A - Volume slide */ gmpcPositionJump, /* B - Position jump */ gmpcSetVolume, /* C - Set volume */ gmpcPatternBreak, /* D - Pattern break */ gmpcNone, /* E - E-commands (see below) */ gmpcSetSpeed, /* F - Set speed/tempo */ }; /* Conversion table from Protracker E-command numbers to GMP commands: */ static uchar ptECommands[16] = { gmpcNone, /* E0 - Toggle filter */ gmpcFineSlideUp, /* E1 - Fine portamento up */ gmpcFineSlideDown, /* E2 - Fine portamento down */ gmpcNone, /* E3 - Set glissando control */ gmpcNone, /* E4 - Set vibrato type */ gmpcNone, /* E5 - Set finetune */ gmpcPatternLoop, /* E6 - Pattern loop */ gmpcNone, /* E7 - Set tremolo type */ gmpcSetPanning16, /* E8 - Set panning (extension) */ gmpcPTRetrig, /* E9 - Retrig note */ gmpcFineVolSlideUp, /* EA - Fine volume slide up */ gmpcFineVolSlideDown, /* EB - Fine volume slide down */ gmpcNoteCut, /* EC - Note cut */ gmpcNoteDelay, /* ED - Note delay */ gmpcPatternDelay, /* EE - Pattern delay */ gmpcNone /* EF - Invert loop */ }; /****************************************************************************\ * Module loader buffers and file handle. These variables are static * instead of local so that a separate deallocation can be used which * will be called before exiting in error situations \****************************************************************************/ static fileHandle f; /* file handle for module file */ static int fileOpened; /* 1 if module file has been opened */ static gmpModule *module; /* pointer to GMP module structure */ static modHeader *header; /* pointer to PT module header */ static uchar *instUsed; /* instrument used flags */ static int extOctaves; /* 1 if extended octaves are needed */ static uchar *ptPatt; /* pointer to PT pattern data */ static gmpPattern *convPatt; /* pointer to converted pattern data*/ static uchar *smpBuf; /* sample loading buffer */ static unsigned numChans; /* number of channels in module */ /****************************************************************************\ * * Function: int ConvertPattern(uchar *srcPatt, gmpPattern *destPatt, * unsigned *convLen); * * Description: Converts a pattern from Protracker to GMP internal format. * Also updates extOctaves and instUsed flags. * * Input: uchar *srcPatt pointer to pattern in PT format * gmpPattern *destPatt pointer to destination GMP pattern * unsigned *convLen pointer to converted pattern length * * Returns: MIDAS error code. Converted pattern length (in bytes, * including header) is written to *convLen. * \****************************************************************************/ static int ConvertPattern(uchar *srcPatt, gmpPattern *destPatt, unsigned *convLen) { uchar *src = srcPatt; uchar *dest = ((uchar*)destPatt) + sizeof(gmpPattern); unsigned row, chan; unsigned period, len; int i; uchar note, inst, command, infobyte, compInfo; int legalCommand; for ( row = 0; row < 64; row++ ) { for ( chan = 0; chan < numChans; chan++ ) { /* Get period number from pattern data: */ period = ((unsigned) (*(src) & 0x0F) << 8) | ((unsigned) *(src+1)); /* Get instrument number from pattern data: */ inst = ((*src) & 0xF0) | (*(src+2) >> 4); /* Get command number from pattern data: */ command = *(src+2) & 0x0F; /* Get command infobyte from pattern data: */ infobyte = *(src+3); /* Point src to next channel pattern data: */ src += 4; /* Set compression info to zero to mark currently there is no new data: */ compInfo = 0; /* Check if there is a new note: */ note = 0; if ( period != 0 ) { /* Find the that corresponds to current period value: */ for ( i = 0; i < 6*12; i++ ) { if ( period >= ptPeriods[i] ) { note = i; break; } } /* If we reached the end of period table, the pattern data is invalid: */ if ( i == (6*12) ) return errInvalidPatt; /* Convert note number to GMP internal format: */ note = ((note / 12) << 4) | (note % 12); /* Check if extended octaves should be enabled: */ if ( (note < 0x10) || (note > 0x3F) ) extOctaves = 1; /* Mark that there is a new note: */ compInfo |= 32; } /* Check if there is a new instrument: */ if ( inst != 0 ) { /* Check that the instrument number is legal: */ if ( inst > 31 ) return errInvalidPatt; /* Mark instrument used: */ instUsed[inst-1] = 1; /* Mark that there is a new instrument: */ compInfo |= 32; } /* Check if there is a command: */ if ( (command != 0) || (infobyte != 0) ) { /* Flag that we have a legal command: */ legalCommand = 1; switch ( command ) { case 0x08: /* Command 8 - possibly Set Panning. Convert to "Set Panning" -command if the infobyte is a legal DMP-compatible panning value: */ if ( (infobyte <= 0x80) || (infobyte == 0xA4) ) { command = gmpcSetPanning; } else { legalCommand = 0; } break; case 0x0E: /* Command E - convert command number: */ command = ptECommands[infobyte >> 4]; infobyte = infobyte & 0x0F; break; case 0x0F: /* Protracker command 0Fh - set speed or set tempo. If infobyte > 32, the BPM tempo is changed: */ if ( infobyte > 32 ) command = gmpcSetTempo; else command = gmpcSetSpeed; break; default: /* Convert command to GMP command number: */ command = ptCommands[command]; break; } /* Mark that there is a command: */ if ( legalCommand ) compInfo |= 128; } /* If the compression information is nonzero, there is some data for this channel: */ if ( compInfo != 0 ) { /* Set channel number to lower 5 bits of the compression info and write it to destination: */ compInfo |= chan; *(dest++) = compInfo; /* Check if there a note+instrument pair: */ if ( compInfo & 32 ) { /* Write note number if there is a new note, otherwise write 0xFF as note: */ if ( period != 0 ) *(dest++) = note; else *(dest++) = 0xFF; /* Write instrument number if there is a new instrument, otherwise write 0xFF as instrument: */ if ( inst != 0 ) *(dest++) = inst-1; else *(dest++) = 0xFF; } /* Check if there is a command: */ if ( compInfo & 128 ) { /* Write command and command infobyte: */ *(dest++) = command; *(dest++) = infobyte; } } } /* Write row end marker: */ *(dest++) = 0; } /* Write number of rows to pattern header: */ destPatt->rows = 64; /* Calculate converted pattern length: */ len = (unsigned) (dest - ((uchar*) destPatt)); /* Write converted pattern length to header and return it in *convLen: */ destPatt->length = len; *convLen = len; return OK; } /****************************************************************************\ * * Function: void LoadError(void) * * Description: Stops loading the module, deallocates all buffers and closes * the file. * \****************************************************************************/ #define condFree(x) { if ( x != NULL ) if ( memFree(x) != OK) return; } static void LoadError(void) { /* Close file if opened. Do not process errors. */ if ( fileOpened ) if ( fileClose(f) != OK ) return; /* Attempt to deallocate module if allocated. Do not process errors. */ if ( module != NULL ) if ( gmpFreeModule(module) != OK ) return; /* Deallocate structures if allocated: */ condFree(header) condFree(instUsed) condFree(smpBuf) } /****************************************************************************\ * * Function: int gmpLoadMOD(char *fileName, int addSD, mpModule **module) * * Description: Loads a Protracker module to memory in Generic Module Player * module format * * Input: char *fileName module file name * int addSD 1 if module samples should be added to * the current Sound Device, 0 if not * int (*SampleCallback)(...) pointer to callback function that * will be called after sample has been * added to Sound Device, but before * sample data is deallocated * mpModule **module pointer to GMP module pointer * * Returns: MIDAS error code * \****************************************************************************/ int CALLING gmpLoadMOD(char *fileName, int addSD, int (CALLING *SampleCallback)(sdSample *sdsmp, gmpSample *gmpsmp), gmpModule **_module) { int error; /* MIDAS error code */ modInstHdr *modi; gmpInstrument *inst; unsigned i; unsigned numPatts, n; ulong maxSample; static unsigned convPattLen; static long filePos; unsigned slength, loopStart, loopLength; gmpSample *sample; static sdSample sdSmp; #ifndef NOEMS uchar *patt; #endif /* point buffers to NULL and set fileOpened to 0 so that LoadError() can be called at any point: */ fileOpened = 0; module = NULL; header = NULL; instUsed = NULL; ptPatt = NULL; convPatt = NULL; smpBuf = NULL; /* Allocate memory for Protracker module header: */ if ( (error = memAlloc(sizeof(modHeader), (void**) &header)) != OK ) PASSERR() /* Open module file: */ if ( (error = fileOpen(fileName, fileOpenRead, &f)) != OK ) PASSERR() fileOpened = 1; /* Allocate memory for the module structure: */ if ( (error = memAlloc(sizeof(gmpModule), (void**) &module)) != OK ) PASSERR() /* Clear module structure so that it can safely be deallocated with gmpFreeModule() at any point: */ module->panning = NULL; module->songData = NULL; module->instruments = NULL; module->patterns = NULL; /* read Protracker module header: */ if ( (error = fileRead(f, header, sizeof(modHeader))) != OK ) PASSERR() numChans = 0; /* Check the module signature to determine number of channels: */ if ( mMemEqual(&header->sign[0], "M.K.", 4) ) numChans = 4; if ( mMemEqual(&header->sign[0], "M!K!", 4) ) numChans = 4; if ( mMemEqual(&header->sign[0], "FLT4", 4) ) numChans = 4; if ( mMemEqual(&header->sign[0], "OCTA", 4) ) numChans = 8; if ( mMemEqual(&header->sign[1], "CHN", 3) ) { /* xCHN, where x is the number of channels */ numChans = header->sign[0] - '0'; } if ( mMemEqual(&header->sign[2], "CH", 2) ) { /* xxCH, where xx is the number of channels */ numChans = (header->sign[0] - '0') * 10 + (header->sign[1] - '0'); } if ( mMemEqual(&header->sign[0], "TDZ", 3) ) { /* TDZx, where x is the number of channels */ numChans = header->sign[3] - '0'; } /* If number of channels is undetermined, the signature is invalid. */ if ( numChans == 0 ) { ERROR(errInvalidModule, ID_gmpLoadMOD); LoadError(); return errInvalidModule; } module->numChannels = numChans; /* store number of channels */ /* Copy song name: */ mMemCopy(&module->name[0], &header->songName[0], 20); module->name[20] = 0; /* force terminating '\0' */ module->songLength = header->songLength; /* copy song length */ module->numInsts = 31; /* set number of instruments */ module->playMode = gmpPT; /* set Protracker playing mode */ module->masterVolume = 64; /* maximum master volume */ module->speed = 6; /* initial speed 6 */ module->tempo = 125; /* initial tempo 125 BPM */ module->playFlags.extOctaves = 0; /* extended octaves not needed */ /* Check if the header contains a valid restart position, and if it does, use it: */ if ( (header->restart != 127) && (header->restart < header->songLength) ) module->restart = header->restart; else module->restart = 0; /* Allocate memory for channel initial panning positions: */ if ( (error = memAlloc(32 * sizeof(int), (void**) &module->panning)) != OK ) PASSERR() /* Set up initial panning positions: (LRRL LRRL LRRL...) */ for ( i = 0; i < numChans; i++ ) { if ( ((i & 3) == 0) || ((i & 3) == 3) ) module->panning[i] = 0x00; else module->panning[i] = 0x80; } /* Find the number of patterns in file by searching through the song data to find the highest pattern number: */ numPatts = 0; for ( i = 0; i < 128; i++ ) { if ( header->songData[i] > numPatts ) numPatts = header->songData[i]; } numPatts++; module->numPatts = numPatts; /* store number of patterns */ /* Allocate memory for song data: */ if ( (error = memAlloc(sizeof(ushort) * module->songLength, (void**) &module->songData)) != OK ) PASSERR() /* Copy song data: */ for ( i = 0; i < module->songLength; i++ ) module->songData[i] = header->songData[i]; /* Allocate memory for pattern pointers: */ if ( (error = memAlloc(sizeof(gmpPattern*) * module->numPatts, (void**) &module->patterns)) != OK ) PASSERR() /* Set all pattern pointers to NULL to mark them unallocated: */ for ( i = 0; i < module->numPatts; i++ ) module->patterns[i] = NULL; /* Allocate memory for instrument pointers: */ if ( (error = memAlloc(sizeof(gmpInstrument*) * module->numInsts, (void**) &module->instruments)) != OK ) PASSERR() /* Set all instrument pointers to NULL to mark them unallocated: */ for ( i = 0; i < module->numInsts; i++ ) module->instruments[i] = NULL; /* Allocate memory for instrument used flags: */ if ( (error = memAlloc(31, (void**)&instUsed)) != OK ) PASSERR() /* Mark all instruments unused: */ for ( i = 0; i < module->numInsts; i++ ) instUsed[i] = 0; /* Now convert all 16-bit fields in the Protracker module instrument headers to little-endian and find maximum sample length: *!!* */ maxSample = 0; for ( i = 0; i < module->numInsts; i++ ) { modi = &header->instruments[i]; modi->slength = SWAP16(modi->slength); if ( maxSample < 2L * (ulong) modi->slength ) maxSample = 2L * (ulong) modi->slength; modi->loopStart = SWAP16(modi->loopStart); modi->loopLength = SWAP16(modi->loopLength); } /* Check that the maximum sample length is below the Sound Device limit:*/ if ( maxSample > SMPMAX ) { ERROR(errInvalidInst, ID_gmpLoadMOD); LoadError(); return errInvalidInst; } /* Allocate memory for pattern loading buffer: */ if ( (error = memAlloc(numChans * 256, (void**) &ptPatt)) != OK ) PASSERR() /* Allocate memory for pattern conversion buffer: (maximum GMP pattern data size is 6 bytes per row per channel plus header) */ if ( (error = memAlloc(numChans * 64 * 6 + sizeof(gmpPattern), (void**) &convPatt)) != OK ) PASSERR() /* Load and convert all patterns: */ for ( i = 0; i < numPatts; i++ ) { /* Read pattern data: */ if ( (error = fileRead(f, ptPatt, 256 * numChans)) != OK ) PASSERR() /* Convert the pattern data, checking the instruments used: */ if ( (error = ConvertPattern(ptPatt, convPatt, &convPattLen)) != OK ) PASSERR() #ifndef NOEMS if ( mUseEMS == 1 ) /* is EMS memory used? */ { /* Allocate EMS for converted pattern data for current pattern in module: */ if ( (error = emsAlloc(convPattLen, (emsBlock**) &module->patterns[i])) != OK ) PASSERR() /* Map the allocated EMS block to conventional memory: */ if ( (error = emsMap((emsBlock*) module->patterns[i], (void**) &patt)) != OK) PASSERR() /* Copy the converted pattern data to the EMS block: */ mMemCopy(patt, convPatt, convPattLen); } else #endif { /* Allocate memory for converted pattern data for current pattern in module: */ if ( (error = memAlloc(convPattLen, (void**)&module->patterns[i])) != OK ) PASSERR() /* Copy the converted pattern data to the EMS block: */ mMemCopy(module->patterns[i], convPatt, convPattLen); } } /* Deallocate pattern conversion buffer: */ if ( (error = memFree(convPatt)) != OK ) PASSERR() convPatt = NULL; /* Deallocate pattern loading buffer: */ if ( (error = memFree(ptPatt)) != OK ) PASSERR() ptPatt = NULL; /* If samples should be added to Sound Device, allocate memory for sample loading buffer: */ if ( addSD ) { if ( (error = memAlloc(maxSample, (void**) &smpBuf)) != OK ) PASSERR() } /* Get current file position: */ if ( (error = fileGetPosition(f, &filePos)) != OK ) PASSERR() /* Load all samples and convert sample and instrument data: */ for ( i = 0; i < module->numInsts; i++ ) { /* Seek to the beginning of current sample: */ if ( (error = fileSeek(f, filePos, fileSeekAbsolute)) != OK ) PASSERR() /* Point modi to current Protracker module instrument: */ modi = &header->instruments[i]; /* Convert sample length, loop start and loop end to bytes: */ slength = 2 * ((unsigned) modi->slength); loopStart = 2 * ((unsigned) modi->loopStart); loopLength = 2 * ((unsigned) modi->loopLength); /* Set file position to the beginning of next sample: */ filePos += (ulong) slength; /* Allocate memory for this instrument structure: (add space for one sample if the instrument is used) */ if ( instUsed[i] ) { if ( (error = memAlloc(sizeof(gmpInstrument) + sizeof(gmpSample), (void**) &module->instruments[i])) != OK ) PASSERR() } else { if ( (error = memAlloc(sizeof(gmpInstrument), (void**) &module->instruments[i])) != OK ) PASSERR() } /* Point inst to current instrument structure: */ inst = module->instruments[i]; /* Mark there are no sample for the instrument: */ inst->numSamples = 0; /* Copy instrument name: */ mMemCopy(&inst->name[0], &modi->iname[0], 22); inst->name[22] = 0; /* force terminating '\0' */ /* Mark the instrument has no sample number table for keyboard notes: */ inst->noteSamples = NULL; /* Mark the instrument has no volume or panning envelopes: */ inst->volEnvelope = NULL; inst->panEnvelope = NULL; /* Mark the instrument has no FT2 auto-vibrato: */ inst->vibType = inst->vibSweep = inst->vibDepth = inst->vibRate = 0; /* Check if the instrument is used in the module: */ if ( instUsed[i] ) { /* The instrument is used - mark it used and point sample to its sample information: */ inst->used = 1; inst->numSamples = 1; sample = &inst->samples[0]; /* Mark there is no sample data and the sample has not been added to Sound Device: */ sample->sample = NULL; sample->sdHandle = 0; /* Copy sample loop start and calculate loop end: */ sample->loop1Start = loopStart; sample->loop1End = loopStart + loopLength; /* If sample loop end is past byte 2, the sample is looping: (Protracker uses loop start 0, length 2 for no loop, Fasttracker 1 uses start 0, length 0) */ if ( sample->loop1End > 2 ) { /* Looping sample - set loop types and limit sample length to loop end: */ sample->loopMode = sdLoopAmiga; sample->loop1Type = loopUnidir; if ( slength > sample->loop1End ) slength = sample->loop1End; sample->sampleLength = slength; } else { /* Sample not looping: */ sample->loopMode = sdLoopAmigaNone; sample->loop1Type = loopNone; sample->sampleLength = slength; } /* Copy sample default volume: */ sample->volume = modi->volume; /* Copy sample finetune value: */ sample->finetune = modi->finetune; /* Set sample base tune to 0: */ sample->baseTune = 0; /* Set sample default panning position to middle: */ sample->panning = panMiddle; /* Check if there is sample data for this sample: */ if ( slength != 0 ) { /* If sample data should not be added to Sound Device, allocate memory for the sample data and point smpBuf there: */ if ( !addSD ) { smpBuf = NULL; if ( (error = memAlloc(slength, (void**)&smpBuf)) != OK ) PASSERR() sample->sample = smpBuf; } else { /* Sample is added to the Sound Device - sample data is not available: */ sample->sample = NULL; } /* There is sample data - load sample: */ if ( (error = fileRead(f, smpBuf, slength)) != OK ) PASSERR() /* Convert sample data from signed to unsigned: */ for ( n = 0; n < slength; n++ ) smpBuf[n] ^= 0x80; /* Set Sound Device sample type: */ sdSmp.sampleType = smp8bit; sample->sampleType = smp8bit; /* Set Sound Device sample position in memory: */ sdSmp.samplePos = sdSmpConv; /* Point Sound Device sample data to sample loading buffer: */ sdSmp.sample = smpBuf; /* Point smpBuf to NULL if the sample is not added to Sound Device to mark it should not be deallocated: */ if ( !addSD ) smpBuf = NULL; } else { /* Mark there is no sample data: */ sdSmp.sampleType = smpNone; sample->sampleType = smpNone; sdSmp.samplePos = sdSmpNone; sdSmp.sample = NULL; } sdSmp.sampleLength = slength; sdSmp.loopMode = sample->loopMode; sdSmp.loop1Start = sample->loop1Start; sdSmp.loop1End = sample->loop1End; sdSmp.loop1Type = sample->loop1Type; /* Set up the rest of Sound Device sample structure so that the sample can be added to the Sound Device: */ if ( addSD ) { /* Add the sample to Sound Device and store the Sound Device sample handle in sample->sdHandle: */ if ( (error = gmpSD->AddSample(&sdSmp, 1, &sample->sdHandle)) != OK) PASSERR() /* Call sample callback if used: */ if ( SampleCallback != NULL ) { if ( (error = SampleCallback(&sdSmp, sample)) != OK ) PASSERR() } } else { /* Sample data has not been added to Sound Device: */ sample->sdHandle = 0; } } else { /* Sample is not used - there is no sample data: */ inst->used = 0; inst->numSamples = 0; } } /* Deallocate sample loading buffer if allocated: */ if ( addSD ) { if ( (error = memFree(smpBuf)) != OK ) PASSERR() } smpBuf = NULL; /* Deallocate instrument use flags: */ if ( (error = memFree(instUsed)) != OK ) PASSERR() instUsed = 0; /* Set extended octaves needed flag in module header if notes in extended octaves were found: */ if ( extOctaves ) module->playFlags.extOctaves = 1; /* Now we are finished loading. Close module file: */ if ( (error = fileClose(f)) != OK) PASSERR() fileOpened = 0; /* Deallocate Protracker module header: */ if ( (error = memFree(header)) != OK ) PASSERR(); /* Return module pointer in *module: */ *_module = module; return OK; } /* * $Log: loadmod.c,v $ * Revision 1.8 1997/01/25 15:23:16 pekangas * The file is now closed properly if loading terminates to an error * * Revision 1.7 1997/01/16 18:41:59 pekangas * Changed copyright messages to Housemarque * * Revision 1.6 1996/12/30 23:38:23 jpaana * Cleaned up LoadError * * Revision 1.5 1996/09/01 15:42:03 pekangas * Changed to use new style slides * * Revision 1.4 1996/07/13 20:11:19 pekangas * Eliminated Visual C warnings * * Revision 1.3 1996/07/13 18:23:27 pekangas * Fixed to compile with Visual C * * Revision 1.2 1996/05/24 16:20:36 jpaana * Fixed to work with gcc * * Revision 1.1 1996/05/22 20:49:33 pekangas * Initial revision * */