--- libgig/trunk/src/gig.cpp 2007/08/25 09:59:53 1301 +++ libgig/trunk/src/gig.cpp 2013/01/19 08:19:14 2402 @@ -2,7 +2,7 @@ * * * libgig - C++ cross-platform Gigasampler format file access library * * * - * Copyright (C) 2003-2007 by Christian Schoenebeck * + * Copyright (C) 2003-2013 by Christian Schoenebeck * * * * * * This library is free software; you can redistribute it and/or modify * @@ -25,6 +25,7 @@ #include "helper.h" +#include #include #include @@ -255,6 +256,69 @@ +// *************** Internal CRC-32 (Cyclic Redundancy Check) functions *************** +// * + + static uint32_t* __initCRCTable() { + static uint32_t res[256]; + + for (int i = 0 ; i < 256 ; i++) { + uint32_t c = i; + for (int j = 0 ; j < 8 ; j++) { + c = (c & 1) ? 0xedb88320 ^ (c >> 1) : c >> 1; + } + res[i] = c; + } + return res; + } + + static const uint32_t* __CRCTable = __initCRCTable(); + + /** + * Initialize a CRC variable. + * + * @param crc - variable to be initialized + */ + inline static void __resetCRC(uint32_t& crc) { + crc = 0xffffffff; + } + + /** + * Used to calculate checksums of the sample data in a gig file. The + * checksums are stored in the 3crc chunk of the gig file and + * automatically updated when a sample is written with Sample::Write(). + * + * One should call __resetCRC() to initialize the CRC variable to be + * used before calling this function the first time. + * + * After initializing the CRC variable one can call this function + * arbitrary times, i.e. to split the overall CRC calculation into + * steps. + * + * Once the whole data was processed by __calculateCRC(), one should + * call __encodeCRC() to get the final CRC result. + * + * @param buf - pointer to data the CRC shall be calculated of + * @param bufSize - size of the data to be processed + * @param crc - variable the CRC sum shall be stored to + */ + static void __calculateCRC(unsigned char* buf, int bufSize, uint32_t& crc) { + for (int i = 0 ; i < bufSize ; i++) { + crc = __CRCTable[(crc ^ buf[i]) & 0xff] ^ (crc >> 8); + } + } + + /** + * Returns the final CRC result. + * + * @param crc - variable previously passed to __calculateCRC() + */ + inline static uint32_t __encodeCRC(const uint32_t& crc) { + return crc ^ 0xffffffff; + } + + + // *************** Other Internal functions *************** // * @@ -278,26 +342,6 @@ -// *************** CRC *************** -// * - - const uint32_t* CRC::table(initTable()); - - uint32_t* CRC::initTable() { - uint32_t* res = new uint32_t[256]; - - for (int i = 0 ; i < 256 ; i++) { - uint32_t c = i; - for (int j = 0 ; j < 8 ; j++) { - c = (c & 1) ? 0xedb88320 ^ (c >> 1) : c >> 1; - } - res[i] = c; - } - return res; - } - - - // *************** Sample *************** // * @@ -323,14 +367,16 @@ * is located, 0 otherwise */ Sample::Sample(File* pFile, RIFF::List* waveList, unsigned long WavePoolOffset, unsigned long fileNo) : DLS::Sample((DLS::File*) pFile, waveList, WavePoolOffset) { - static const DLS::Info::FixedStringLength fixedStringLengths[] = { + static const DLS::Info::string_length_t fixedStringLengths[] = { { CHUNK_ID_INAM, 64 }, { 0, 0 } }; - pInfo->FixedStringLengths = fixedStringLengths; + pInfo->SetFixedStringLengths(fixedStringLengths); Instances++; FileNo = fileNo; + __resetCRC(crc); + pCk3gix = waveList->GetSubChunk(CHUNK_ID_3GIX); if (pCk3gix) { uint16_t iSampleGroup = pCk3gix->ReadInt16(); @@ -631,6 +677,7 @@ if (SampleCount > this->SamplesTotal) SampleCount = this->SamplesTotal; if (RAMCache.pStart) delete[] (int8_t*) RAMCache.pStart; unsigned long allocationsize = (SampleCount + NullSamplesCount) * this->FrameSize; + SetPos(0); // reset read position to begin of sample RAMCache.pStart = new int8_t[allocationsize]; RAMCache.Size = Read(RAMCache.pStart, SampleCount) * this->FrameSize; RAMCache.NullExtensionSize = allocationsize - RAMCache.Size; @@ -668,6 +715,7 @@ if (RAMCache.pStart) delete[] (int8_t*) RAMCache.pStart; RAMCache.pStart = NULL; RAMCache.Size = 0; + RAMCache.NullExtensionSize = 0; } /** @brief Resize sample. @@ -862,7 +910,8 @@ } // reverse the sample frames for backward playback - SwapMemoryArea(&pDst[swapareastart * this->FrameSize], (totalreadsamples - swapareastart) * this->FrameSize, this->FrameSize); + if (totalreadsamples > swapareastart) //FIXME: this if() is just a crash workaround for now (#102), but totalreadsamples <= swapareastart should never be the case, so there's probably still a bug above! + SwapMemoryArea(&pDst[swapareastart * this->FrameSize], (totalreadsamples - swapareastart) * this->FrameSize, this->FrameSize); } } while (samplestoread && readsamples); break; @@ -1168,7 +1217,7 @@ // if this is the first write in this sample, reset the // checksum calculator if (pCkData->GetPos() == 0) { - crc.reset(); + __resetCRC(crc); } if (GetSize() < SampleCount) throw Exception("Could not write sample data, current sample size to small"); unsigned long res; @@ -1178,13 +1227,13 @@ res = Channels == 2 ? pCkData->Write(pBuffer, SampleCount << 1, 2) >> 1 : pCkData->Write(pBuffer, SampleCount, 2); } - crc.update((unsigned char *)pBuffer, SampleCount * FrameSize); + __calculateCRC((unsigned char *)pBuffer, SampleCount * FrameSize, crc); // if this is the last write, update the checksum chunk in the // file if (pCkData->GetPos() == pCkData->GetSize()) { File* pFile = static_cast(GetParent()); - pFile->SetSampleChecksum(this, crc.getValue()); + pFile->SetSampleChecksum(this, __encodeCRC(crc)); } return res; } @@ -1262,10 +1311,11 @@ uint DimensionRegion::Instances = 0; DimensionRegion::VelocityTableMap* DimensionRegion::pVelocityTables = NULL; - DimensionRegion::DimensionRegion(RIFF::List* _3ewl) : DLS::Sampler(_3ewl) { + DimensionRegion::DimensionRegion(Region* pParent, RIFF::List* _3ewl) : DLS::Sampler(_3ewl) { Instances++; pSample = NULL; + pRegion = pParent; if (_3ewl->GetSubChunk(CHUNK_ID_WSMP)) memcpy(&Crossfade, &SamplerOptions, 4); else memset(&Crossfade, 0, 4); @@ -1383,7 +1433,7 @@ : vcf_res_ctrl_none; uint16_t eg3depth = _3ewa->ReadUint16(); EG3Depth = (eg3depth <= 1200) ? eg3depth /* positives */ - : (-1) * (int16_t) ((eg3depth ^ 0xffff) + 1); /* binary complementary for negatives */ + : (-1) * (int16_t) ((eg3depth ^ 0xfff) + 1); /* binary complementary for negatives */ _3ewa->ReadInt16(); // unknown ChannelOffset = _3ewa->ReadUint8() / 4; uint8_t regoptions = _3ewa->ReadUint8(); @@ -1511,32 +1561,15 @@ 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); + pVelocityReleaseTable = GetReleaseVelocityTable( + ReleaseVelocityResponseCurve, + ReleaseVelocityResponseDepth + ); + + pVelocityCutoffTable = GetCutoffVelocityTable(VCFVelocityCurve, + VCFVelocityDynamicRange, + VCFVelocityScale, + VCFCutoffController); SampleAttenuation = pow(10.0, -Gain / (20.0 * 655360)); VelocityTable = 0; @@ -1548,6 +1581,7 @@ */ DimensionRegion::DimensionRegion(RIFF::List* _3ewl, const DimensionRegion& src) : DLS::Sampler(_3ewl) { Instances++; + //NOTE: I think we cannot call CopyAssign() here (in a constructor) as long as its a virtual method *this = src; // default memberwise shallow copy of all parameters pParentList = _3ewl; // restore the chunk pointer @@ -1563,6 +1597,50 @@ pSampleLoops[k] = src.pSampleLoops[k]; } } + + /** + * Make a (semi) deep copy of the DimensionRegion object given by @a orig + * and assign it to this object. + * + * Note that all sample pointers referenced by @a orig are simply copied as + * memory address. Thus the respective samples are shared, not duplicated! + * + * @param orig - original DimensionRegion object to be copied from + */ + void DimensionRegion::CopyAssign(const DimensionRegion* orig) { + // delete all allocated data first + if (VelocityTable) delete [] VelocityTable; + if (pSampleLoops) delete [] pSampleLoops; + + // backup parent list pointer + RIFF::List* p = pParentList; + + //NOTE: copy code copied from assignment constructor above, see comment there as well + + *this = *orig; // default memberwise shallow copy of all parameters + pParentList = p; // restore the chunk pointer + + // deep copy of owned structures + if (orig->VelocityTable) { + VelocityTable = new uint8_t[128]; + for (int k = 0 ; k < 128 ; k++) + VelocityTable[k] = orig->VelocityTable[k]; + } + if (orig->pSampleLoops) { + pSampleLoops = new DLS::sample_loop_t[orig->SampleLoops]; + for (int k = 0 ; k < orig->SampleLoops ; k++) + pSampleLoops[k] = orig->pSampleLoops[k]; + } + } + + /** + * Updates the respective member variable and updates @c SampleAttenuation + * which depends on this value. + */ + void DimensionRegion::SetGain(int32_t gain) { + DLS::Sampler::SetGain(gain); + SampleAttenuation = pow(10.0, -Gain / (20.0 * 655360)); + } /** * Apply dimension region settings to the respective RIFF chunks. You @@ -1572,10 +1650,6 @@ * It will be called automatically when File::Save() was called. */ void DimensionRegion::UpdateChunks() { - // check if wsmp is going to be created by - // DLS::Sampler::UpdateChunks - bool wsmp_created = !pParentList->GetSubChunk(CHUNK_ID_WSMP); - // first update base class's chunk DLS::Sampler::UpdateChunks(); @@ -1588,10 +1662,10 @@ // make sure '3ewa' chunk exists RIFF::Chunk* _3ewa = pParentList->GetSubChunk(CHUNK_ID_3EWA); - if (!_3ewa) _3ewa = pParentList->AddSubChunk(CHUNK_ID_3EWA, 140); - else if (wsmp_created) { - // make sure the chunk order is: wsmp, 3ewa - pParentList->MoveSubChunk(_3ewa, 0); + if (!_3ewa) { + File* pFile = (File*) GetParent()->GetParent()->GetParent(); + bool version3 = pFile->pVersion && pFile->pVersion->major == 3; + _3ewa = pParentList->AddSubChunk(CHUNK_ID_3EWA, version3 ? 148 : 140); } pData = (uint8_t*) _3ewa->LoadChunkData(); @@ -1799,8 +1873,8 @@ } const uint16_t eg3depth = (EG3Depth >= 0) ? EG3Depth - : uint16_t(((-EG3Depth) - 1) ^ 0xffff); /* binary complementary for negatives */ - pData[116] = eg3depth; + : uint16_t(((-EG3Depth) - 1) ^ 0xfff); /* binary complementary for negatives */ + store16(&pData[116], eg3depth); // next 2 bytes unknown @@ -1847,7 +1921,7 @@ (VCFKeyboardTrackingBreakpoint & 0x7f); /* lower 7 bits */ pData[137] = vcfbreakpoint; - const uint8_t vcfvelocity = VCFVelocityDynamicRange % 5 | + const uint8_t vcfvelocity = VCFVelocityDynamicRange % 5 + VCFVelocityCurve * 5; pData[138] = vcfvelocity; @@ -1859,6 +1933,40 @@ } } + double* DimensionRegion::GetReleaseVelocityTable(curve_type_t releaseVelocityResponseCurve, uint8_t releaseVelocityResponseDepth) { + 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; + } + return GetVelocityTable(curveType, depth, 0); + } + + double* DimensionRegion::GetCutoffVelocityTable(curve_type_t vcfVelocityCurve, + uint8_t vcfVelocityDynamicRange, + uint8_t vcfVelocityScale, + vcf_cutoff_ctrl_t vcfCutoffController) + { + curve_type_t curveType = vcfVelocityCurve; + uint8_t 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; + } + return GetVelocityTable(curveType, depth, + (vcfCutoffController <= vcf_cutoff_ctrl_none2) + ? vcfVelocityScale : 0); + } + // 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) { @@ -1874,6 +1982,10 @@ return table; } + Region* DimensionRegion::GetParent() const { + return pRegion; + } + leverage_ctrl_t DimensionRegion::DecodeLeverageController(_lev_ctrl_t EncodedController) { leverage_ctrl_t decodedcontroller; switch (EncodedController) { @@ -2127,6 +2239,96 @@ return pVelocityCutoffTable[MIDIKeyVelocity]; } + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetVelocityResponseCurve(curve_type_t curve) { + pVelocityAttenuationTable = + GetVelocityTable( + curve, VelocityResponseDepth, VelocityResponseCurveScaling + ); + VelocityResponseCurve = curve; + } + + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetVelocityResponseDepth(uint8_t depth) { + pVelocityAttenuationTable = + GetVelocityTable( + VelocityResponseCurve, depth, VelocityResponseCurveScaling + ); + VelocityResponseDepth = depth; + } + + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetVelocityResponseCurveScaling(uint8_t scaling) { + pVelocityAttenuationTable = + GetVelocityTable( + VelocityResponseCurve, VelocityResponseDepth, scaling + ); + VelocityResponseCurveScaling = scaling; + } + + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetReleaseVelocityResponseCurve(curve_type_t curve) { + pVelocityReleaseTable = GetReleaseVelocityTable(curve, ReleaseVelocityResponseDepth); + ReleaseVelocityResponseCurve = curve; + } + + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetReleaseVelocityResponseDepth(uint8_t depth) { + pVelocityReleaseTable = GetReleaseVelocityTable(ReleaseVelocityResponseCurve, depth); + ReleaseVelocityResponseDepth = depth; + } + + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetVCFCutoffController(vcf_cutoff_ctrl_t controller) { + pVelocityCutoffTable = GetCutoffVelocityTable(VCFVelocityCurve, VCFVelocityDynamicRange, VCFVelocityScale, controller); + VCFCutoffController = controller; + } + + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetVCFVelocityCurve(curve_type_t curve) { + pVelocityCutoffTable = GetCutoffVelocityTable(curve, VCFVelocityDynamicRange, VCFVelocityScale, VCFCutoffController); + VCFVelocityCurve = curve; + } + + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetVCFVelocityDynamicRange(uint8_t range) { + pVelocityCutoffTable = GetCutoffVelocityTable(VCFVelocityCurve, range, VCFVelocityScale, VCFCutoffController); + VCFVelocityDynamicRange = range; + } + + /** + * Updates the respective member variable and the lookup table / cache + * that depends on this value. + */ + void DimensionRegion::SetVCFVelocityScale(uint8_t scaling) { + pVelocityCutoffTable = GetCutoffVelocityTable(VCFVelocityCurve, VCFVelocityDynamicRange, scaling, VCFCutoffController); + VCFVelocityScale = scaling; + } + double* DimensionRegion::CreateVelocityTable(curve_type_t curveType, uint8_t depth, uint8_t scaling) { // line-segment approximations of the 15 velocity curves @@ -2210,6 +2412,8 @@ // Actual Loading + if (!file->GetAutoLoad()) return; + LoadDimensionRegions(rgnList); RIFF::Chunk* _3lnk = rgnList->GetSubChunk(CHUNK_ID_3LNK); @@ -2253,12 +2457,14 @@ else _3lnk->SetPos(44); - // load sample references - for (uint i = 0; i < DimensionRegions; i++) { - uint32_t wavepoolindex = _3lnk->ReadUint32(); - if (file->pWavePoolTable) pDimensionRegions[i]->pSample = GetSampleFromWavePool(wavepoolindex); + // load sample references (if auto loading is enabled) + if (file->GetAutoLoad()) { + for (uint i = 0; i < DimensionRegions; i++) { + uint32_t wavepoolindex = _3lnk->ReadUint32(); + if (file->pWavePoolTable) pDimensionRegions[i]->pSample = GetSampleFromWavePool(wavepoolindex); + } + GetSample(); // load global region sample reference } - GetSample(); // load global region sample reference } else { DimensionRegions = 0; for (int i = 0 ; i < 8 ; i++) { @@ -2273,7 +2479,7 @@ RIFF::List* _3prg = rgnList->GetSubList(LIST_TYPE_3PRG); if (!_3prg) _3prg = rgnList->AddSubList(LIST_TYPE_3PRG); RIFF::List* _3ewl = _3prg->AddSubList(LIST_TYPE_3EWL); - pDimensionRegions[0] = new DimensionRegion(_3ewl); + pDimensionRegions[0] = new DimensionRegion(this, _3ewl); DimensionRegions = 1; } } @@ -2297,22 +2503,13 @@ // first update base class's chunks DLS::Region::UpdateChunks(); - File* pFile = (File*) GetParent()->GetParent(); - bool version3 = pFile->pVersion && pFile->pVersion->major == 3; - // update dimension region's chunks for (int i = 0; i < DimensionRegions; i++) { - DimensionRegion* d = pDimensionRegions[i]; - - // make sure '3ewa' chunk exists (we need to this before - // calling DimensionRegion::UpdateChunks, as - // DimensionRegion doesn't know which file version it is) - RIFF::Chunk* _3ewa = d->pParentList->GetSubChunk(CHUNK_ID_3EWA); - if (!_3ewa) d->pParentList->AddSubChunk(CHUNK_ID_3EWA, version3 ? 148 : 140); - - d->UpdateChunks(); + pDimensionRegions[i]->UpdateChunks(); } + File* pFile = (File*) GetParent()->GetParent(); + bool version3 = pFile->pVersion && pFile->pVersion->major == 3; const int iMaxDimensions = version3 ? 8 : 5; const int iMaxDimensionRegions = version3 ? 256 : 32; @@ -2368,7 +2565,7 @@ RIFF::List* _3ewl = _3prg->GetFirstSubList(); while (_3ewl) { if (_3ewl->GetListType() == LIST_TYPE_3EWL) { - pDimensionRegions[dimensionRegionNr] = new DimensionRegion(_3ewl); + pDimensionRegions[dimensionRegionNr] = new DimensionRegion(this, _3ewl); dimensionRegionNr++; } _3ewl = _3prg->GetNextSubList(); @@ -2377,6 +2574,13 @@ } } + void Region::SetKeyRange(uint16_t Low, uint16_t High) { + // update KeyRange struct and make sure regions are in correct order + DLS::Region::SetKeyRange(Low, High); + // update Region key table for fast lookup + ((gig::Instrument*)GetParent())->UpdateRegionKeyTable(); + } + void Region::UpdateVelocityTable() { // get velocity dimension's index int veldim = -1; @@ -2757,19 +2961,74 @@ } return NULL; } + + /** + * Make a (semi) deep copy of the Region object given by @a orig + * and assign it to this object. + * + * Note that all sample pointers referenced by @a orig are simply copied as + * memory address. Thus the respective samples are shared, not duplicated! + * + * @param orig - original Region object to be copied from + */ + void Region::CopyAssign(const Region* orig) { + // handle base classes + DLS::Region::CopyAssign(orig); + + // handle own member variables + for (int i = Dimensions - 1; i >= 0; --i) { + DeleteDimension(&pDimensionDefinitions[i]); + } + Layers = 0; // just to be sure + for (int i = 0; i < orig->Dimensions; i++) { + // we need to copy the dim definition here, to avoid the compiler + // complaining about const-ness issue + dimension_def_t def = orig->pDimensionDefinitions[i]; + AddDimension(&def); + } + for (int i = 0; i < 256; i++) { + if (pDimensionRegions[i] && orig->pDimensionRegions[i]) { + pDimensionRegions[i]->CopyAssign( + orig->pDimensionRegions[i] + ); + } + } + Layers = orig->Layers; + } +// *************** MidiRule *************** +// * + +MidiRuleCtrlTrigger::MidiRuleCtrlTrigger(RIFF::Chunk* _3ewg) { + _3ewg->SetPos(36); + Triggers = _3ewg->ReadUint8(); + _3ewg->SetPos(40); + ControllerNumber = _3ewg->ReadUint8(); + _3ewg->SetPos(46); + for (int i = 0 ; i < Triggers ; i++) { + pTriggers[i].TriggerPoint = _3ewg->ReadUint8(); + pTriggers[i].Descending = _3ewg->ReadUint8(); + pTriggers[i].VelSensitivity = _3ewg->ReadUint8(); + pTriggers[i].Key = _3ewg->ReadUint8(); + pTriggers[i].NoteOff = _3ewg->ReadUint8(); + pTriggers[i].Velocity = _3ewg->ReadUint8(); + pTriggers[i].OverridePedal = _3ewg->ReadUint8(); + _3ewg->ReadUint8(); + } +} + // *************** Instrument *************** // * Instrument::Instrument(File* pFile, RIFF::List* insList, progress_t* pProgress) : DLS::Instrument((DLS::File*)pFile, insList) { - static const DLS::Info::FixedStringLength fixedStringLengths[] = { + static const DLS::Info::string_length_t fixedStringLengths[] = { { CHUNK_ID_INAM, 64 }, { CHUNK_ID_ISFT, 12 }, { 0, 0 } }; - pInfo->FixedStringLengths = fixedStringLengths; + pInfo->SetFixedStringLengths(fixedStringLengths); // Initialization for (int i = 0; i < 128; i++) RegionKeyTable[i] = NULL; @@ -2780,6 +3039,8 @@ PianoReleaseMode = false; DimensionKeyRange.low = 0; DimensionKeyRange.high = 0; + pMidiRules = new MidiRule*[3]; + pMidiRules[0] = NULL; // Loading RIFF::List* lart = insList->GetSubList(LIST_TYPE_LART); @@ -2794,28 +3055,46 @@ PianoReleaseMode = dimkeystart & 0x01; DimensionKeyRange.low = dimkeystart >> 1; DimensionKeyRange.high = _3ewg->ReadUint8(); + + if (_3ewg->GetSize() > 32) { + // read MIDI rules + int i = 0; + _3ewg->SetPos(32); + uint8_t id1 = _3ewg->ReadUint8(); + uint8_t id2 = _3ewg->ReadUint8(); + + if (id1 == 4 && id2 == 16) { + pMidiRules[i++] = new MidiRuleCtrlTrigger(_3ewg); + } + //TODO: all the other types of rules + + pMidiRules[i] = NULL; + } } } - if (!pRegions) pRegions = new RegionList; - RIFF::List* lrgn = insList->GetSubList(LIST_TYPE_LRGN); - if (lrgn) { - RIFF::List* rgn = lrgn->GetFirstSubList(); - while (rgn) { - if (rgn->GetListType() == LIST_TYPE_RGN) { - __notify_progress(pProgress, (float) pRegions->size() / (float) Regions); - pRegions->push_back(new Region(this, rgn)); + if (pFile->GetAutoLoad()) { + if (!pRegions) pRegions = new RegionList; + RIFF::List* lrgn = insList->GetSubList(LIST_TYPE_LRGN); + if (lrgn) { + RIFF::List* rgn = lrgn->GetFirstSubList(); + while (rgn) { + if (rgn->GetListType() == LIST_TYPE_RGN) { + __notify_progress(pProgress, (float) pRegions->size() / (float) Regions); + pRegions->push_back(new Region(this, rgn)); + } + rgn = lrgn->GetNextSubList(); } - rgn = lrgn->GetNextSubList(); + // Creating Region Key Table for fast lookup + UpdateRegionKeyTable(); } - // Creating Region Key Table for fast lookup - UpdateRegionKeyTable(); } __notify_progress(pProgress, 1.0f); // notify done } void Instrument::UpdateRegionKeyTable() { + for (int i = 0; i < 128; i++) RegionKeyTable[i] = NULL; RegionList::iterator iter = pRegions->begin(); RegionList::iterator end = pRegions->end(); for (; iter != end; ++iter) { @@ -2827,6 +3106,10 @@ } Instrument::~Instrument() { + for (int i = 0 ; pMidiRules[i] ; i++) { + delete pMidiRules[i]; + } + delete[] pMidiRules; } /** @@ -2883,7 +3166,7 @@ * there is no Region defined for the given \a Key */ Region* Instrument::GetRegion(unsigned int Key) { - if (!pRegions || !pRegions->size() || Key > 127) return NULL; + if (!pRegions || pRegions->empty() || Key > 127) return NULL; return RegionKeyTable[Key]; /*for (int i = 0; i < Regions; i++) { @@ -2941,6 +3224,65 @@ UpdateRegionKeyTable(); } + /** + * Returns a MIDI rule of the instrument. + * + * The list of MIDI rules, at least in gig v3, always contains at + * most two rules. The second rule can only be the DEF filter + * (which currently isn't supported by libgig). + * + * @param i - MIDI rule number + * @returns pointer address to MIDI rule number i or NULL if there is none + */ + MidiRule* Instrument::GetMidiRule(int i) { + return pMidiRules[i]; + } + + /** + * Make a (semi) deep copy of the Instrument object given by @a orig + * and assign it to this object. + * + * Note that all sample pointers referenced by @a orig are simply copied as + * memory address. Thus the respective samples are shared, not duplicated! + * + * @param orig - original Instrument object to be copied from + */ + void Instrument::CopyAssign(const Instrument* orig) { + // handle base class + // (without copying DLS region stuff) + DLS::Instrument::CopyAssignCore(orig); + + // handle own member variables + Attenuation = orig->Attenuation; + EffectSend = orig->EffectSend; + FineTune = orig->FineTune; + PitchbendRange = orig->PitchbendRange; + PianoReleaseMode = orig->PianoReleaseMode; + DimensionKeyRange = orig->DimensionKeyRange; + + // free old midi rules + for (int i = 0 ; pMidiRules[i] ; i++) { + delete pMidiRules[i]; + } + //TODO: MIDI rule copying + pMidiRules[0] = NULL; + + // delete all old regions + while (Regions) DeleteRegion(GetFirstRegion()); + // create new regions and copy them from original + { + RegionList::const_iterator it = orig->pRegions->begin(); + for (int i = 0; i < orig->Regions; ++i, ++it) { + Region* dstRgn = AddRegion(); + //NOTE: Region does semi-deep copy ! + dstRgn->CopyAssign( + static_cast(*it) + ); + } + } + + UpdateRegionKeyTable(); + } // *************** Group *************** @@ -3066,17 +3408,17 @@ // *************** File *************** // * - // File version 2.0, 1998-06-28 + /// Reflects Gigasampler file format version 2.0 (1998-06-28). const DLS::version_t File::VERSION_2 = { 0, 2, 19980628 & 0xffff, 19980628 >> 16 }; - // File version 3.0, 2003-03-31 + /// Reflects Gigasampler file format version 3.0 (2003-03-31). const DLS::version_t File::VERSION_3 = { 0, 3, 20030331 & 0xffff, 20030331 >> 16 }; - const DLS::Info::FixedStringLength File::FixedStringLengths[] = { + static const DLS::Info::string_length_t _FileFixedStringLengths[] = { { CHUNK_ID_IARL, 256 }, { CHUNK_ID_IART, 128 }, { CHUNK_ID_ICMS, 128 }, @@ -3098,9 +3440,10 @@ }; File::File() : DLS::File() { + bAutoLoad = true; *pVersion = VERSION_3; pGroups = NULL; - pInfo->FixedStringLengths = FixedStringLengths; + pInfo->SetFixedStringLengths(_FileFixedStringLengths); pInfo->ArchivalLocation = String(256, ' '); // add some mandatory chunks to get the file chunks in right @@ -3113,8 +3456,9 @@ } File::File(RIFF::File* pRIFF) : DLS::File(pRIFF) { + bAutoLoad = true; pGroups = NULL; - pInfo->FixedStringLengths = FixedStringLengths; + pInfo->SetFixedStringLengths(_FileFixedStringLengths); } File::~File() { @@ -3182,6 +3526,7 @@ pSamples->erase(iter); delete pSample; + SampleList::iterator tmp = SamplesIterator; // remove all references to the sample for (Instrument* instrument = GetFirstInstrument() ; instrument ; instrument = GetNextInstrument()) { @@ -3196,6 +3541,7 @@ } } } + SamplesIterator = tmp; // restore iterator } void File::LoadSamples() { @@ -3286,7 +3632,8 @@ 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 + if (GetAutoLoad()) + GetFirstSample(&subprogress); // now force all samples to be loaded __notify_progress(&subprogress, 1.0f); // instrument loading subtask @@ -3335,6 +3682,27 @@ pInstruments->push_back(pInstrument); return pInstrument; } + + /** @brief Add a duplicate of an existing instrument. + * + * Duplicates the instrument definition given by @a orig and adds it + * to this file. This allows in an instrument editor application to + * easily create variations of an instrument, which will be stored in + * the same .gig file, sharing i.e. the same samples. + * + * Note that all sample pointers referenced by @a orig are simply copied as + * memory address. Thus the respective samples are shared, not duplicated! + * + * You have to call Save() to make this persistent to the file. + * + * @param orig - original instrument to be copied + * @returns duplicated copy of the given instrument + */ + Instrument* File::AddDuplicateInstrument(const Instrument* orig) { + Instrument* instr = AddInstrument(); + instr->CopyAssign(orig); + return instr; + } /** @brief Delete an instrument. * @@ -3693,6 +4061,33 @@ } } + /** + * Enable / disable automatic loading. By default this properyt is + * enabled and all informations are loaded automatically. However + * loading all Regions, DimensionRegions and especially samples might + * take a long time for large .gig files, and sometimes one might only + * be interested in retrieving very superficial informations like the + * amount of instruments and their names. In this case one might disable + * automatic loading to avoid very slow response times. + * + * @e CAUTION: by disabling this property many pointers (i.e. sample + * references) and informations will have invalid or even undefined + * data! This feature is currently only intended for retrieving very + * superficial informations in a very fast way. Don't use it to retrieve + * details like synthesis informations or even to modify .gig files! + */ + void File::SetAutoLoad(bool b) { + bAutoLoad = b; + } + + /** + * Returns whether automatic loading is enabled. + * @see SetAutoLoad() + */ + bool File::GetAutoLoad() { + return bAutoLoad; + } + // *************** Exception ***************