Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

load_mdl.cpp

Go to the documentation of this file.
00001 /*
00002  * This program is  free software; you can redistribute it  and modify it
00003  * under the terms of the GNU  General Public License as published by the
00004  * Free Software Foundation; either version 2  of the license or (at your
00005  * option) any later version.
00006  *
00007  * Authors: Olivier Lapicque <olivierl@jps.net>
00008 */
00009 
00011 // DigiTracker (MDL) module loader          //
00013 #include "stdafx.h"
00014 #include "sndfile.h"
00015 
00016 //#pragma warning(disable:4244)
00017 
00018 typedef struct MDLSONGHEADER
00019 {
00020         DWORD id;       // "DMDL" = 0x4C444D44
00021         BYTE version;
00022 } Q_PACKED MDLSONGHEADER;
00023 
00024 
00025 typedef struct MDLINFOBLOCK
00026 {
00027         CHAR songname[32];
00028         CHAR composer[20];
00029         WORD norders;
00030         WORD repeatpos;
00031         BYTE globalvol;
00032         BYTE speed;
00033         BYTE tempo;
00034         BYTE channelinfo[32];
00035         BYTE seq[256];
00036 } Q_PACKED MDLINFOBLOCK;
00037 
00038 
00039 typedef struct MDLPATTERNDATA
00040 {
00041         BYTE channels;
00042         BYTE lastrow;   // nrows = lastrow+1
00043         CHAR name[16];
00044         WORD data[1];
00045 } Q_PACKED MDLPATTERNDATA;
00046 
00047 
00048 void ConvertMDLCommand(MODCOMMAND *m, UINT eff, UINT data)
00049 //--------------------------------------------------------
00050 {
00051         UINT command = 0, param = data;
00052         switch(eff)
00053         {
00054         case 0x01:      command = CMD_PORTAMENTOUP; break;
00055         case 0x02:      command = CMD_PORTAMENTODOWN; break;
00056         case 0x03:      command = CMD_TONEPORTAMENTO; break;
00057         case 0x04:      command = CMD_VIBRATO; break;
00058         case 0x05:      command = CMD_ARPEGGIO; break;
00059         case 0x07:      command = (param < 0x20) ? CMD_SPEED : CMD_TEMPO; break;
00060         case 0x08:      command = CMD_PANNING8; param <<= 1; break;
00061         case 0x0B:      command = CMD_POSITIONJUMP; break;
00062         case 0x0C:      command = CMD_GLOBALVOLUME; break;
00063         case 0x0D:      command = CMD_PATTERNBREAK; param = (data & 0x0F) + (data>>4)*10; break;
00064         case 0x0E:
00065                 command = CMD_S3MCMDEX;
00066                 switch(data & 0xF0)
00067                 {
00068                 case 0x00:      command = 0; break; // What is E0x in MDL (there is a bunch) ?
00069                 case 0x10:      if (param & 0x0F) { param |= 0xF0; command = CMD_PANNINGSLIDE; } else command = 0; break;
00070                 case 0x20:      if (param & 0x0F) { param = (param << 4) | 0x0F; command = CMD_PANNINGSLIDE; } else command = 0; break;
00071                 case 0x30:      param = (data & 0x0F) | 0x10; break; // glissando
00072                 case 0x40:      param = (data & 0x0F) | 0x30; break; // vibrato waveform
00073                 case 0x60:      param = (data & 0x0F) | 0xB0; break;
00074                 case 0x70:      param = (data & 0x0F) | 0x40; break; // tremolo waveform
00075                 case 0x90:      command = CMD_RETRIG; param &= 0x0F; break;
00076                 case 0xA0:      param = (data & 0x0F) << 4; command = CMD_GLOBALVOLSLIDE; break;
00077                 case 0xB0:      param = data & 0x0F; command = CMD_GLOBALVOLSLIDE; break;
00078                 case 0xF0:      param = ((data >> 8) & 0x0F) | 0xA0; break;
00079                 }
00080                 break;
00081         case 0x0F:      command = CMD_SPEED; break;
00082         case 0x10:      if ((param & 0xF0) != 0xE0) { command = CMD_VOLUMESLIDE; if ((param & 0xF0) == 0xF0) param = ((param << 4) | 0x0F); else param >>= 2; } break;
00083         case 0x20:      if ((param & 0xF0) != 0xE0) { command = CMD_VOLUMESLIDE; if ((param & 0xF0) != 0xF0) param >>= 2; } break;
00084         case 0x30:      command = CMD_RETRIG; break;
00085         case 0x40:      command = CMD_TREMOLO; break;
00086         case 0x50:      command = CMD_TREMOR; break;
00087         case 0xEF:      if (param > 0xFF) param = 0xFF; command = CMD_OFFSET; break;
00088         }
00089         if (command)
00090         {
00091                 m->command = command;
00092                 m->param = param;
00093         }
00094 }
00095 
00096 
00097 void UnpackMDLTrack(MODCOMMAND *pat, UINT nChannels, UINT nRows, UINT nTrack, const BYTE *lpTracks)
00098 //-------------------------------------------------------------------------------------------------
00099 {
00100         MODCOMMAND cmd, *m = pat;
00101         UINT len = *((WORD *)lpTracks);
00102         UINT pos = 0, row = 0, i;
00103         lpTracks += 2;
00104         for (UINT ntrk=1; ntrk<nTrack; ntrk++)
00105         {
00106                 lpTracks += len;
00107                 len = *((WORD *)lpTracks);
00108                 lpTracks += 2;
00109         }
00110         cmd.note = cmd.instr = 0;
00111         cmd.volcmd = cmd.vol = 0;
00112         cmd.command = cmd.param = 0;
00113         while ((row < nRows) && (pos < len))
00114         {
00115                 UINT xx;
00116                 BYTE b = lpTracks[pos++];
00117                 xx = b >> 2;
00118                 switch(b & 0x03)
00119                 {
00120                 case 0x01:
00121                         for (i=0; i<=xx; i++)
00122                         {
00123                                 if (row) *m = *(m-nChannels);
00124                                 m += nChannels;
00125                                 row++;
00126                                 if (row >= nRows) break;
00127                         }
00128                         break;
00129 
00130                 case 0x02:
00131                         if (xx < row) *m = pat[nChannels*xx];
00132                         m += nChannels;
00133                         row++;
00134                         break;
00135 
00136                 case 0x03:
00137                         {
00138                                 cmd.note = (xx & 0x01) ? lpTracks[pos++] : 0;
00139                                 cmd.instr = (xx & 0x02) ? lpTracks[pos++] : 0;
00140                                 cmd.volcmd = cmd.vol = 0;
00141                                 cmd.command = cmd.param = 0;
00142                                 if ((cmd.note < 120-12) && (cmd.note)) cmd.note += 12;
00143                                 UINT volume = (xx & 0x04) ? lpTracks[pos++] : 0;
00144                                 UINT commands = (xx & 0x08) ? lpTracks[pos++] : 0;
00145                                 UINT command1 = commands & 0x0F;
00146                                 UINT command2 = commands & 0xF0;
00147                                 UINT param1 = (xx & 0x10) ? lpTracks[pos++] : 0;
00148                                 UINT param2 = (xx & 0x20) ? lpTracks[pos++] : 0;
00149                                 if ((command1 == 0x0E) && ((param1 & 0xF0) == 0xF0) && (!command2))
00150                                 {
00151                                         param1 = ((param1 & 0x0F) << 8) | param2;
00152                                         command1 = 0xEF;
00153                                         command2 = param2 = 0;
00154                                 }
00155                                 if (volume)
00156                                 {
00157                                         cmd.volcmd = VOLCMD_VOLUME;
00158                                         cmd.vol = (volume+1) >> 2;
00159                                 }
00160                                 ConvertMDLCommand(&cmd, command1, param1);
00161                                 if ((cmd.command != CMD_SPEED)
00162                                  && (cmd.command != CMD_TEMPO)
00163                                  && (cmd.command != CMD_PATTERNBREAK))
00164                                         ConvertMDLCommand(&cmd, command2, param2);
00165                                 *m = cmd;
00166                                 m += nChannels;
00167                                 row++;
00168                         }
00169                         break;
00170 
00171                 // Empty Slots
00172                 default:
00173                         row += xx+1;
00174                         m += (xx+1)*nChannels;
00175                         if (row >= nRows) break;
00176                 }
00177         }
00178 }
00179 
00180 
00181 
00182 BOOL CSoundFile::ReadMDL(const BYTE *lpStream, DWORD dwMemLength)
00183 //---------------------------------------------------------------
00184 {
00185         DWORD dwMemPos, dwPos, blocklen, dwTrackPos;
00186         const MDLSONGHEADER *pmsh = (const MDLSONGHEADER *)lpStream;
00187         MDLINFOBLOCK *pmib;
00188         MDLPATTERNDATA *pmpd;
00189         UINT i,j, norders = 0, npatterns = 0, ntracks = 0;
00190         UINT ninstruments = 0, nsamples = 0;
00191         WORD block;
00192         WORD patterntracks[MAX_PATTERNS*32];
00193         BYTE smpinfo[MAX_SAMPLES];
00194         BYTE insvolenv[MAX_INSTRUMENTS];
00195         BYTE inspanenv[MAX_INSTRUMENTS];
00196         LPCBYTE pvolenv, ppanenv, ppitchenv;
00197         UINT nvolenv, npanenv, npitchenv;
00198 
00199         if ((!lpStream) || (dwMemLength < 1024)) return FALSE;
00200         if ((pmsh->id != 0x4C444D44) || ((pmsh->version & 0xF0) > 0x10)) return FALSE;
00201         memset(patterntracks, 0, sizeof(patterntracks));
00202         memset(smpinfo, 0, sizeof(smpinfo));
00203         memset(insvolenv, 0, sizeof(insvolenv));
00204         memset(inspanenv, 0, sizeof(inspanenv));
00205         dwMemPos = 5;
00206         dwTrackPos = 0;
00207         pvolenv = ppanenv = ppitchenv = NULL;
00208         nvolenv = npanenv = npitchenv = 0;
00209         m_nSamples = m_nInstruments = 0;
00210         while (dwMemPos+6 < dwMemLength)
00211         {
00212                 block = *((WORD *)(lpStream+dwMemPos));
00213                 blocklen = *((DWORD *)(lpStream+dwMemPos+2));
00214                 dwMemPos += 6;
00215                 if (dwMemPos + blocklen > dwMemLength)
00216                 {
00217                         if (dwMemPos == 11) return FALSE;
00218                         break;
00219                 }
00220                 switch(block)
00221                 {
00222                 // IN: infoblock
00223                 case 0x4E49:
00224                         pmib = (MDLINFOBLOCK *)(lpStream+dwMemPos);
00225                         memcpy(m_szNames[0], pmib->songname, 32);
00226                         norders = pmib->norders;
00227                         if (norders > MAX_ORDERS) norders = MAX_ORDERS;
00228                         m_nRestartPos = pmib->repeatpos;
00229                         m_nDefaultGlobalVolume = pmib->globalvol;
00230                         m_nDefaultTempo = pmib->tempo;
00231                         m_nDefaultSpeed = pmib->speed;
00232                         m_nChannels = 4;
00233                         for (i=0; i<32; i++)
00234                         {
00235                                 ChnSettings[i].nVolume = 64;
00236                                 ChnSettings[i].nPan = (pmib->channelinfo[i] & 0x7F) << 1;
00237                                 if (pmib->channelinfo[i] & 0x80)
00238                                         ChnSettings[i].dwFlags |= CHN_MUTE;
00239                                 else
00240                                         m_nChannels = i+1;
00241                         }
00242                         for (j=0; j<norders; j++) Order[j] = pmib->seq[j];
00243                         break;
00244                 // ME: song message
00245                 case 0x454D:
00246                         if (blocklen)
00247                         {
00248                                 if (m_lpszSongComments) delete m_lpszSongComments;
00249                                 m_lpszSongComments = new char[blocklen];
00250                                 if (m_lpszSongComments)
00251                                 {
00252                                         memcpy(m_lpszSongComments, lpStream+dwMemPos, blocklen);
00253                                         m_lpszSongComments[blocklen-1] = 0;
00254                                 }
00255                         }
00256                         break;
00257                 // PA: Pattern Data
00258                 case 0x4150:
00259                         npatterns = lpStream[dwMemPos];
00260                         if (npatterns > MAX_PATTERNS) npatterns = MAX_PATTERNS;
00261                         dwPos = dwMemPos + 1;
00262                         for (i=0; i<npatterns; i++)
00263                         {
00264                                 if (dwPos+18 >= dwMemLength) break;
00265                                 pmpd = (MDLPATTERNDATA *)(lpStream + dwPos);
00266                                 if (pmpd->channels > 32) break;
00267                                 PatternSize[i] = pmpd->lastrow+1;
00268                                 if (m_nChannels < pmpd->channels) m_nChannels = pmpd->channels;
00269                                 dwPos += 18 + 2*pmpd->channels;
00270                                 for (j=0; j<pmpd->channels; j++)
00271                                 {
00272                                         patterntracks[i*32+j] = pmpd->data[j];
00273                                 }
00274                         }
00275                         break;
00276                 // TR: Track Data
00277                 case 0x5254:
00278                         if (dwTrackPos) break;
00279                         ntracks = *((WORD *)(lpStream+dwMemPos));
00280                         dwTrackPos = dwMemPos+2;
00281                         break;
00282                 // II: Instruments
00283                 case 0x4949:
00284                         ninstruments = lpStream[dwMemPos];
00285                         dwPos = dwMemPos+1;
00286                         for (i=0; i<ninstruments; i++)
00287                         {
00288                                 UINT nins = lpStream[dwPos];
00289                                 if ((nins >= MAX_INSTRUMENTS) || (!nins)) break;
00290                                 if (m_nInstruments < nins) m_nInstruments = nins;
00291                                 if (!Headers[nins])
00292                                 {
00293                                         UINT note = 12;
00294                                         if ((Headers[nins] = new INSTRUMENTHEADER) == NULL) break;
00295                                         INSTRUMENTHEADER *penv = Headers[nins];
00296                                         memset(penv, 0, sizeof(INSTRUMENTHEADER));
00297                                         memcpy(penv->name, lpStream+dwPos+2, 32);
00298                                         penv->nGlobalVol = 64;
00299                                         penv->nPPC = 5*12;
00300                                         for (j=0; j<lpStream[dwPos+1]; j++)
00301                                         {
00302                                                 const BYTE *ps = lpStream+dwPos+34+14*j;
00303                                                 while ((note < (UINT)(ps[1]+12)) && (note < 120))
00304                                                 {
00305                                                         penv->NoteMap[note] = note+1;
00306                                                         if (ps[0] < MAX_SAMPLES)
00307                                                         {
00308                                                                 int ismp = ps[0];
00309                                                                 penv->Keyboard[note] = ps[0];
00310                                                                 Ins[ismp].nVolume = ps[2];
00311                                                                 Ins[ismp].nPan = ps[4] << 1;
00312                                                                 Ins[ismp].nVibType = ps[11];
00313                                                                 Ins[ismp].nVibSweep = ps[10];
00314                                                                 Ins[ismp].nVibDepth = ps[9];
00315                                                                 Ins[ismp].nVibRate = ps[8];
00316                                                         }
00317                                                         penv->nFadeOut = (ps[7] << 8) | ps[6];
00318                                                         if (penv->nFadeOut == 0xFFFF) penv->nFadeOut = 0;
00319                                                         note++;
00320                                                 }
00321                                                 // Use volume envelope ?
00322                                                 if (ps[3] & 0x80)
00323                                                 {
00324                                                         penv->dwFlags |= ENV_VOLUME;
00325                                                         insvolenv[nins] = (ps[3] & 0x3F) + 1;
00326                                                 }
00327                                                 // Use panning envelope ?
00328                                                 if (ps[5] & 0x80)
00329                                                 {
00330                                                         penv->dwFlags |= ENV_PANNING;
00331                                                         inspanenv[nins] = (ps[5] & 0x3F) + 1;
00332                                                 }
00333                                         }
00334                                 }
00335                                 dwPos += 34 + 14*lpStream[dwPos+1];
00336                         }
00337                         for (j=1; j<=m_nInstruments; j++) if (!Headers[j])
00338                         {
00339                                 Headers[j] = new INSTRUMENTHEADER;
00340                                 if (Headers[j]) memset(Headers[j], 0, sizeof(INSTRUMENTHEADER));
00341                         }
00342                         break;
00343                 // VE: Volume Envelope
00344                 case 0x4556:
00345                         if ((nvolenv = lpStream[dwMemPos]) == 0) break;
00346                         if (dwMemPos + nvolenv*32 + 1 <= dwMemLength) pvolenv = lpStream + dwMemPos + 1;
00347                         break;
00348                 // PE: Panning Envelope
00349                 case 0x4550:
00350                         if ((npanenv = lpStream[dwMemPos]) == 0) break;
00351                         if (dwMemPos + npanenv*32 + 1 <= dwMemLength) ppanenv = lpStream + dwMemPos + 1;
00352                         break;
00353                 // FE: Pitch Envelope
00354                 case 0x4546:
00355                         if ((npitchenv = lpStream[dwMemPos]) == 0) break;
00356                         if (dwMemPos + npitchenv*32 + 1 <= dwMemLength) ppitchenv = lpStream + dwMemPos + 1;
00357                         break;
00358                 // IS: Sample Infoblock
00359                 case 0x5349:
00360                         nsamples = lpStream[dwMemPos];
00361                         dwPos = dwMemPos+1;
00362                         for (i=0; i<nsamples; i++, dwPos += 59)
00363                         {
00364                                 UINT nins = lpStream[dwPos];
00365                                 if ((nins >= MAX_SAMPLES) || (!nins)) continue;
00366                                 if (m_nSamples < nins) m_nSamples = nins;
00367                                 MODINSTRUMENT *pins = &Ins[nins];
00368                                 memcpy(m_szNames[nins], lpStream+dwPos+1, 32);
00369                                 memcpy(pins->name, lpStream+dwPos+33, 8);
00370                                 pins->nC4Speed = *((DWORD *)(lpStream+dwPos+41));
00371                                 pins->nLength = *((DWORD *)(lpStream+dwPos+45));
00372                                 pins->nLoopStart = *((DWORD *)(lpStream+dwPos+49));
00373                                 pins->nLoopEnd = pins->nLoopStart + *((DWORD *)(lpStream+dwPos+53));
00374                                 if (pins->nLoopEnd > pins->nLoopStart) pins->uFlags |= CHN_LOOP;
00375                                 pins->nGlobalVol = 64;
00376                                 if (lpStream[dwPos+58] & 0x01)
00377                                 {
00378                                         pins->uFlags |= CHN_16BIT;
00379                                         pins->nLength >>= 1;
00380                                         pins->nLoopStart >>= 1;
00381                                         pins->nLoopEnd >>= 1;
00382                                 }
00383                                 if (lpStream[dwPos+58] & 0x02) pins->uFlags |= CHN_PINGPONGLOOP;
00384                                 smpinfo[nins] = (lpStream[dwPos+58] >> 2) & 3;
00385                         }
00386                         break;
00387                 // SA: Sample Data
00388                 case 0x4153:
00389                         dwPos = dwMemPos;
00390                         for (i=1; i<=m_nSamples; i++) if ((Ins[i].nLength) && (!Ins[i].pSample) && (smpinfo[i] != 3) && (dwPos < dwMemLength))
00391                         {
00392                                 MODINSTRUMENT *pins = &Ins[i];
00393                                 UINT flags = (pins->uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S;
00394                                 if (!smpinfo[i])
00395                                 {
00396                                         dwPos += ReadSample(pins, flags, (LPSTR)(lpStream+dwPos), dwMemLength - dwPos);
00397                                 } else
00398                                 {
00399                                         DWORD dwLen = *((DWORD *)(lpStream+dwPos));
00400                                         dwPos += 4;
00401                                         if ((dwPos+dwLen <= dwMemLength) && (dwLen > 4))
00402                                         {
00403                                                 flags = (pins->uFlags & CHN_16BIT) ? RS_MDL16 : RS_MDL8;
00404                                                 ReadSample(pins, flags, (LPSTR)(lpStream+dwPos), dwLen);
00405                                         }
00406                                         dwPos += dwLen;
00407                                 }
00408                         }
00409                         break;
00410                 }
00411                 dwMemPos += blocklen;
00412         }
00413         // Unpack Patterns
00414         if ((dwTrackPos) && (npatterns) && (m_nChannels) && (ntracks))
00415         {
00416                 for (UINT ipat=0; ipat<npatterns; ipat++)
00417                 {
00418                         if ((Patterns[ipat] = AllocatePattern(PatternSize[ipat], m_nChannels)) == NULL) break;
00419                         for (UINT chn=0; chn<m_nChannels; chn++) if ((patterntracks[ipat*32+chn]) && (patterntracks[ipat*32+chn] <= ntracks))
00420                         {
00421                                 MODCOMMAND *m = Patterns[ipat] + chn;
00422                                 UnpackMDLTrack(m, m_nChannels, PatternSize[ipat], patterntracks[ipat*32+chn], lpStream+dwTrackPos);
00423                         }
00424                 }
00425         }
00426         // Set up envelopes
00427         for (UINT iIns=1; iIns<=m_nInstruments; iIns++) if (Headers[iIns])
00428         {
00429                 INSTRUMENTHEADER *penv = Headers[iIns];
00430                 // Setup volume envelope
00431                 if ((nvolenv) && (pvolenv) && (insvolenv[iIns]))
00432                 {
00433                         LPCBYTE pve = pvolenv;
00434                         for (UINT nve=0; nve<nvolenv; nve++, pve+=33) if (pve[0]+1 == insvolenv[iIns])
00435                         {
00436                                 WORD vtick = 1;
00437                                 penv->nVolEnv = 15;
00438                                 for (UINT iv=0; iv<15; iv++)
00439                                 {
00440                                         if (iv) vtick += pve[iv*2+1];
00441                                         penv->VolPoints[iv] = vtick;
00442                                         penv->VolEnv[iv] = pve[iv*2+2];
00443                                         if (!pve[iv*2+1])
00444                                         {
00445                                                 penv->nVolEnv = iv+1;
00446                                                 break;
00447                                         }
00448                                 }
00449                                 penv->nVolSustainBegin = penv->nVolSustainEnd = pve[31] & 0x0F;
00450                                 if (pve[31] & 0x10) penv->dwFlags |= ENV_VOLSUSTAIN;
00451                                 if (pve[31] & 0x20) penv->dwFlags |= ENV_VOLLOOP;
00452                                 penv->nVolLoopStart = pve[32] & 0x0F;
00453                                 penv->nVolLoopEnd = pve[32] >> 4;
00454                         }
00455                 }
00456                 // Setup panning envelope
00457                 if ((npanenv) && (ppanenv) && (inspanenv[iIns]))
00458                 {
00459                         LPCBYTE ppe = ppanenv;
00460                         for (UINT npe=0; npe<npanenv; npe++, ppe+=33) if (ppe[0]+1 == inspanenv[iIns])
00461                         {
00462                                 WORD vtick = 1;
00463                                 penv->nPanEnv = 15;
00464                                 for (UINT iv=0; iv<15; iv++)
00465                                 {
00466                                         if (iv) vtick += ppe[iv*2+1];
00467                                         penv->PanPoints[iv] = vtick;
00468                                         penv->PanEnv[iv] = ppe[iv*2+2];
00469                                         if (!ppe[iv*2+1])
00470                                         {
00471                                                 penv->nPanEnv = iv+1;
00472                                                 break;
00473                                         }
00474                                 }
00475                                 if (ppe[31] & 0x10) penv->dwFlags |= ENV_PANSUSTAIN;
00476                                 if (ppe[31] & 0x20) penv->dwFlags |= ENV_PANLOOP;
00477                                 penv->nPanLoopStart = ppe[32] & 0x0F;
00478                                 penv->nPanLoopEnd = ppe[32] >> 4;
00479                         }
00480                 }
00481         }
00482         m_dwSongFlags |= SONG_LINEARSLIDES;
00483         m_nType = MOD_TYPE_MDL;
00484         return TRUE;
00485 }
00486 
00487 
00489 // MDL Sample Unpacking
00490 
00491 // MDL Huffman ReadBits compression
00492 WORD MDLReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n)
00493 //-----------------------------------------------------------------
00494 {
00495         WORD v = (WORD)(bitbuf & ((1 << n) - 1) );
00496         bitbuf >>= n;
00497         bitnum -= n;
00498         if (bitnum <= 24)
00499         {
00500                 bitbuf |= (((DWORD)(*ibuf++)) << bitnum);
00501                 bitnum += 8;
00502         }
00503         return v;
00504 }
00505 
00506 

Generated on Sat Nov 5 16:15:39 2005 for OPIE by  doxygen 1.4.2