/* ** Instr.c ** ** Copyright (C) 1994,95,96,97 Bernardo Innocenti ** ** Instrument loading/saving/handling routines. */ #define IFFPARSE_V37_NAMES_ONLY #include #include #include #include #include #include #include #include #include #include #include #include #include #include "XModulePriv.h" #include "Gui.h" #define UNITY 0x10000L /* Local function prototypes */ static LONG DTLoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename); static LONG LoadMAUDInstrument (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename); static LONG RawLoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename, UWORD mode); static BYTE D1Unpack (UBYTE source[], LONG n, BYTE dest[], BYTE x); static void DUnpack (UBYTE source[], LONG n, BYTE dest[]); struct Library *DataTypesBase = NULL; struct Library *ToccataBase = NULL; GLOBALCALL LONG LoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename) /* Load an instrument file to the instrument slot in the passed song. * Will use DataTypes if available, otherwise it will use the built-in loaders. */ { LONG err = IFFERR_NOTIFF; /* Try loading with DataTypes */ if (GuiSwitches.UseDataTypes) if (DataTypesBase = OpenLibrary ("datatypes.library", 39L)) { err = DTLoadInstrument (si, num, filename); CloseLibrary (DataTypesBase); DataTypesBase = NULL; } /* Try again using built-in loaders */ if (err) { struct IFFHandle *iff; if (iff = AllocIFF()) { if (iff->iff_Stream = (ULONG) Open (filename, MODE_OLDFILE)) { InitIFFasDOS (iff); if (!(err = OpenIFF (iff, IFFF_READ))) { if (!(err = ParseIFF (iff, IFFPARSE_RAWSTEP))) { LONG type = CurrentChunk (iff)->cn_Type; switch (type) { case ID_8SVX: err = Load8SVXInstrument (si, num, iff, filename); break; case ID_MAUD: err = LoadMAUDInstrument (si, num, iff, filename); break; default: { UBYTE buf[5]; IDtoStr (type, buf); ShowMessage (MSG_UNKNOWN_IFF, buf); err = ERROR_OBJECT_WRONG_TYPE; break; } } } } Close (iff->iff_Stream); } else err = IoErr(); FreeIFF (iff); } else err = ERROR_NO_FREE_STORE; } if (err == IFFERR_MANGLED || err == IFFERR_SYNTAX) ShowMessage (MSG_ILLEGAL_IFF_STRUCTURE); if (err == IFFERR_NOTIFF) { LONG mode; if (mode = ShowRequestArgs (MSG_SELECT_RAW_MODE, MSG_RAW_MODES, NULL)) err = RawLoadInstrument (si, num, filename, mode); else err = 0; } UpdateInstrList(); if (err) LastErr = err; return err; } static LONG DTLoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename) { Object *dto; UBYTE *instname; LONG err = 0; if (dto = NewDTObject (filename, DTA_GroupID, GID_SOUND, TAG_DONE)) { struct VoiceHeader *vhdr; LONG Len, Vol; UBYTE *Sample; UBYTE *errorstring; if (GetDTAttrs (dto, SDTA_VoiceHeader, &vhdr, SDTA_SampleLength, &Len, SDTA_Volume, &Vol, SDTA_Sample, &Sample, DTA_Title, &instname, TAG_DONE) == 5) { /* Detach sample from DataType Object, so it won't * be freed when we dispose the object. */ SetDTAttrs (dto, NULL, NULL, SDTA_Sample, NULL, TAG_DONE); /* Create new instrument */ if (!xmAddInstrument (si, num, INSTRA_Name, instname, INSTRA_Sample, Sample, INSTRA_Length, Len, INSTRA_Repeat, vhdr->vh_OneShotHiSamples, INSTRA_Replen, vhdr->vh_RepeatHiSamples, /* The sound.datatype _should_ return * volumes in the normal Amiga range 0-64. * However, the 8svx.datatype behaves * differently: it returns the volume in the * standard 8SVX format, where the maximum * volume is $10000. Here is a good * workaround to this bug. */ INSTRA_Volume, (vhdr->vh_Volume > 64) ? ((vhdr->vh_Volume * 64) / UNITY) : vhdr->vh_Volume, TAG_DONE)) err = ERROR_NO_FREE_STORE; DB(kprintf ("Vol: %ld InstVol: %ld\n", Vol, si->Instr[num]->Volume)); } else err = RETURN_FAIL; if (GetDTAttrs (dto, DTA_ErrorString, &errorstring, TAG_DONE) == 1) ShowMessage (MSG_DATATYPES_ERROR, errorstring); DisposeDTObject (dto); } else err = IoErr(); return err; } GLOBALCALL LONG Load8SVXInstrument (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename) /* Load an IFF 8SVX file to the instrument slot . If is 0, it requires * the INST chunk and it uses the num stored there. * Can decode Fibonacci Delta encoded samples. */ { struct ContextNode *cn; struct VoiceHeader vhdr; struct InstrumentHeader insthdr; LONG err; UBYTE name[64]; BOOL is_valid_8svx = FALSE, insthdr_loaded = FALSE; static LONG stopchunks[] = { ID_8SVX, ID_INST, ID_8SVX, ID_VHDR, ID_8SVX, ID_BODY, ID_8SVX, ID_NAME }; /* Put the file name if the optional NAME propriety is missing */ if (filename) { strncpy (name, FilePart (filename), 63); name[63] = '\0'; } else name[0] = '\0'; if (err = StopChunks (iff, stopchunks, 4)) return err; if (err = StopOnExit (iff, ID_8SVX, ID_FORM)) return err; while (1) { if (err = ParseIFF (iff, IFFPARSE_SCAN)) { if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK; break; /* Free resources & exit */ } if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_8SVX)) { switch (cn->cn_ID) { case ID_INST: { if ((err = ReadChunkBytes (iff, &insthdr, sizeof (insthdr))) != sizeof (insthdr)) return err; if (!num) num = insthdr.Num; } case ID_VHDR: { if ((err = ReadChunkBytes (iff, &vhdr, sizeof (vhdr))) != sizeof (vhdr)) return err; if (!is_valid_8svx) { if (!xmAddInstrumentA (si, num, NULL)) return ERROR_NO_FREE_STORE; if (!num) num = si->LastInstrument; } xmSetInstrument (si, num, INSTRA_Repeat, (vhdr.vh_RepeatHiSamples ? vhdr.vh_OneShotHiSamples : 0), INSTRA_Replen, vhdr.vh_RepeatHiSamples, INSTRA_Volume, (vhdr.vh_Volume * 64) / UNITY, INSTRA_FineTune, insthdr_loaded ? insthdr.FineTune : 0, TAG_DONE); is_valid_8svx = TRUE; break; } case ID_BODY: { struct Instrument *instr; BYTE *sample; if (!is_valid_8svx) { xmAddInstrumentA (si, num, NULL); if (!num) num = si->LastInstrument; } if (!(instr = si->Instr[num])) return ERROR_NO_FREE_STORE; if (!(sample = AllocVec (cn->cn_Size, MEMF_SAMPLE))) return ERROR_NO_FREE_STORE; xmSetInstrument (si, num, INSTRA_Sample, sample, INSTRA_Length, cn->cn_Size, TAG_DONE); /* We only require that at least some data is * read. This way if, say, you have a corrupted * 8SVX file, you can still load part of the * instrument data. */ if ((err = ReadChunkBytes (iff, instr->Sample, cn->cn_Size)) < 0) return err; is_valid_8svx = TRUE; break; } case ID_NAME: ReadChunkBytes (iff, name, min(cn->cn_Size, 63)); name[63] = '\0'; /* Ensure string termination */ break; default: break; } } } if (is_valid_8svx) { xmSetInstrument (si, num, INSTRA_Name, name, TAG_DONE); if (!err) { if (vhdr.vh_Compression == CMP_FIBDELTA) { BYTE *buf; struct Instrument *instr = si->Instr[num]; if (buf = AllocVec (instr->Length * 2, MEMF_SAMPLE)) { DUnpack (instr->Sample, instr->Length + 2, buf); FreeVec (instr->Sample); xmSetInstrument (si, num, INSTRA_Sample, buf, INSTRA_Length, instr->Length * 2, TAG_DONE); } } else if (vhdr.vh_Compression != CMP_NONE) ShowMessage (MSG_UNKNOWN_COMPRESSION); } } else err = IFFERR_MANGLED; return err; } /* Number of samples loaded & processed at one time */ #define MAUDBLOCKSIZE 32768 static LONG LoadMAUDInstrument (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename) /* Load an IFF MAUD file to the instrument slot of the passed song. * MAUD is the standard file format for Macrosystem's audio boards * Toccata and Maestro. */ { struct ContextNode *cn; struct MaudHeader mhdr; LONG err; BOOL is_valid_maud = FALSE; UBYTE name[64]; static LONG stopchunks[] = { ID_MAUD, ID_MHDR, ID_MAUD, ID_MDAT, ID_MAUD, ID_NAME }; /* Put the file name if the optional NAME propriety is missing */ if (filename) { strncpy (name, FilePart (filename), 63); name[63] = '\0'; } else name[0] = '\0'; if (err = StopChunks (iff, stopchunks, 3)) return err; if (err = StopOnExit (iff, ID_MAUD, ID_FORM)) return err; while (1) { if (err = ParseIFF (iff, IFFPARSE_SCAN)) { if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK; break; /* Free resources & exit */ } if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_MAUD)) { switch (cn->cn_ID) { case ID_MHDR: if ((err = ReadChunkBytes (iff, &mhdr, sizeof (mhdr))) != sizeof (mhdr)) return err; if ((mhdr.mhdr_SampleSizeU != 8) && (mhdr.mhdr_SampleSizeU != 16)) { ShowMessage (MSG_SAMPLE_WRONG_SIZE, mhdr.mhdr_SampleSizeU); return IFFERR_SYNTAX; } if (mhdr.mhdr_ChannelInfo != MCI_MONO) { ShowMessage (MSG_SAMPLE_NOT_MONO, mhdr.mhdr_ChannelInfo); return IFFERR_SYNTAX; } if (mhdr.mhdr_Channels != 1) { ShowMessage (MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS, mhdr.mhdr_Channels); return IFFERR_SYNTAX; } is_valid_maud = TRUE; break; case ID_MDAT: { ULONG i; struct Instrument *instr; BYTE *sample; if (!is_valid_maud) return IFFERR_SYNTAX; if (!(sample = AllocVec ((mhdr.mhdr_Samples + 1) & (~1), MEMF_SAMPLE))) return ERROR_NO_FREE_STORE; if (!(instr = xmAddInstrument (si, num, INSTRA_Sample, sample, INSTRA_Length, (mhdr.mhdr_Samples + 1) & (~1), TAG_DONE))) { FreeVec (sample); return ERROR_NO_FREE_STORE; } if (mhdr.mhdr_SampleSizeU == 8) /* 8 bit */ { /* We only require that at least some data is * read. This way if, say, you have a corrupted * MAUD file, you can still load part of the * sample data. */ if ((err = ReadChunkBytes (iff, instr->Sample, instr->Length)) == 0) return err; SampChangeSign8 (instr->Sample, instr->Length); } else if (mhdr.mhdr_SampleSizeU == 16) /* 16 bit */ { WORD *tmp; ULONG actual, current = 0; if (!(tmp = AllocPooled (Pool, MAUDBLOCKSIZE * ((mhdr.mhdr_SampleSizeC + 7) / 8)))) return ERROR_NO_FREE_STORE; if (mhdr.mhdr_Compression != MCOMP_NONE) if (!(ToccataBase = MyOpenLibrary ("toccata.library", 0L))) { FreePooled (Pool, tmp, MAUDBLOCKSIZE * sizeof (WORD)); CantOpenLib ("toccata.library", 0L); return ERROR_INVALID_RESIDENT_LIBRARY; } for (;;) { actual = ReadChunkBytes (iff, tmp, MAUDBLOCKSIZE * ((mhdr.mhdr_SampleSizeC + 7) / 8)); if (actual == 0) break; /* Filter (tmp, actual); */ switch (mhdr.mhdr_Compression) { case MCOMP_ALAW: /* Convert 8bit A-Law data to 8bit signed data */ T_Convert (tmp, instr->Sample + current, actual, TMODE_ALAW, TMODE_LINEAR_8); current += actual; break; case MCOMP_ULAW: /* Convert 8bit µ-Law data to 8bit signed data */ T_Convert (tmp, instr->Sample + current, actual, TMODE_ULAW, TMODE_LINEAR_8); current += actual; break; default: /* Convert 16bit signed data to 8bit signed data */ actual >>= 1; for (i = 0; (i < actual) && (current < mhdr.mhdr_Samples); i++, current++) instr->Sample[current] = tmp[i] >> 8; break; } } SampChangeSign8 (instr->Sample, instr->Length); FreePooled (Pool, tmp, MAUDBLOCKSIZE * sizeof (WORD)); CloseLibrary (ToccataBase); ToccataBase = NULL; } is_valid_maud = TRUE; break; } case ID_NAME: ReadChunkBytes (iff, name, min(cn->cn_Size, 63)); break; default: break; } } } if (is_valid_maud) { xmSetInstrument (si, num, INSTRA_Name, name, TAG_DONE); if (!err) { if (mhdr.mhdr_Compression == CMP_FIBDELTA) { BYTE *buf; struct Instrument *instr = si->Instr[num]; if (buf = AllocVec (instr->Length * 2, MEMF_SAMPLE)) { DUnpack (instr->Sample, instr->Length + 2, buf); FreeVec (instr->Sample); xmSetInstrument (si, num, INSTRA_Sample, buf, INSTRA_Length, instr->Length * 2, TAG_DONE); } } else if (mhdr.mhdr_Compression != CMP_NONE) ShowMessage (MSG_UNKNOWN_COMPRESSION); } } else err = IFFERR_NOTIFF; return err; } static LONG RawLoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename, UWORD mode) /* Load a raw file to the instrument slot pointed by inst. * mode 1 - signed 8bit * 2 - unsigned 8bit */ { BPTR lock, fh; struct FileInfoBlock *fib; struct Instrument *instr; LONG err = 0; ULONG len; if (lock = Lock (filename, ACCESS_READ)) { /* Get file size */ if (fib = AllocDosObject (DOS_FIB, NULL)) { if (Examine (lock, fib)) len = fib->fib_Size; else err = IoErr(); FreeDosObject (DOS_FIB, fib); } else err = ERROR_NO_FREE_STORE; if (!err) { if (fh = OpenFromLock (lock)) { BYTE *sample; lock = NULL; /* OpenFromLock() relinquished our lock! */ if (sample = AllocVec (len, MEMF_SAMPLE)) { if (instr = xmAddInstrument (si, num, INSTRA_Sample, sample, INSTRA_Length, len, INSTRA_Volume, 64, INSTRA_Name, FilePart (filename), TAG_DONE)) { /* We do not check for failure here to * be more error tolerant. This way you * can load at least part of an instrument * from a corrupted file ;-) */ Read (fh, sample, len); if (mode == 2) SampChangeSign8 (sample, instr->Length); } else { FreeVec (sample); err = ERROR_NO_FREE_STORE; } } else err = ERROR_NO_FREE_STORE; Close (fh); } else err = IoErr(); } UnLock (lock); /* Will be NULL if OpenFromLock() was successful */ } else err = IoErr(); return err; } GLOBALCALL LONG SaveInstrument (struct Instrument *inst, CONST_STRPTR filename) { LONG err; struct IFFHandle *iff; if (iff = AllocIFF()) { if (iff->iff_Stream = (ULONG) Open (filename, MODE_NEWFILE)) { InitIFFasDOS (iff); if (!(err = OpenIFF (iff, IFFF_WRITE))) { err = Save8SVXInstrument (inst, 0, iff); CloseIFF (iff); } Close (iff->iff_Stream); } else err = IoErr(); FreeIFF (iff); } else return ERROR_NO_FREE_STORE; if (!err) { if (GuiSwitches.InstrSaveIcons) /* Write icon */ PutIcon ("def_Instrument", filename); } else { /* Remove incomplete file */ LastErr = err; DeleteFile (filename); } return (err); } GLOBALCALL LONG Save8SVXInstrument (struct Instrument *instr, ULONG num, struct IFFHandle *iff) /* Save the instrument pointed by inst to a standard IFF 8SVX file. */ { struct VoiceHeader vhdr; LONG err; /* Write 8SVX */ if (err = PushChunk (iff, ID_8SVX, ID_FORM, IFFSIZE_UNKNOWN)) return err; /* Write INST */ if (num) { struct InstrumentHeader insthdr; insthdr.Num = num; insthdr.Type = ITYPE_SAMPLE8; insthdr.FineTune = instr->FineTune; if (err = PushChunk (iff, ID_8SVX, ID_INST, sizeof (insthdr))) return err; if ((err = WriteChunkBytes (iff, &insthdr, sizeof (insthdr))) != sizeof (insthdr)) return err; if (err = PopChunk (iff)) return err; /* Pop INST */ } /* Write VHDR */ if (vhdr.vh_RepeatHiSamples = instr->Replen) /* Loop */ vhdr.vh_OneShotHiSamples = instr->Repeat; else /* No Loop */ vhdr.vh_OneShotHiSamples = instr->Length; vhdr.vh_SamplesPerHiCycle = 0; vhdr.vh_SamplesPerSec = 8363; vhdr.vh_Octaves = 1; vhdr.vh_Compression = CMP_NONE; vhdr.vh_Volume = (instr->Volume * UNITY) / 64; if (err = PushChunk (iff, ID_8SVX, ID_VHDR, sizeof (vhdr))) return err; if ((err = WriteChunkBytes (iff, &vhdr, sizeof (vhdr))) != sizeof (vhdr)) return err; if (err = PopChunk (iff)) return err; /* Pop VHDR */ /* Write NAME */ { ULONG l = strlen (instr->Name) + 1; if (err = PushChunk (iff, ID_8SVX, ID_NAME, l)) return err; if ((err = WriteChunkBytes (iff, instr->Name, l)) != l) return err; if (err = PopChunk (iff)) return err; /* Pop NAME */ } /* Write BODY */ if (instr->Sample) { if (PushChunk (iff, ID_8SVX, ID_BODY, instr->Length)) return err; if ((err = WriteChunkBytes (iff, instr->Sample, instr->Length)) != instr->Length) return err; if (err = PopChunk (iff)) return err; /* Pop BODY */ } PopChunk (iff); /* Pop 8SVX */ return err; } GLOBALCALL void OptimizeInstruments (struct SongInfo *si) /* Remove useless sample data (cut beyond loops and zero-tails) */ { UWORD i; ULONG newlen; struct Instrument *instr; for (i = 1 ; i <= si->LastInstrument ; i++) { if (!(instr = si->Instr[i])) continue; if (!instr->Sample) continue; newlen = instr->Length; if (instr->Replen) { if (instr->Length > instr->Repeat + instr->Replen) newlen = instr->Repeat + instr->Replen; /* Cut instrument after loop */ } else { BYTE *tail; instr->Repeat = 0; /* Kill null loops */ /* Kill instrument zero-tail. * In order to reduce the instrument even more, * 1 & -1 are treated the same as zero. */ tail = instr->Sample + instr->Length - 1; while ((*tail < 1) && (*tail > -1) && (tail > instr->Sample)) tail--; newlen = tail - instr->Sample; if (newlen & 1) newlen++; /* Pad instrument size to words */ /* leave 2 end zeroes to prevent an audible end-of-instrument click. */ if (newlen) newlen += 2; } if (newlen == 0) xmRemInstrument (si, i); /* This instrument is mute! Free it... */ else if (newlen < instr->Length) { /* Resize the instrument if necessary */ BYTE *newinstr; /* Allocate memory for optimized instrument */ if (!(newinstr = AllocVec (newlen, MEMF_SAMPLE))) { ShowMessage (MSG_NO_MEMORY_TO_OPTIMIZE_INSTR, i); continue; /* Better luck with next instrument :) */ } ShowMessage (MSG_INSTR_WILL_SHRINK, i, instr->Length, newlen); /* Copy first part of instrument */ CopyMem (instr->Sample, newinstr, newlen); /* Free old instrument */ FreeVec (instr->Sample); /* Replace with new instrument */ xmSetInstrument (si, i, INSTRA_Sample, newinstr, INSTRA_Length, newlen, TAG_DONE); } } } GLOBALCALL void RemDupInstruments (struct SongInfo *si) /* Find out identical patterns and cut them out */ { ULONG i, j, k, w, v; struct Instrument *insta, *instb; struct Pattern *patt; struct Note *note; for (i = 1; i < si->LastInstrument; i++) /* '<' instead of '<=' is ok here... */ { if (!(insta = si->Instr[i])) continue; if (!insta->Length) continue; for (j = i + 1; j <= si->LastInstrument ; j++) { if (!(instb = si->Instr[j])) continue; if (insta->Length == instb->Length && insta->Repeat == instb->Repeat && insta->Replen == instb->Replen && insta->Volume == instb->Volume && insta->Type == instb->Type && insta->FineTune == instb->FineTune) { if (!memcmp (insta->Sample, instb->Sample, insta->Length)) { xmRemInstrument (si, j); for (k = 0; k < si->NumPatterns; k++) { if (!(patt = si->Patt[k])) continue; for (w = 0; w < patt->Tracks; w++) { note = patt->Notes[w]; for (v = 0; v < patt->Lines; v++, note++) if (note->Inst == j) note->Inst = i; } } ShowMessage (MSG_INSTR_DUPES_REMOVED, i, j); } } } } } GLOBALCALL void RemapInstruments (struct SongInfo *si) /* Remove empty slots between instruments, to allow those module formats * that support less instruments to use even the last instruments. */ { UWORD i, j, k; UBYTE newpos[MAXINSTRUMENTS] = { 0 }; struct Instrument *instr; struct Pattern *patt; struct Note *note; /* newpos[0] = 0; */ DB (kprintf ("Before - LastInstrument = %ld", si->LastInstrument)); /* Build instrument remap table & compress instrument slots */ for (i = 1, j = 0; i <= si->LastInstrument; i++) { if (!(instr = si->Instr[i])) continue; if (instr->Length) { j++; newpos[i] = j; if (j != i) DoMethod ((Object *)si, SNGM_SWAPINSTRUMENTS, i, j); } else newpos[i] = 0; } /* Update score */ for (i = 0 ; i < si->NumPatterns ; i++) { patt = si->Patt[i]; for (j = 0 ; j < patt->Tracks ; j++) { note = patt->Notes[j]; for (k = 0; k < patt->Lines ; k++, note++) if (note->Note) { note->Inst = newpos[note->Inst]; if (!note->Inst) note->Note = 0; } } } DB (kprintf ("After - LastInstrument = %ld", si->LastInstrument)); } GLOBALCALL void RemUnusedInstruments (struct SongInfo *si) { ULONG usecount[MAXINSTRUMENTS] = { 0 }; struct Pattern *patt; struct Note *note; UWORD i, j, k; for (i = 0; i < si->NumPatterns; i++) { if (!(patt = si->Patt[i])) continue; for (j = 0; j < patt->Tracks; j++) { note = patt->Notes[j]; for (k = 0; k < patt->Lines; k++, note++) usecount[note->Inst]++; } } for (i = 1; i <= si->LastInstrument; i++) { if ((usecount[i] == 0) && si->Instr[i]) { ShowMessage (MSG_INSTR_UNUSED, i); xmRemInstrument (si, i); } } } /* DUnpack.c --- Fibonacci Delta decompression by Steve Hayes */ /* Fibonacci delta encoding for sound data */ static const BYTE codeToDelta[16] = {-34,-21,-13,-8,-5,-3,-2,-1,0,1,2,3,5,8,13,21}; static BYTE D1Unpack (UBYTE source[], LONG n, BYTE dest[], BYTE x) /* Unpack Fibonacci-delta encoded data from n byte source * buffer into 2*n byte dest buffer, given initial data * value x. It returns the last data value x so you can * call it several times to incrementally decompress the data. */ { UBYTE d; LONG i, lim; lim = n << 1; for (i = 0; i < lim; ++i) { /* Decode a data nibble, high nibble then low nibble */ d = source[i >> 1]; /* get a pair of nibbles */ if (i & 1) /* select low or high nibble */ d &= 0xf; /* mask to get the low nibble */ else d >>= 4; /* shift to get the high nibble */ x += codeToDelta[d]; /* add in the decoded delta */ dest[i] = x; /* store a 1 byte sample */ } return x; } static void DUnpack (UBYTE source[], LONG n, BYTE dest[]) /* Unpack Fibonacci-delta encoded data from n byte * source buffer into 2*(n-2) byte dest buffer. * Source buffer has a pad byte, an 8-bit initial * value, followed by n-2 bytes comprising 2*(n-2) * 4-bit encoded samples. */ { D1Unpack (source+2, n-2, dest, (BYTE)source[1]); } GLOBALCALL void SampChangeSign8 (UBYTE *samp, ULONG len) /* Performs a sign conversion on a 8bit sample. The same function can be * used to convert a signed sample into an unsigned one and vice versa. * * TODO: optimize with a LONG oriented loop */ { while (len) samp[--len] ^= 0x80; }