/** @brief Combine given list of instruments to one instrument. * * Takes a list of @a instruments as argument and combines them to one single * new @a output instrument. For this task, it will create a dimension of type * given by @a mainDimension in the new instrument and copies the source * instruments to those dimension zones. * * @param instruments - (input) list of instruments that shall be combined, * they will only be read, so they will be left untouched * @param gig - (input/output) .gig file where the new combined instrument shall * be created * @param output - (output) on success this pointer will be set to the new * instrument being created * @param mainDimension - the dimension that shall be used to combine the * instruments * @throw RIFF::Exception on any kinds of errors */ static void combineInstruments(std::vector<gig::Instrument*>& instruments, gig::File* gig, gig::Instrument*& output, gig::dimension_t mainDimension) { output = NULL; // divide the individual regions to (probably even smaller) groups of // regions, coping with the fact that the source regions of the instruments // might have quite different range sizes and start and end points RegionGroups groups = groupByRegionIntersections(instruments); #if DEBUG_COMBINE_INSTRUMENTS std::cout << std::endl << "New regions: " << std::flush; printRanges(groups); std::cout << std::endl; #endif if (groups.empty()) throw gig::Exception(_("No regions found to create a new instrument with.")); // create a new output instrument gig::Instrument* outInstr = gig->AddInstrument(); outInstr->pInfo->Name = _("NEW COMBINATION"); // Distinguishing in the following code block between 'horizontal' and // 'vertical' regions. The 'horizontal' ones are meant to be the key ranges // in the output instrument, while the 'vertical' regions are meant to be // the set of source regions that shall be layered to that 'horizontal' // region / key range. It is important to know, that the key ranges defined // in the 'horizontal' and 'vertical' regions might differ. // merge the instruments to the new output instrument for (RegionGroups::iterator itGroup = groups.begin(); itGroup != groups.end(); ++itGroup) // iterate over 'horizontal' / target regions ... { gig::Region* outRgn = outInstr->AddRegion(); outRgn->SetKeyRange(itGroup->first.low, itGroup->first.high); #if DEBUG_COMBINE_INSTRUMENTS printf("---> Start target region %d..%d\n", itGroup->first.low, itGroup->first.high); #endif // detect the total amount of zones required for the given main // dimension to build up this combi for current key range int iTotalZones = 0; for (RegionGroup::iterator itRgn = itGroup->second.begin(); itRgn != itGroup->second.end(); ++itRgn) { gig::Region* inRgn = itRgn->second; gig::dimension_def_t* def = inRgn->GetDimensionDefinition(mainDimension); iTotalZones += (def) ? def->zones : 1; } #if DEBUG_COMBINE_INSTRUMENTS printf("Required total zones: %d, vertical regions: %d\n", iTotalZones, itGroup->second.size()); #endif // create all required dimensions for this output region // (except the main dimension used for separating the individual // instruments, we create that particular dimension as next step) Dimensions dims = getDimensionsForRegionGroup(itGroup->second); // the given main dimension which is used to combine the instruments is // created separately after the next code block, and the main dimension // should not be part of dims here, because it also used for iterating // all dimensions zones, which would lead to this dimensions being // iterated twice dims.erase(mainDimension); { std::vector<gig::dimension_t> skipTheseDimensions; // used to prevent a misbehavior (i.e. crash) of the combine algorithm in case one of the source instruments has a dimension with only one zone, which is not standard conform for (Dimensions::iterator itDim = dims.begin(); itDim != dims.end(); ++itDim) { gig::dimension_def_t def; def.dimension = itDim->first; // dimension type def.zones = itDim->second.size(); def.bits = zoneCountToBits(def.zones); if (def.zones < 2) { addWarning( "Attempt to create dimension with type=0x%x with only " "ONE zone (because at least one of the source " "instruments seems to have such a velocity dimension " "with only ONE zone, which is odd)! Skipping this " "dimension for now.", (int)itDim->first ); skipTheseDimensions.push_back(itDim->first); continue; } #if DEBUG_COMBINE_INSTRUMENTS std::cout << "Adding new regular dimension type=" << std::hex << (int)def.dimension << std::dec << ", zones=" << (int)def.zones << ", bits=" << (int)def.bits << " ... " << std::flush; #endif outRgn->AddDimension(&def); #if DEBUG_COMBINE_INSTRUMENTS std::cout << "OK" << std::endl << std::flush; #endif } // prevent the following dimensions to be processed further below // (since the respective dimension was not created above) for (int i = 0; i < skipTheseDimensions.size(); ++i) dims.erase(skipTheseDimensions[i]); } // create the main dimension (if necessary for current key range) if (iTotalZones > 1) { gig::dimension_def_t def; def.dimension = mainDimension; // dimension type def.zones = iTotalZones; def.bits = zoneCountToBits(def.zones); #if DEBUG_COMBINE_INSTRUMENTS std::cout << "Adding new main combi dimension type=" << std::hex << (int)def.dimension << std::dec << ", zones=" << (int)def.zones << ", bits=" << (int)def.bits << " ... " << std::flush; #endif outRgn->AddDimension(&def); #if DEBUG_COMBINE_INSTRUMENTS std::cout << "OK" << std::endl << std::flush; #endif } else { dims.erase(mainDimension); } // for the next task we need to have the current RegionGroup to be // sorted by instrument in the same sequence as the 'instruments' vector // argument passed to this function (because the std::map behind the // 'RegionGroup' type sorts by memory address instead, and that would // sometimes lead to the source instruments' region to be sorted into // the wrong target layer) OrderedRegionGroup currentGroup = sortRegionGroup(itGroup->second, instruments); // schedule copying the source dimension regions to the target dimension // regions CopyAssignSchedule schedule; int iDstMainBit = 0; for (OrderedRegionGroup::iterator itRgn = currentGroup.begin(); itRgn != currentGroup.end(); ++itRgn) // iterate over 'vertical' / source regions ... { gig::Region* inRgn = itRgn->second; #if DEBUG_COMBINE_INSTRUMENTS printf("[source region of '%s']\n", inRgn->GetParent()->pInfo->Name.c_str()); #endif // determine how many main dimension zones this input region requires gig::dimension_def_t* def = inRgn->GetDimensionDefinition(mainDimension); const int inRgnMainZones = (def) ? def->zones : 1; for (uint iSrcMainBit = 0; iSrcMainBit < inRgnMainZones; ++iSrcMainBit, ++iDstMainBit) { scheduleCopyDimensionRegions( outRgn, inRgn, dims, mainDimension, iDstMainBit, iSrcMainBit, &schedule ); } } // finally copy the scheduled source -> target dimension regions for (uint i = 0; i < schedule.size(); ++i) { CopyAssignSchedEntry& e = schedule[i]; // backup the target DimensionRegion's current dimension zones upper // limits (because the target DimensionRegion's upper limits are // already defined correctly since calling AddDimension(), and the // CopyAssign() call next, will overwrite those upper limits // unfortunately DimensionRegionUpperLimits dstUpperLimits = getDimensionRegionUpperLimits(e.dst); DimensionRegionUpperLimits srcUpperLimits = getDimensionRegionUpperLimits(e.src); // now actually copy over the current DimensionRegion const gig::Region* const origRgn = e.dst->GetParent(); // just for sanity check below e.dst->CopyAssign(e.src); assert(origRgn == e.dst->GetParent()); // if gigedit is crashing here, then you must update libgig (to at least SVN r2547, v3.3.0.svn10) // restore all original dimension zone upper limits except of the // velocity dimension, because the velocity dimension zone sizes are // allowed to differ for individual DimensionRegions in gig v3 // format // // if the main dinension is the 'velocity' dimension, then skip // restoring the source's original velocity zone limits, because // dealing with merging that is not implemented yet // TODO: merge custom velocity splits if main dimension is the velocity dimension (for now equal sized velocity zones are used if mainDim is 'velocity') if (srcUpperLimits.count(gig::dimension_velocity) && mainDimension != gig::dimension_velocity) { if (!dstUpperLimits.count(gig::dimension_velocity)) { addWarning("Source instrument seems to have a velocity dimension whereas new target instrument doesn't!"); } else { dstUpperLimits[gig::dimension_velocity] = (e.velocityZone >= e.totalSrcVelocityZones) ? 127 : srcUpperLimits[gig::dimension_velocity]; } } restoreDimensionRegionUpperLimits(e.dst, dstUpperLimits); } } // success output = outInstr; }
/** @brief Schedule copying DimensionRegions from source Region to target Region. * * Schedules copying the entire articulation informations (including sample * reference) from all individual DimensionRegions of source Region @a inRgn to * target Region @a outRgn. It is expected that the required dimensions (thus * the required dimension regions) were already created before calling this * function. * * To be precise, it does the task above only for the dimension zones defined by * the three arguments @a mainDim, @a iSrcMainBit, @a iDstMainBit, which reflect * a selection which dimension zones shall be copied. All other dimension zones * will not be scheduled to be copied by a single call of this function. So this * function needs to be called several time in case all dimension regions shall * be copied of the entire region (@a inRgn, @a outRgn). * * @param outRgn - where the dimension regions shall be copied to * @param inRgn - all dimension regions that shall be copied from * @param dims - precise dimension definitions of target region * @param mainDim - this dimension type, in combination with @a iSrcMainBit and * @a iDstMainBit defines a selection which dimension region * zones shall be copied by this call of this function * @param iDstMainBit - destination bit of @a mainDim * @param iSrcMainBit - source bit of @a mainDim * @param schedule - list of all DimensionRegion copy operations which is filled * during the nested loops / recursions of this function call * @param dimCase - just for internal purpose (function recursion), don't pass * anything here, this function will call itself recursively * will fill this container with concrete dimension values for * selecting the precise dimension regions during its task */ static void scheduleCopyDimensionRegions(gig::Region* outRgn, gig::Region* inRgn, Dimensions dims, gig::dimension_t mainDim, int iDstMainBit, int iSrcMainBit, CopyAssignSchedule* schedule, DimensionCase dimCase = DimensionCase()) { if (dims.empty()) { // reached deepest level of function recursion ... CopyAssignSchedEntry e; // resolve the respective source & destination DimensionRegion ... uint srcDimValues[8] = {}; uint dstDimValues[8] = {}; DimensionCase srcDimCase = dimCase; DimensionCase dstDimCase = dimCase; srcDimCase[mainDim] = iSrcMainBit; dstDimCase[mainDim] = iDstMainBit; #if DEBUG_COMBINE_INSTRUMENTS printf("-------------------------------\n"); printf("iDstMainBit=%d iSrcMainBit=%d\n", iDstMainBit, iSrcMainBit); #endif // first select source & target dimension region with an arbitrary // velocity split zone, to get access to the precise individual velocity // split zone sizes (if there is actually a velocity dimension at all, // otherwise we already select the desired source & target dimension // region here) #if DEBUG_COMBINE_INSTRUMENTS printf("src "); fflush(stdout); #endif fillDimValues(srcDimValues, srcDimCase, inRgn, false); #if DEBUG_COMBINE_INSTRUMENTS printf("dst "); fflush(stdout); #endif fillDimValues(dstDimValues, dstDimCase, outRgn, false); gig::DimensionRegion* srcDimRgn = inRgn->GetDimensionRegionByValue(srcDimValues); gig::DimensionRegion* dstDimRgn = outRgn->GetDimensionRegionByValue(dstDimValues); #if DEBUG_COMBINE_INSTRUMENTS printf("iDstMainBit=%d iSrcMainBit=%d\n", iDstMainBit, iSrcMainBit); printf("srcDimRgn=%lx dstDimRgn=%lx\n", (uint64_t)srcDimRgn, (uint64_t)dstDimRgn); printf("srcSample='%s' dstSample='%s'\n", (!srcDimRgn->pSample ? "NULL" : srcDimRgn->pSample->pInfo->Name.c_str()), (!dstDimRgn->pSample ? "NULL" : dstDimRgn->pSample->pInfo->Name.c_str()) ); #endif assert(srcDimRgn->GetParent() == inRgn); assert(dstDimRgn->GetParent() == outRgn); // now that we have access to the precise velocity split zone upper // limits, we can select the actual source & destination dimension // regions we need to copy (assuming that source or target region has // a velocity dimension) if (outRgn->GetDimensionDefinition(gig::dimension_velocity)) { // re-select target dimension region (with correct velocity zone) DimensionZones dstZones = preciseDimensionZonesFor(gig::dimension_velocity, dstDimRgn); assert(dstZones.size() > 1); const int iDstZoneIndex = (mainDim == gig::dimension_velocity) ? iDstMainBit : dstDimCase[gig::dimension_velocity]; // (mainDim == gig::dimension_velocity) exception case probably unnecessary here e.velocityZone = iDstZoneIndex; #if DEBUG_COMBINE_INSTRUMENTS printf("dst velocity zone: %d/%d\n", iDstZoneIndex, (int)dstZones.size()); #endif assert(uint(iDstZoneIndex) < dstZones.size()); dstDimCase[gig::dimension_velocity] = dstZones[iDstZoneIndex].low; // arbitrary value between low and high #if DEBUG_COMBINE_INSTRUMENTS printf("dst velocity value = %d\n", dstDimCase[gig::dimension_velocity]); printf("dst refilled "); fflush(stdout); #endif fillDimValues(dstDimValues, dstDimCase, outRgn, false); dstDimRgn = outRgn->GetDimensionRegionByValue(dstDimValues); #if DEBUG_COMBINE_INSTRUMENTS printf("reselected dstDimRgn=%lx\n", (uint64_t)dstDimRgn); printf("dstSample='%s'%s\n", (!dstDimRgn->pSample ? "NULL" : dstDimRgn->pSample->pInfo->Name.c_str()), (dstDimRgn->pSample ? " <--- ERROR ERROR ERROR !!!!!!!!! " : "") ); #endif // re-select source dimension region with correct velocity zone // (if it has a velocity dimension that is) if (inRgn->GetDimensionDefinition(gig::dimension_velocity)) { DimensionZones srcZones = preciseDimensionZonesFor(gig::dimension_velocity, srcDimRgn); e.totalSrcVelocityZones = srcZones.size(); assert(srcZones.size() > 0); if (srcZones.size() <= 1) { addWarning("Input region has a velocity dimension with only ONE zone!"); } int iSrcZoneIndex = (mainDim == gig::dimension_velocity) ? iSrcMainBit : iDstZoneIndex; if (uint(iSrcZoneIndex) >= srcZones.size()) iSrcZoneIndex = srcZones.size() - 1; srcDimCase[gig::dimension_velocity] = srcZones[iSrcZoneIndex].low; // same zone as used above for target dimension region (no matter what the precise zone ranges are) #if DEBUG_COMBINE_INSTRUMENTS printf("src refilled "); fflush(stdout); #endif fillDimValues(srcDimValues, srcDimCase, inRgn, false); srcDimRgn = inRgn->GetDimensionRegionByValue(srcDimValues); #if DEBUG_COMBINE_INSTRUMENTS printf("reselected srcDimRgn=%lx\n", (uint64_t)srcDimRgn); printf("srcSample='%s'\n", (!srcDimRgn->pSample ? "NULL" : srcDimRgn->pSample->pInfo->Name.c_str()) ); #endif } } // Schedule copy operation of source -> target DimensionRegion for the // time after all nested loops have been traversed. We have to postpone // the actual copy operations this way, because otherwise it would // overwrite informations inside the destination DimensionRegion object // that we need to read in the code block above. e.src = srcDimRgn; e.dst = dstDimRgn; schedule->push_back(e); return; // returning from deepest level of function recursion } // Copying n dimensions requires n nested loops. That's why this function // is calling itself recursively to provide the required amount of nested // loops. With each call it pops from argument 'dims' and pushes to // argument 'dimCase'. Dimensions::iterator itDimension = dims.begin(); gig::dimension_t type = itDimension->first; DimensionZones zones = itDimension->second; dims.erase(itDimension); int iZone = 0; for (DimensionZones::iterator itZone = zones.begin(); itZone != zones.end(); ++itZone, ++iZone) { DLS::range_t zoneRange = *itZone; gig::dimension_def_t* def = outRgn->GetDimensionDefinition(type); dimCase[type] = (def->split_type == gig::split_type_bit) ? iZone : zoneRange.low; // recurse until 'dims' is exhausted (and dimCase filled up with concrete value) scheduleCopyDimensionRegions(outRgn, inRgn, dims, mainDim, iDstMainBit, iSrcMainBit, schedule, dimCase); } }