--- libgig/trunk/src/gig.cpp 2005/02/11 18:58:07 372 +++ libgig/trunk/src/gig.cpp 2005/09/25 13:40:37 780 @@ -2,8 +2,8 @@ * * * libgig - C++ cross-platform Gigasampler format file loader library * * * - * Copyright (C) 2003, 2004 by Christian Schoenebeck * - * * + * Copyright (C) 2003-2005 by Christian Schoenebeck * + * * * * * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -23,11 +23,47 @@ #include "gig.h" -namespace gig { namespace { +#include + +namespace gig { + +// *************** progress_t *************** +// * + + progress_t::progress_t() { + callback = NULL; + custom = NULL; + __range_min = 0.0f; + __range_max = 1.0f; + } + + // private helper function to convert progress of a subprocess into the global progress + static void __notify_progress(progress_t* pProgress, float subprogress) { + if (pProgress && pProgress->callback) { + const float totalrange = pProgress->__range_max - pProgress->__range_min; + const float totalprogress = pProgress->__range_min + subprogress * totalrange; + pProgress->factor = totalprogress; + pProgress->callback(pProgress); // now actually notify about the progress + } + } + + // private helper function to divide a progress into subprogresses + static void __divide_progress(progress_t* pParentProgress, progress_t* pSubProgress, float totalTasks, float currentTask) { + if (pParentProgress && pParentProgress->callback) { + const float totalrange = pParentProgress->__range_max - pParentProgress->__range_min; + pSubProgress->callback = pParentProgress->callback; + pSubProgress->custom = pParentProgress->custom; + pSubProgress->__range_min = pParentProgress->__range_min + totalrange * currentTask / totalTasks; + pSubProgress->__range_max = pSubProgress->__range_min + totalrange / totalTasks; + } + } + // *************** Internal functions for sample decopmression *************** // * +namespace { + inline int get12lo(const unsigned char* pSrc) { const int x = pSrc[0] | (pSrc[1] & 0x0f) << 8; @@ -92,43 +128,35 @@ void Decompress24(int compressionmode, const unsigned char* params, int dstStep, const unsigned char* pSrc, int16_t* pDst, unsigned long currentframeoffset, - unsigned long copysamples) + unsigned long copysamples, int truncatedBits) { // Note: The 24 bits are truncated to 16 bits for now. - // Note: The calculation of the initial value of y is strange - // and not 100% correct. What should the first two parameters - // really be used for? Why are they two? The correct value for - // y seems to lie somewhere between the values of the first - // two parameters. - // - // Strange thing #2: The formula in SKIP_ONE gives values for - // y that are twice as high as they should be. That's why - // COPY_ONE shifts 9 steps instead of 8, and also why y is - // initialized with a sum instead of a mean value. - - int y, dy, ddy; - -#define GET_PARAMS(params) \ - y = (get24(params) + get24((params) + 3)); \ - dy = get24((params) + 6); \ - ddy = get24((params) + 9) + int y, dy, ddy, dddy; + const int shift = 8 - truncatedBits; + +#define GET_PARAMS(params) \ + y = get24(params); \ + dy = y - get24((params) + 3); \ + ddy = get24((params) + 6); \ + dddy = get24((params) + 9) #define SKIP_ONE(x) \ - ddy -= (x); \ - dy -= ddy; \ - y -= dy + dddy -= (x); \ + ddy -= dddy; \ + dy = -dy - ddy; \ + y += dy #define COPY_ONE(x) \ SKIP_ONE(x); \ - *pDst = y >> 9; \ + *pDst = y >> shift; \ pDst += dstStep switch (compressionmode) { case 2: // 24 bit uncompressed pSrc += currentframeoffset * 3; while (copysamples) { - *pDst = get24(pSrc) >> 8; + *pDst = get24(pSrc) >> shift; pDst += dstStep; pSrc += 3; copysamples--; @@ -201,12 +229,12 @@ // *************** Sample *************** // * - unsigned int Sample::Instances = 0; - unsigned char* Sample::pDecompressionBuffer = NULL; - unsigned long Sample::DecompressionBufferSize = 0; + unsigned int Sample::Instances = 0; + buffer_t Sample::InternalDecompressionBuffer; - Sample::Sample(File* pFile, RIFF::List* waveList, unsigned long WavePoolOffset) : DLS::Sample((DLS::File*) pFile, waveList, WavePoolOffset) { + Sample::Sample(File* pFile, RIFF::List* waveList, unsigned long WavePoolOffset, unsigned long fileNo) : DLS::Sample((DLS::File*) pFile, waveList, WavePoolOffset) { Instances++; + FileNo = fileNo; RIFF::Chunk* _3gix = waveList->GetSubChunk(CHUNK_ID_3GIX); if (!_3gix) throw gig::Exception("Mandatory chunks in list chunk not found."); @@ -238,17 +266,26 @@ if (BitDepth > 24) throw gig::Exception("Only samples up to 24 bit supported"); - Compressed = (waveList->GetSubChunk(CHUNK_ID_EWAV)); + RIFF::Chunk* ewav = waveList->GetSubChunk(CHUNK_ID_EWAV); + Compressed = ewav; + Dithered = false; + TruncatedBits = 0; if (Compressed) { + uint32_t version = ewav->ReadInt32(); + if (version == 3 && BitDepth == 24) { + Dithered = ewav->ReadInt32(); + ewav->SetPos(Channels == 2 ? 84 : 64); + TruncatedBits = ewav->ReadInt32(); + } ScanCompressedSample(); } // we use a buffer for decompression and for truncating 24 bit samples to 16 bit - if ((Compressed || BitDepth == 24) && !pDecompressionBuffer) { - pDecompressionBuffer = new unsigned char[INITIAL_SAMPLE_BUFFER_SIZE]; - DecompressionBufferSize = INITIAL_SAMPLE_BUFFER_SIZE; + if ((Compressed || BitDepth == 24) && !InternalDecompressionBuffer.Size) { + InternalDecompressionBuffer.pStart = new unsigned char[INITIAL_SAMPLE_BUFFER_SIZE]; + InternalDecompressionBuffer.Size = INITIAL_SAMPLE_BUFFER_SIZE; } - FrameOffset = 0; // just for streaming compressed samples + FrameOffset = 0; // just for streaming compressed samples LoopSize = LoopEnd - LoopStart; } @@ -260,7 +297,7 @@ std::list frameOffsets; SamplesPerFrame = BitDepth == 24 ? 256 : 2048; - WorstCaseFrameSize = SamplesPerFrame * FrameSize + Channels; + WorstCaseFrameSize = SamplesPerFrame * FrameSize + Channels; // +Channels for compression flag // Scanning pCkData->SetPos(0); @@ -341,9 +378,10 @@ * that will be returned to determine the actual cached samples, but note * that the size is given in bytes! You get the number of actually cached * samples by dividing it by the frame size of the sample: - * + * @code * buffer_t buf = pSample->LoadSampleData(acquired_samples); * long cachedsamples = buf.Size / pSample->FrameSize; + * @endcode * * @param SampleCount - number of sample points to load into RAM * @returns buffer_t structure with start address and size of @@ -389,10 +427,10 @@ * that will be returned to determine the actual cached samples, but note * that the size is given in bytes! You get the number of actually cached * samples by dividing it by the frame size of the sample: - * + * @code * buffer_t buf = pSample->LoadSampleDataWithNullSamplesExtension(acquired_samples, null_samples); * long cachedsamples = buf.Size / pSample->FrameSize; - * + * @endcode * The method will add \a NullSamplesCount silence samples past the * official buffer end (this won't affect the 'Size' member of the * buffer_t structure, that means 'Size' always reflects the size of the @@ -523,25 +561,29 @@ * for the next time you call this method is stored in \a pPlaybackState. * You have to allocate and initialize the playback_state_t structure by * yourself before you use it to stream a sample: - * - * - * gig::playback_state_t playbackstate;
- * playbackstate.position = 0;
- * playbackstate.reverse = false;
- * playbackstate.loop_cycles_left = pSample->LoopPlayCount;
- *
- * + * @code + * gig::playback_state_t playbackstate; + * playbackstate.position = 0; + * playbackstate.reverse = false; + * playbackstate.loop_cycles_left = pSample->LoopPlayCount; + * @endcode * You don't have to take care of things like if there is actually a loop * defined or if the current read position is located within a loop area. * The method already handles such cases by itself. * + * Caution: If you are using more than one streaming thread, you + * have to use an external decompression buffer for EACH + * streaming thread to avoid race conditions and crashes! + * * @param pBuffer destination buffer * @param SampleCount number of sample points to read * @param pPlaybackState will be used to store and reload the playback * state for the next ReadAndLoop() call + * @param pExternalDecompressionBuffer (optional) external buffer to use for decompression * @returns number of successfully read sample points + * @see CreateDecompressionBuffer() */ - unsigned long Sample::ReadAndLoop(void* pBuffer, unsigned long SampleCount, playback_state_t* pPlaybackState) { + unsigned long Sample::ReadAndLoop(void* pBuffer, unsigned long SampleCount, playback_state_t* pPlaybackState, buffer_t* pExternalDecompressionBuffer) { unsigned long samplestoread = SampleCount, totalreadsamples = 0, readsamples, samplestoloopend; uint8_t* pDst = (uint8_t*) pBuffer; @@ -559,7 +601,7 @@ if (!pPlaybackState->reverse) { // forward playback do { samplestoloopend = this->LoopEnd - GetPos(); - readsamples = Read(&pDst[totalreadsamples * this->FrameSize], Min(samplestoread, samplestoloopend)); + readsamples = Read(&pDst[totalreadsamples * this->FrameSize], Min(samplestoread, samplestoloopend), pExternalDecompressionBuffer); samplestoread -= readsamples; totalreadsamples += readsamples; if (readsamples == samplestoloopend) { @@ -585,7 +627,7 @@ // read samples for backward playback do { - readsamples = Read(&pDst[totalreadsamples * this->FrameSize], samplestoreadinloop); + readsamples = Read(&pDst[totalreadsamples * this->FrameSize], samplestoreadinloop, pExternalDecompressionBuffer); samplestoreadinloop -= readsamples; samplestoread -= readsamples; totalreadsamples += readsamples; @@ -609,7 +651,7 @@ // forward playback (not entered the loop yet) if (!pPlaybackState->reverse) do { samplestoloopend = this->LoopEnd - GetPos(); - readsamples = Read(&pDst[totalreadsamples * this->FrameSize], Min(samplestoread, samplestoloopend)); + readsamples = Read(&pDst[totalreadsamples * this->FrameSize], Min(samplestoread, samplestoloopend), pExternalDecompressionBuffer); samplestoread -= readsamples; totalreadsamples += readsamples; if (readsamples == samplestoloopend) { @@ -639,7 +681,7 @@ // if not endless loop check if max. number of loop cycles have been passed if (this->LoopPlayCount && !pPlaybackState->loop_cycles_left) break; samplestoloopend = this->LoopEnd - GetPos(); - readsamples = Read(&pDst[totalreadsamples * this->FrameSize], Min(samplestoreadinloop, samplestoloopend)); + readsamples = Read(&pDst[totalreadsamples * this->FrameSize], Min(samplestoreadinloop, samplestoloopend), pExternalDecompressionBuffer); samplestoreadinloop -= readsamples; samplestoread -= readsamples; totalreadsamples += readsamples; @@ -661,7 +703,7 @@ // if not endless loop check if max. number of loop cycles have been passed if (this->LoopPlayCount && !pPlaybackState->loop_cycles_left) break; samplestoloopend = this->LoopEnd - GetPos(); - readsamples = Read(&pDst[totalreadsamples * this->FrameSize], Min(samplestoread, samplestoloopend)); + readsamples = Read(&pDst[totalreadsamples * this->FrameSize], Min(samplestoread, samplestoloopend), pExternalDecompressionBuffer); samplestoread -= readsamples; totalreadsamples += readsamples; if (readsamples == samplestoloopend) { @@ -676,7 +718,7 @@ // read on without looping if (samplestoread) do { - readsamples = Read(&pDst[totalreadsamples * this->FrameSize], samplestoread); + readsamples = Read(&pDst[totalreadsamples * this->FrameSize], samplestoread, pExternalDecompressionBuffer); samplestoread -= readsamples; totalreadsamples += readsamples; } while (readsamples && samplestoread); @@ -695,17 +737,22 @@ * and SetPos() if you don't want to load the sample into RAM, * thus for disk streaming. * + * Caution: If you are using more than one streaming thread, you + * have to use an external decompression buffer for EACH + * streaming thread to avoid race conditions and crashes! + * * @param pBuffer destination buffer * @param SampleCount number of sample points to read + * @param pExternalDecompressionBuffer (optional) external buffer to use for decompression * @returns number of successfully read sample points - * @see SetPos() + * @see SetPos(), CreateDecompressionBuffer() */ - unsigned long Sample::Read(void* pBuffer, unsigned long SampleCount) { + unsigned long Sample::Read(void* pBuffer, unsigned long SampleCount, buffer_t* pExternalDecompressionBuffer) { if (SampleCount == 0) return 0; if (!Compressed) { if (BitDepth == 24) { // 24 bit sample. For now just truncate to 16 bit. - unsigned char* pSrc = this->pDecompressionBuffer; + unsigned char* pSrc = (unsigned char*) ((pExternalDecompressionBuffer) ? pExternalDecompressionBuffer->pStart : this->InternalDecompressionBuffer.pStart); int16_t* pDst = static_cast(pBuffer); if (Channels == 2) { // Stereo unsigned long readBytes = pCkData->Read(pSrc, SampleCount * 6, 1); @@ -742,14 +789,17 @@ currentframeoffset = this->FrameOffset; // offset in current sample frame since last Read() this->FrameOffset = 0; - if (assumedsize > this->DecompressionBufferSize) { - // local buffer reallocation - hope this won't happen - if (this->pDecompressionBuffer) delete[] this->pDecompressionBuffer; - this->pDecompressionBuffer = new unsigned char[assumedsize << 1]; // double of current needed size - this->DecompressionBufferSize = assumedsize << 1; + buffer_t* pDecompressionBuffer = (pExternalDecompressionBuffer) ? pExternalDecompressionBuffer : &InternalDecompressionBuffer; + + // if decompression buffer too small, then reduce amount of samples to read + if (pDecompressionBuffer->Size < assumedsize) { + std::cerr << "gig::Read(): WARNING - decompression buffer size too small!" << std::endl; + SampleCount = WorstCaseMaxSamples(pDecompressionBuffer); + remainingsamples = SampleCount; + assumedsize = GuessSize(SampleCount); } - unsigned char* pSrc = this->pDecompressionBuffer; + unsigned char* pSrc = (unsigned char*) pDecompressionBuffer->pStart; int16_t* pDst = static_cast(pBuffer); remainingbytes = pCkData->Read(pSrc, assumedsize, 1); @@ -832,13 +882,15 @@ const unsigned char* const param_r = pSrc; if (mode_r != 2) pSrc += 12; - Decompress24(mode_l, param_l, 2, pSrc, pDst, skipsamples, copysamples); + Decompress24(mode_l, param_l, 2, pSrc, pDst, + skipsamples, copysamples, TruncatedBits); Decompress24(mode_r, param_r, 2, pSrc + rightChannelOffset, pDst + 1, - skipsamples, copysamples); + skipsamples, copysamples, TruncatedBits); pDst += copysamples << 1; } else { // Mono - Decompress24(mode_l, param_l, 1, pSrc, pDst, skipsamples, copysamples); + Decompress24(mode_l, param_l, 1, pSrc, pDst, + skipsamples, copysamples, TruncatedBits); pDst += copysamples; } } @@ -870,8 +922,8 @@ assumedsize = GuessSize(remainingsamples); pCkData->SetPos(remainingbytes, RIFF::stream_backward); if (pCkData->RemainingBytes() < assumedsize) assumedsize = pCkData->RemainingBytes(); - remainingbytes = pCkData->Read(this->pDecompressionBuffer, assumedsize, 1); - pSrc = this->pDecompressionBuffer; + remainingbytes = pCkData->Read(pDecompressionBuffer->pStart, assumedsize, 1); + pSrc = (unsigned char*) pDecompressionBuffer->pStart; } } // while @@ -881,11 +933,54 @@ } } + /** + * Allocates a decompression buffer for streaming (compressed) samples + * with Sample::Read(). If you are using more than one streaming thread + * in your application you HAVE to create a decompression buffer + * for EACH of your streaming threads and provide it with the + * Sample::Read() call in order to avoid race conditions and crashes. + * + * You should free the memory occupied by the allocated buffer(s) once + * you don't need one of your streaming threads anymore by calling + * DestroyDecompressionBuffer(). + * + * @param MaxReadSize - the maximum size (in sample points) you ever + * expect to read with one Read() call + * @returns allocated decompression buffer + * @see DestroyDecompressionBuffer() + */ + buffer_t Sample::CreateDecompressionBuffer(unsigned long MaxReadSize) { + buffer_t result; + const double worstCaseHeaderOverhead = + (256.0 /*frame size*/ + 12.0 /*header*/ + 2.0 /*compression type flag (stereo)*/) / 256.0; + result.Size = (unsigned long) (double(MaxReadSize) * 3.0 /*(24 Bit)*/ * 2.0 /*stereo*/ * worstCaseHeaderOverhead); + result.pStart = new int8_t[result.Size]; + result.NullExtensionSize = 0; + return result; + } + + /** + * Free decompression buffer, previously created with + * CreateDecompressionBuffer(). + * + * @param DecompressionBuffer - previously allocated decompression + * buffer to free + */ + void Sample::DestroyDecompressionBuffer(buffer_t& DecompressionBuffer) { + if (DecompressionBuffer.Size && DecompressionBuffer.pStart) { + delete[] (int8_t*) DecompressionBuffer.pStart; + DecompressionBuffer.pStart = NULL; + DecompressionBuffer.Size = 0; + DecompressionBuffer.NullExtensionSize = 0; + } + } + Sample::~Sample() { Instances--; - if (!Instances && pDecompressionBuffer) { - delete[] pDecompressionBuffer; - pDecompressionBuffer = NULL; + if (!Instances && InternalDecompressionBuffer.Size) { + delete[] (unsigned char*) InternalDecompressionBuffer.pStart; + InternalDecompressionBuffer.pStart = NULL; + InternalDecompressionBuffer.Size = 0; } if (FrameTable) delete[] FrameTable; if (RAMCache.pStart) delete[] (int8_t*) RAMCache.pStart; @@ -1039,7 +1134,9 @@ VCFEnabled = vcfcutoff & 0x80; // bit 7 VCFCutoff = vcfcutoff & 0x7f; // lower 7 bits VCFCutoffController = static_cast(_3ewa->ReadUint8()); - VCFVelocityScale = _3ewa->ReadUint8(); + uint8_t vcfvelscale = _3ewa->ReadUint8(); + VCFCutoffControllerInvert = vcfvelscale & 0x80; // bit 7 + VCFVelocityScale = vcfvelscale & 0x7f; // lower 7 bits _3ewa->ReadInt8(); // unknown uint8_t vcfresonance = _3ewa->ReadUint8(); VCFResonance = vcfresonance & 0x7f; // lower 7 bits @@ -1056,18 +1153,53 @@ VCFType = vcf_type_lowpassturbo; } - // get the corresponding velocity->volume table from the table map or create & calculate that table if it doesn't exist yet - uint32_t tableKey = (VelocityResponseCurve<<16) | (VelocityResponseDepth<<8) | VelocityResponseCurveScaling; + pVelocityAttenuationTable = GetVelocityTable(VelocityResponseCurve, + VelocityResponseDepth, + VelocityResponseCurveScaling); + + curve_type_t curveType = ReleaseVelocityResponseCurve; + uint8_t depth = ReleaseVelocityResponseDepth; + + // this models a strange behaviour or bug in GSt: two of the + // velocity response curves for release time are not used even + // if specified, instead another curve is chosen. + if ((curveType == curve_type_nonlinear && depth == 0) || + (curveType == curve_type_special && depth == 4)) { + curveType = curve_type_nonlinear; + depth = 3; + } + pVelocityReleaseTable = GetVelocityTable(curveType, depth, 0); + + curveType = VCFVelocityCurve; + depth = VCFVelocityDynamicRange; + + // even stranger GSt: two of the velocity response curves for + // filter cutoff are not used, instead another special curve + // is chosen. This curve is not used anywhere else. + if ((curveType == curve_type_nonlinear && depth == 0) || + (curveType == curve_type_special && depth == 4)) { + curveType = curve_type_special; + depth = 5; + } + pVelocityCutoffTable = GetVelocityTable(curveType, depth, + VCFCutoffController <= vcf_cutoff_ctrl_none2 ? VCFVelocityScale : 0); + + SampleAttenuation = pow(10.0, -Gain / (20.0 * 655360)); + } + + // get the corresponding velocity table from the table map or create & calculate that table if it doesn't exist yet + double* DimensionRegion::GetVelocityTable(curve_type_t curveType, uint8_t depth, uint8_t scaling) + { + double* table; + uint32_t tableKey = (curveType<<16) | (depth<<8) | scaling; if (pVelocityTables->count(tableKey)) { // if key exists - pVelocityAttenuationTable = (*pVelocityTables)[tableKey]; + table = (*pVelocityTables)[tableKey]; } else { - pVelocityAttenuationTable = - CreateVelocityTable(VelocityResponseCurve, - VelocityResponseDepth, - VelocityResponseCurveScaling); - (*pVelocityTables)[tableKey] = pVelocityAttenuationTable; // put the new table into the tables map + table = CreateVelocityTable(curveType, depth, scaling); + (*pVelocityTables)[tableKey] = table; // put the new table into the tables map } + return table; } leverage_ctrl_t DimensionRegion::DecodeLeverageController(_lev_ctrl_t EncodedController) { @@ -1218,6 +1350,14 @@ return pVelocityAttenuationTable[MIDIKeyVelocity]; } + double DimensionRegion::GetVelocityRelease(uint8_t MIDIKeyVelocity) { + return pVelocityReleaseTable[MIDIKeyVelocity]; + } + + double DimensionRegion::GetVelocityCutoff(uint8_t MIDIKeyVelocity) { + return pVelocityCutoffTable[MIDIKeyVelocity]; + } + double* DimensionRegion::CreateVelocityTable(curve_type_t curveType, uint8_t depth, uint8_t scaling) { // line-segment approximations of the 15 velocity curves @@ -1251,9 +1391,13 @@ const int spe4[] = { 1, 4, 23, 5, 49, 13, 57, 17, 92, 57, 122, 127, 127, 127 }; + // this is only used by the VCF velocity curve + const int spe5[] = { 1, 2, 30, 5, 60, 19, 77, 70, 83, 85, 88, 106, + 91, 127, 127, 127 }; + const int* const curves[] = { non0, non1, non2, non3, non4, lin0, lin1, lin2, lin3, lin4, - spe0, spe1, spe2, spe3, spe4 }; + spe0, spe1, spe2, spe3, spe4, spe5 }; double* const table = new double[128]; @@ -1305,6 +1449,9 @@ for (int i = 0; i < dimensionBits; i++) { dimension_t dimension = static_cast(_3lnk->ReadUint8()); uint8_t bits = _3lnk->ReadUint8(); + _3lnk->ReadUint8(); // probably the position of the dimension + _3lnk->ReadUint8(); // unknown + uint8_t zones = _3lnk->ReadUint8(); // new for v3: number of zones doesn't have to be == pow(2,bits) if (dimension == dimension_none) { // inactive dimension pDimensionDefinitions[i].dimension = dimension_none; pDimensionDefinitions[i].bits = 0; @@ -1316,21 +1463,23 @@ else { // active dimension pDimensionDefinitions[i].dimension = dimension; pDimensionDefinitions[i].bits = bits; - pDimensionDefinitions[i].zones = 0x01 << bits; // = pow(2,bits) + pDimensionDefinitions[i].zones = zones ? zones : 0x01 << bits; // = pow(2,bits) pDimensionDefinitions[i].split_type = (dimension == dimension_layer || dimension == dimension_samplechannel || - dimension == dimension_releasetrigger) ? split_type_bit - : split_type_normal; + dimension == dimension_releasetrigger || + dimension == dimension_roundrobin || + dimension == dimension_random) ? split_type_bit + : split_type_normal; pDimensionDefinitions[i].ranges = NULL; // it's not possible to check velocity dimensions for custom defined ranges at this point pDimensionDefinitions[i].zone_size = - (pDimensionDefinitions[i].split_type == split_type_normal) ? 128 / pDimensionDefinitions[i].zones + (pDimensionDefinitions[i].split_type == split_type_normal) ? 128.0 / pDimensionDefinitions[i].zones : 0; Dimensions++; // if this is a layer dimension, remember the amount of layers if (dimension == dimension_layer) Layers = pDimensionDefinitions[i].zones; } - _3lnk->SetPos(6, RIFF::stream_curpos); // jump forward to next dimension definition + _3lnk->SetPos(3, RIFF::stream_curpos); // jump forward to next dimension definition } // check velocity dimension (if there is one) for custom defined zone ranges @@ -1428,7 +1577,7 @@ bits[i] = DimValues[i]; switch (pDimensionDefinitions[i].split_type) { case split_type_normal: - bits[i] /= pDimensionDefinitions[i].zone_size; + bits[i] = uint8_t(bits[i] / pDimensionDefinitions[i].zone_size); break; case split_type_customvelocity: bits[i] = VelocityTable[bits[i]]; @@ -1476,13 +1625,15 @@ else return static_cast(pSample = GetSampleFromWavePool(WavePoolTableIndex)); } - Sample* Region::GetSampleFromWavePool(unsigned int WavePoolTableIndex) { + Sample* Region::GetSampleFromWavePool(unsigned int WavePoolTableIndex, progress_t* pProgress) { if ((int32_t)WavePoolTableIndex == -1) return NULL; File* file = (File*) GetParent()->GetParent(); unsigned long soughtoffset = file->pWavePoolTable[WavePoolTableIndex]; - Sample* sample = file->GetFirstSample(); + unsigned long soughtfileno = file->pWavePoolTableHi[WavePoolTableIndex]; + Sample* sample = file->GetFirstSample(pProgress); while (sample) { - if (sample->ulWavePoolOffset == soughtoffset) return static_cast(pSample = sample); + if (sample->ulWavePoolOffset == soughtoffset && + sample->FileNo == soughtfileno) return static_cast(pSample = sample); sample = file->GetNextSample(); } return NULL; @@ -1493,7 +1644,7 @@ // *************** Instrument *************** // * - Instrument::Instrument(File* pFile, RIFF::List* insList) : DLS::Instrument((DLS::File*)pFile, insList) { + Instrument::Instrument(File* pFile, RIFF::List* insList, progress_t* pProgress) : DLS::Instrument((DLS::File*)pFile, insList) { // Initialization for (int i = 0; i < 128; i++) RegionKeyTable[i] = NULL; RegionIndex = -1; @@ -1524,6 +1675,7 @@ unsigned int iRegion = 0; while (rgn) { if (rgn->GetListType() == LIST_TYPE_RGN) { + __notify_progress(pProgress, (float) iRegion / (float) Regions); pRegions[iRegion] = new Region(this, rgn); iRegion++; } @@ -1536,6 +1688,8 @@ RegionKeyTable[iKey] = pRegions[iReg]; } } + + __notify_progress(pProgress, 1.0f); // notify done } Instrument::~Instrument() { @@ -1622,10 +1776,13 @@ pInstruments->clear(); delete pInstruments; } + // free extension files + for (std::list::iterator i = ExtensionFiles.begin() ; i != ExtensionFiles.end() ; i++) + delete *i; } - Sample* File::GetFirstSample() { - if (!pSamples) LoadSamples(); + Sample* File::GetFirstSample(progress_t* pProgress) { + if (!pSamples) LoadSamples(pProgress); if (!pSamples) return NULL; SamplesIterator = pSamples->begin(); return static_cast( (SamplesIterator != pSamples->end()) ? *SamplesIterator : NULL ); @@ -1637,21 +1794,56 @@ return static_cast( (SamplesIterator != pSamples->end()) ? *SamplesIterator : NULL ); } - void File::LoadSamples() { - RIFF::List* wvpl = pRIFF->GetSubList(LIST_TYPE_WVPL); - if (wvpl) { - unsigned long wvplFileOffset = wvpl->GetFilePos(); - RIFF::List* wave = wvpl->GetFirstSubList(); - while (wave) { - if (wave->GetListType() == LIST_TYPE_WAVE) { - if (!pSamples) pSamples = new SampleList; - unsigned long waveFileOffset = wave->GetFilePos(); - pSamples->push_back(new Sample(this, wave, waveFileOffset - wvplFileOffset)); + void File::LoadSamples(progress_t* pProgress) { + RIFF::File* file = pRIFF; + + // just for progress calculation + int iSampleIndex = 0; + int iTotalSamples = WavePoolCount; + + // check if samples should be loaded from extension files + int lastFileNo = 0; + for (int i = 0 ; i < WavePoolCount ; i++) { + if (pWavePoolTableHi[i] > lastFileNo) lastFileNo = pWavePoolTableHi[i]; + } + String name(pRIFF->GetFileName()); + int nameLen = name.length(); + char suffix[6]; + if (nameLen > 4 && name.substr(nameLen - 4) == ".gig") nameLen -= 4; + + for (int fileNo = 0 ; ; ) { + RIFF::List* wvpl = file->GetSubList(LIST_TYPE_WVPL); + if (wvpl) { + unsigned long wvplFileOffset = wvpl->GetFilePos(); + RIFF::List* wave = wvpl->GetFirstSubList(); + while (wave) { + if (wave->GetListType() == LIST_TYPE_WAVE) { + // notify current progress + const float subprogress = (float) iSampleIndex / (float) iTotalSamples; + __notify_progress(pProgress, subprogress); + + if (!pSamples) pSamples = new SampleList; + unsigned long waveFileOffset = wave->GetFilePos(); + pSamples->push_back(new Sample(this, wave, waveFileOffset - wvplFileOffset, fileNo)); + + iSampleIndex++; + } + wave = wvpl->GetNextSubList(); } - wave = wvpl->GetNextSubList(); + + if (fileNo == lastFileNo) break; + + // open extension file (*.gx01, *.gx02, ...) + fileNo++; + sprintf(suffix, ".gx%02d", fileNo); + name.replace(nameLen, 5, suffix); + file = new RIFF::File(name); + ExtensionFiles.push_back(file); } + else throw gig::Exception("Mandatory chunk not found."); } - else throw gig::Exception("Mandatory chunk not found."); + + __notify_progress(pProgress, 1.0); // notify done } Instrument* File::GetFirstInstrument() { @@ -1670,10 +1862,30 @@ /** * Returns the instrument with the given index. * + * @param index - number of the sought instrument (0..n) + * @param pProgress - optional: callback function for progress notification * @returns sought instrument or NULL if there's no such instrument */ - Instrument* File::GetInstrument(uint index) { - if (!pInstruments) LoadInstruments(); + Instrument* File::GetInstrument(uint index, progress_t* pProgress) { + if (!pInstruments) { + // TODO: hack - we simply load ALL samples here, it would have been done in the Region constructor anyway (ATM) + + // sample loading subtask + progress_t subprogress; + __divide_progress(pProgress, &subprogress, 3.0f, 0.0f); // randomly schedule 33% for this subtask + __notify_progress(&subprogress, 0.0f); + GetFirstSample(&subprogress); // now force all samples to be loaded + __notify_progress(&subprogress, 1.0f); + + // instrument loading subtask + if (pProgress && pProgress->callback) { + subprogress.__range_min = subprogress.__range_max; + subprogress.__range_max = pProgress->__range_max; // schedule remaining percentage for this subtask + } + __notify_progress(&subprogress, 0.0f); + LoadInstruments(&subprogress); + __notify_progress(&subprogress, 1.0f); + } if (!pInstruments) return NULL; InstrumentsIterator = pInstruments->begin(); for (uint i = 0; InstrumentsIterator != pInstruments->end(); i++) { @@ -1683,17 +1895,29 @@ return NULL; } - void File::LoadInstruments() { + void File::LoadInstruments(progress_t* pProgress) { RIFF::List* lstInstruments = pRIFF->GetSubList(LIST_TYPE_LINS); if (lstInstruments) { + int iInstrumentIndex = 0; RIFF::List* lstInstr = lstInstruments->GetFirstSubList(); while (lstInstr) { if (lstInstr->GetListType() == LIST_TYPE_INS) { + // notify current progress + const float localProgress = (float) iInstrumentIndex / (float) Instruments; + __notify_progress(pProgress, localProgress); + + // divide local progress into subprogress for loading current Instrument + progress_t subprogress; + __divide_progress(pProgress, &subprogress, Instruments, iInstrumentIndex); + if (!pInstruments) pInstruments = new InstrumentList; - pInstruments->push_back(new Instrument(this, lstInstr)); + pInstruments->push_back(new Instrument(this, lstInstr, &subprogress)); + + iInstrumentIndex++; } lstInstr = lstInstruments->GetNextSubList(); } + __notify_progress(pProgress, 1.0); // notify done } else throw gig::Exception("Mandatory list chunk not found."); } @@ -1710,4 +1934,25 @@ std::cout << "gig::Exception: " << Message << std::endl; } + +// *************** functions *************** +// * + + /** + * Returns the name of this C++ library. This is usually "libgig" of + * course. This call is equivalent to RIFF::libraryName() and + * DLS::libraryName(). + */ + String libraryName() { + return PACKAGE; + } + + /** + * Returns version of this C++ library. This call is equivalent to + * RIFF::libraryVersion() and DLS::libraryVersion(). + */ + String libraryVersion() { + return VERSION; + } + } // namespace gig