/[svn]/gigedit/trunk/src/gigedit/CombineInstrumentsDialog.cpp
ViewVC logotype

Contents of /gigedit/trunk/src/gigedit/CombineInstrumentsDialog.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2558 - (show annotations) (download)
Sat May 17 23:55:30 2014 UTC (9 years, 10 months ago) by schoenebeck
File size: 43583 byte(s)
* Combine instruments: generalized this tool to allow combining instruments
  based on any dimension (selectable by combo box), not only the 'layer'
  dimension. Also fixed some bugs that could lead to undesired results
  when combining instruments, i.e. source instruments being copied into
  wrong target zones or in wrong order under certain conditions.
* 'Dimension Manager' dialog: show dialog window with at least 460x300px on
  initial usage (was far too small by default).

1 /*
2 Copyright (c) 2014 Christian Schoenebeck
3
4 This file is part of "gigedit" and released under the terms of the
5 GNU General Public License version 2.
6 */
7
8 #include "CombineInstrumentsDialog.h"
9
10 // enable this for debug messages being printed while combining the instruments
11 #define DEBUG_COMBINE_INSTRUMENTS 0
12
13 #include "global.h"
14 #include "compat.h"
15
16 #include <set>
17 #include <iostream>
18 #include <assert.h>
19 #include <stdarg.h>
20 #include <string.h>
21
22 #include <glibmm/ustring.h>
23 #include <gtkmm/stock.h>
24 #include <gtkmm/messagedialog.h>
25 #include <gtkmm/label.h>
26
27 Glib::ustring gig_to_utf8(const gig::String& gig_string);
28 Glib::ustring dimTypeAsString(gig::dimension_t d);
29
30 typedef std::vector< std::pair<gig::Instrument*, gig::Region*> > OrderedRegionGroup;
31 typedef std::map<gig::Instrument*, gig::Region*> RegionGroup;
32 typedef std::map<DLS::range_t,RegionGroup> RegionGroups;
33
34 typedef std::vector<DLS::range_t> DimensionZones;
35 typedef std::map<gig::dimension_t,DimensionZones> Dimensions;
36
37 typedef std::map<gig::dimension_t,int> DimensionCase;
38
39 typedef std::map<gig::dimension_t, int> DimensionRegionUpperLimits;
40
41 typedef std::set<Glib::ustring> Warnings;
42
43 ///////////////////////////////////////////////////////////////////////////
44 // private static data
45
46 static Warnings g_warnings;
47
48 ///////////////////////////////////////////////////////////////////////////
49 // private functions
50
51 #if DEBUG_COMBINE_INSTRUMENTS
52 static void printRanges(const RegionGroups& regions) {
53 std::cout << "{ ";
54 for (RegionGroups::const_iterator it = regions.begin(); it != regions.end(); ++it) {
55 if (it != regions.begin()) std::cout << ", ";
56 std::cout << (int)it->first.low << ".." << (int)it->first.high;
57 }
58 std::cout << " }" << std::flush;
59 }
60 #endif
61
62 /**
63 * Store a warning message that shall be stored and displayed to the user as a
64 * list of warnings after the overall operation has finished. Duplicate warning
65 * messages are automatically eliminated.
66 */
67 inline void addWarning(const char* fmt, ...) {
68 va_list arg;
69 va_start(arg, fmt);
70 const int SZ = 255 + strlen(fmt);
71 char* buf = new char[SZ];
72 vsnprintf(buf, SZ, fmt, arg);
73 Glib::ustring s = buf;
74 delete [] buf;
75 va_end(arg);
76 std::cerr << _("WARNING:") << " " << s << std::endl << std::flush;
77 g_warnings.insert(s);
78 }
79
80 /**
81 * If the two ranges overlap, then this function returns the smallest point
82 * within that overlapping zone. If the two ranges do not overlap, then this
83 * function will return -1 instead.
84 */
85 inline int smallestOverlapPoint(const DLS::range_t& r1, const DLS::range_t& r2) {
86 if (r1.overlaps(r2.low)) return r2.low;
87 if (r2.overlaps(r1.low)) return r1.low;
88 return -1;
89 }
90
91 /**
92 * Get the most smallest region point (not necessarily its region start point)
93 * of all regions of the given instruments, start searching at keyboard
94 * position @a iStart.
95 *
96 * @returns very first region point >= iStart, or -1 if no region could be
97 * found with a range member point >= iStart
98 */
99 static int findLowestRegionPoint(std::vector<gig::Instrument*>& instruments, int iStart) {
100 DLS::range_t searchRange = { iStart, 127 };
101 int result = -1;
102 for (uint i = 0; i < instruments.size(); ++i) {
103 gig::Instrument* instr = instruments[i];
104 for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
105 if (rgn->KeyRange.overlaps(searchRange)) {
106 int lowest = smallestOverlapPoint(rgn->KeyRange, searchRange);
107 if (result == -1 || lowest < result) result = lowest;
108 }
109 }
110 }
111 return result;
112 }
113
114 /**
115 * Get the most smallest region end of all regions of the given instruments,
116 * start searching at keyboard position @a iStart.
117 *
118 * @returns very first region end >= iStart, or -1 if no region could be found
119 * with a range end >= iStart
120 */
121 static int findFirstRegionEnd(std::vector<gig::Instrument*>& instruments, int iStart) {
122 DLS::range_t searchRange = { iStart, 127 };
123 int result = -1;
124 for (uint i = 0; i < instruments.size(); ++i) {
125 gig::Instrument* instr = instruments[i];
126 for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
127 if (rgn->KeyRange.overlaps(searchRange)) {
128 if (result == -1 || rgn->KeyRange.high < result)
129 result = rgn->KeyRange.high;
130 }
131 }
132 }
133 return result;
134 }
135
136 /**
137 * Returns a list of all regions of the given @a instrument where the respective
138 * region's key range overlaps the given @a range.
139 */
140 static std::vector<gig::Region*> getAllRegionsWhichOverlapRange(gig::Instrument* instrument, DLS::range_t range) {
141 //std::cout << "All regions which overlap { " << (int)range.low << ".." << (int)range.high << " } : " << std::flush;
142 std::vector<gig::Region*> v;
143 for (gig::Region* rgn = instrument->GetFirstRegion(); rgn; rgn = instrument->GetNextRegion()) {
144 if (rgn->KeyRange.overlaps(range)) {
145 v.push_back(rgn);
146 //std::cout << (int)rgn->KeyRange.low << ".." << (int)rgn->KeyRange.high << ", " << std::flush;
147 }
148 }
149 //std::cout << " END." << std::endl;
150 return v;
151 }
152
153 /**
154 * Returns all regions of the given @a instruments where the respective region's
155 * key range overlaps the given @a range. The regions returned are ordered (in a
156 * map) by their instrument pointer.
157 */
158 static RegionGroup getAllRegionsWhichOverlapRange(std::vector<gig::Instrument*>& instruments, DLS::range_t range) {
159 RegionGroup group;
160 for (uint i = 0; i < instruments.size(); ++i) {
161 gig::Instrument* instr = instruments[i];
162 std::vector<gig::Region*> v = getAllRegionsWhichOverlapRange(instr, range);
163 if (v.empty()) continue;
164 if (v.size() > 1) {
165 addWarning("More than one region found!");
166 }
167 group[instr] = v[0];
168 }
169 return group;
170 }
171
172 /** @brief Identify required regions.
173 *
174 * Takes a list of @a instruments as argument (which are planned to be combined
175 * as separate dimension zones of a certain dimension into one single new
176 * instrument) and fulfills the following tasks:
177 *
178 * - 1. Identification of total amount of regions required to create a new
179 * instrument to become a combined version of the given instruments.
180 * - 2. Precise key range of each of those identified required regions to be
181 * created in that new instrument.
182 * - 3. Grouping the original source regions of the given original instruments
183 * to the respective target key range (new region) of the instrument to be
184 * created.
185 *
186 * @param instruments - list of instruments that are planned to be combined
187 * @returns structured result of the tasks described above
188 */
189 static RegionGroups groupByRegionIntersections(std::vector<gig::Instrument*>& instruments) {
190 RegionGroups groups;
191
192 // find all region intersections of all instruments
193 std::vector<DLS::range_t> intersections;
194 for (int iStart = 0; iStart <= 127; ) {
195 iStart = findLowestRegionPoint(instruments, iStart);
196 if (iStart < 0) break;
197 const int iEnd = findFirstRegionEnd(instruments, iStart);
198 DLS::range_t range = { iStart, iEnd };
199 intersections.push_back(range);
200 iStart = iEnd + 1;
201 }
202
203 // now sort all regions to those found intersections
204 for (uint i = 0; i < intersections.size(); ++i) {
205 const DLS::range_t& range = intersections[i];
206 RegionGroup group = getAllRegionsWhichOverlapRange(instruments, range);
207 if (!group.empty())
208 groups[range] = group;
209 else
210 addWarning("Empty region group!");
211 }
212
213 return groups;
214 }
215
216 /** @brief Identify required dimensions.
217 *
218 * Takes a planned new region (@a regionGroup) as argument and identifies which
219 * precise dimensions would have to be created for that new region, along with
220 * the amount of dimension zones and their precise individual zone sizes.
221 *
222 * @param regionGroup - planned new region for a new instrument
223 * @returns set of dimensions that shall be created for the given planned region
224 */
225 static Dimensions getDimensionsForRegionGroup(RegionGroup& regionGroup) {
226 std::map<gig::dimension_t, std::set<int> > dimUpperLimits;
227
228 // collect all dimension region zones' upper limits
229 for (RegionGroup::iterator it = regionGroup.begin();
230 it != regionGroup.end(); ++it)
231 {
232 gig::Region* rgn = it->second;
233 int previousBits = 0;
234 for (uint d = 0; d < rgn->Dimensions; ++d) {
235 const gig::dimension_def_t& def = rgn->pDimensionDefinitions[d];
236 for (uint z = 0; z < def.zones; ++z) {
237 int dr = z << previousBits;
238 gig::DimensionRegion* dimRgn = rgn->pDimensionRegions[dr];
239 // Store the individual dimension zone sizes (or actually their
240 // upper limits here) for each dimension.
241 // HACK: Note that the velocity dimension is specially handled
242 // here. Instead of taking over custom velocity split sizes
243 // here, only a bogus number (zone index number) is stored for
244 // each velocity zone, that way only the maxiumum amount of
245 // velocity splits of all regions is stored here, and when their
246 // individual DimensionRegions are finally copied (later), the
247 // individual velocity split size are copied by that.
248 dimUpperLimits[def.dimension].insert(
249 (def.dimension == gig::dimension_velocity) ?
250 z : (def.split_type == gig::split_type_bit) ?
251 ((z+1) * 128/def.zones - 1) : dimRgn->DimensionUpperLimits[dr]
252 );
253 }
254 previousBits += def.bits;
255 }
256 }
257
258 // convert upper limit set to range vector
259 Dimensions dims;
260 for (std::map<gig::dimension_t, std::set<int> >::const_iterator it = dimUpperLimits.begin();
261 it != dimUpperLimits.end(); ++it)
262 {
263 gig::dimension_t type = it->first;
264 int iLow = 0;
265 for (std::set<int>::const_iterator itNums = it->second.begin();
266 itNums != it->second.end(); ++itNums)
267 {
268 const int iUpperLimit = *itNums;
269 DLS::range_t range = { iLow, iUpperLimit };
270 dims[type].push_back(range);
271 iLow = iUpperLimit + 1;
272 }
273 }
274
275 return dims;
276 }
277
278 inline int getDimensionIndex(gig::dimension_t type, gig::Region* rgn) {
279 for (uint i = 0; i < rgn->Dimensions; ++i)
280 if (rgn->pDimensionDefinitions[i].dimension == type)
281 return i;
282 return -1;
283 }
284
285 static void fillDimValues(uint* values/*[8]*/, DimensionCase dimCase, gig::Region* rgn, bool bShouldHaveAllDimensionsPassed) {
286 #if DEBUG_COMBINE_INSTRUMENTS
287 printf("dimvalues = { ");
288 fflush(stdout);
289 #endif
290 for (DimensionCase::iterator it = dimCase.begin(); it != dimCase.end(); ++it) {
291 gig::dimension_t type = it->first;
292 int iDimIndex = getDimensionIndex(type, rgn);
293 if (bShouldHaveAllDimensionsPassed) assert(iDimIndex >= 0);
294 else if (iDimIndex < 0) continue;
295 values[iDimIndex] = it->second;
296 #if DEBUG_COMBINE_INSTRUMENTS
297 printf("%x=%d, ", type, it->second);
298 #endif
299 }
300 #if DEBUG_COMBINE_INSTRUMENTS
301 printf("}\n");
302 #endif
303 }
304
305 static DimensionRegionUpperLimits getDimensionRegionUpperLimits(gig::DimensionRegion* dimRgn) {
306 DimensionRegionUpperLimits limits;
307 gig::Region* rgn = dimRgn->GetParent();
308 for (uint d = 0; d < rgn->Dimensions; ++d) {
309 const gig::dimension_def_t& def = rgn->pDimensionDefinitions[d];
310 limits[def.dimension] = dimRgn->DimensionUpperLimits[d];
311 }
312 return limits;
313 }
314
315 static void restoreDimensionRegionUpperLimits(gig::DimensionRegion* dimRgn, const DimensionRegionUpperLimits& limits) {
316 gig::Region* rgn = dimRgn->GetParent();
317 for (DimensionRegionUpperLimits::const_iterator it = limits.begin();
318 it != limits.end(); ++it)
319 {
320 int index = getDimensionIndex(it->first, rgn);
321 assert(index >= 0);
322 dimRgn->DimensionUpperLimits[index] = it->second;
323 }
324 }
325
326 /**
327 * Returns the sum of all bits of all dimensions defined before the given
328 * dimensions (@a type). This allows to access cases of that particular
329 * dimension directly.
330 *
331 * @param type - dimension that shall be used
332 * @param rgn - parent region of that dimension
333 */
334 inline int baseBits(gig::dimension_t type, gig::Region* rgn) {
335 int previousBits = 0;
336 for (uint i = 0; i < rgn->Dimensions; ++i) {
337 if (rgn->pDimensionDefinitions[i].dimension == type) break;
338 previousBits += rgn->pDimensionDefinitions[i].bits;
339 }
340 return previousBits;
341 }
342
343 inline int dimensionRegionIndex(gig::DimensionRegion* dimRgn) {
344 gig::Region* rgn = dimRgn->GetParent();
345 int sz = sizeof(rgn->pDimensionRegions) / sizeof(gig::DimensionRegion*);
346 for (int i = 0; i < sz; ++i)
347 if (rgn->pDimensionRegions[i] == dimRgn)
348 return i;
349 return -1;
350 }
351
352 /** @brief Get exact zone ranges of given dimension.
353 *
354 * This function is useful for the velocity type dimension. In contrast to other
355 * dimension types, this dimension can have different zone ranges (that is
356 * different individual start and end points of its dimension zones) depending
357 * on which zones of other dimensions (on that gig::Region) are currently
358 * selected.
359 *
360 * @param type - dimension where the zone ranges should be retrieved for
361 * (usually the velocity dimension in this context)
362 * @param dimRgn - reflects the exact cases (zone selections) of all other
363 * dimensions than the given one in question
364 * @returns individual ranges for each zone of the questioned dimension type,
365 * it returns an empty result on errors instead
366 */
367 static DimensionZones preciseDimensionZonesFor(gig::dimension_t type, gig::DimensionRegion* dimRgn) {
368 DimensionZones zones;
369 gig::Region* rgn = dimRgn->GetParent();
370 int iDimension = getDimensionIndex(type, rgn);
371 if (iDimension < 0) return zones;
372 const gig::dimension_def_t& def = rgn->pDimensionDefinitions[iDimension];
373 int iDimRgn = dimensionRegionIndex(dimRgn);
374 int iBaseBits = baseBits(type, rgn);
375 int mask = ~(((1 << def.bits) - 1) << iBaseBits);
376
377 #if DEBUG_COMBINE_INSTRUMENTS
378 printf("velo zones { ");
379 fflush(stdout);
380 #endif
381 int iLow = 0;
382 for (int z = 0; z < def.zones; ++z) {
383 gig::DimensionRegion* dimRgn2 =
384 rgn->pDimensionRegions[ (iDimRgn & mask) | ( z << iBaseBits) ];
385 int iHigh = dimRgn2->DimensionUpperLimits[iDimension];
386 DLS::range_t range = { iLow, iHigh};
387 #if DEBUG_COMBINE_INSTRUMENTS
388 printf("%d..%d, ", iLow, iHigh);
389 fflush(stdout);
390 #endif
391 zones.push_back(range);
392 iLow = iHigh + 1;
393 }
394 #if DEBUG_COMBINE_INSTRUMENTS
395 printf("}\n");
396 #endif
397 return zones;
398 }
399
400 struct CopyAssignSchedEntry {
401 gig::DimensionRegion* src;
402 gig::DimensionRegion* dst;
403 int velocityZone;
404 int totalSrcVelocityZones;
405 };
406 typedef std::vector<CopyAssignSchedEntry> CopyAssignSchedule;
407
408 /** @brief Schedule copying DimensionRegions from source Region to target Region.
409 *
410 * Schedules copying the entire articulation informations (including sample
411 * reference) from all individual DimensionRegions of source Region @a inRgn to
412 * target Region @a outRgn. It is expected that the required dimensions (thus
413 * the required dimension regions) were already created before calling this
414 * function.
415 *
416 * To be precise, it does the task above only for the dimension zones defined by
417 * the three arguments @a mainDim, @a iSrcMainBit, @a iDstMainBit, which reflect
418 * a selection which dimension zones shall be copied. All other dimension zones
419 * will not be scheduled to be copied by a single call of this function. So this
420 * function needs to be called several time in case all dimension regions shall
421 * be copied of the entire region (@a inRgn, @a outRgn).
422 *
423 * @param outRgn - where the dimension regions shall be copied to
424 * @param inRgn - all dimension regions that shall be copied from
425 * @param dims - precise dimension definitions of target region
426 * @param mainDim - this dimension type, in combination with @a iSrcMainBit and
427 * @a iDstMainBit defines a selection which dimension region
428 * zones shall be copied by this call of this function
429 * @param iDstMainBit - destination bit of @a mainDim
430 * @param iSrcMainBit - source bit of @a mainDim
431 * @param schedule - list of all DimensionRegion copy operations which is filled
432 * during the nested loops / recursions of this function call
433 * @param dimCase - just for internal purpose (function recursion), don't pass
434 * anything here, this function will call itself recursively
435 * will fill this container with concrete dimension values for
436 * selecting the precise dimension regions during its task
437 */
438 static void scheduleCopyDimensionRegions(gig::Region* outRgn, gig::Region* inRgn,
439 Dimensions dims, gig::dimension_t mainDim,
440 int iDstMainBit, int iSrcMainBit,
441 CopyAssignSchedule* schedule,
442 DimensionCase dimCase = DimensionCase())
443 {
444 if (dims.empty()) { // reached deepest level of function recursion ...
445 CopyAssignSchedEntry e;
446
447 // resolve the respective source & destination DimensionRegion ...
448 uint srcDimValues[8] = {};
449 uint dstDimValues[8] = {};
450 DimensionCase srcDimCase = dimCase;
451 DimensionCase dstDimCase = dimCase;
452 srcDimCase[mainDim] = iSrcMainBit;
453 dstDimCase[mainDim] = iDstMainBit;
454
455 #if DEBUG_COMBINE_INSTRUMENTS
456 printf("-------------------------------\n");
457 printf("iDstMainBit=%d iSrcMainBit=%d\n", iDstMainBit, iSrcMainBit);
458 #endif
459
460 // first select source & target dimension region with an arbitrary
461 // velocity split zone, to get access to the precise individual velocity
462 // split zone sizes (if there is actually a velocity dimension at all,
463 // otherwise we already select the desired source & target dimension
464 // region here)
465 #if DEBUG_COMBINE_INSTRUMENTS
466 printf("src "); fflush(stdout);
467 #endif
468 fillDimValues(srcDimValues, srcDimCase, inRgn, false);
469 #if DEBUG_COMBINE_INSTRUMENTS
470 printf("dst "); fflush(stdout);
471 #endif
472 fillDimValues(dstDimValues, dstDimCase, outRgn, true);
473 gig::DimensionRegion* srcDimRgn = inRgn->GetDimensionRegionByValue(srcDimValues);
474 gig::DimensionRegion* dstDimRgn = outRgn->GetDimensionRegionByValue(dstDimValues);
475 #if DEBUG_COMBINE_INSTRUMENTS
476 printf("iDstMainBit=%d iSrcMainBit=%d\n", iDstMainBit, iSrcMainBit);
477 printf("srcDimRgn=%lx dstDimRgn=%lx\n", (uint64_t)srcDimRgn, (uint64_t)dstDimRgn);
478 printf("srcSample='%s' dstSample='%s'\n",
479 (!srcDimRgn->pSample ? "NULL" : srcDimRgn->pSample->pInfo->Name.c_str()),
480 (!dstDimRgn->pSample ? "NULL" : dstDimRgn->pSample->pInfo->Name.c_str())
481 );
482 #endif
483
484 assert(srcDimRgn->GetParent() == inRgn);
485 assert(dstDimRgn->GetParent() == outRgn);
486
487 // now that we have access to the precise velocity split zone upper
488 // limits, we can select the actual source & destination dimension
489 // regions we need to copy (assuming that source or target region has
490 // a velocity dimension)
491 if (outRgn->GetDimensionDefinition(gig::dimension_velocity)) {
492 // re-select target dimension region (with correct velocity zone)
493 DimensionZones dstZones = preciseDimensionZonesFor(gig::dimension_velocity, dstDimRgn);
494 assert(dstZones.size() > 1);
495 const int iDstZoneIndex =
496 (mainDim == gig::dimension_velocity)
497 ? iDstMainBit : dstDimCase[gig::dimension_velocity]; // (mainDim == gig::dimension_velocity) exception case probably unnecessary here
498 e.velocityZone = iDstZoneIndex;
499 #if DEBUG_COMBINE_INSTRUMENTS
500 printf("dst velocity zone: %d/%d\n", iDstZoneIndex, (int)dstZones.size());
501 #endif
502 assert(uint(iDstZoneIndex) < dstZones.size());
503 dstDimCase[gig::dimension_velocity] = dstZones[iDstZoneIndex].low; // arbitrary value between low and high
504 #if DEBUG_COMBINE_INSTRUMENTS
505 printf("dst velocity value = %d\n", dstDimCase[gig::dimension_velocity]);
506 printf("dst refilled "); fflush(stdout);
507 #endif
508 fillDimValues(dstDimValues, dstDimCase, outRgn, true);
509 dstDimRgn = outRgn->GetDimensionRegionByValue(dstDimValues);
510 #if DEBUG_COMBINE_INSTRUMENTS
511 printf("reselected dstDimRgn=%lx\n", (uint64_t)dstDimRgn);
512 printf("dstSample='%s'%s\n",
513 (!dstDimRgn->pSample ? "NULL" : dstDimRgn->pSample->pInfo->Name.c_str()),
514 (dstDimRgn->pSample ? " <--- ERROR ERROR ERROR !!!!!!!!! " : "")
515 );
516 #endif
517
518 // re-select source dimension region with correct velocity zone
519 // (if it has a velocity dimension that is)
520 if (inRgn->GetDimensionDefinition(gig::dimension_velocity)) {
521 DimensionZones srcZones = preciseDimensionZonesFor(gig::dimension_velocity, srcDimRgn);
522 e.totalSrcVelocityZones = srcZones.size();
523 assert(srcZones.size() > 0);
524 if (srcZones.size() <= 1) {
525 addWarning("Input region has a velocity dimension with only ONE zone!");
526 }
527 int iSrcZoneIndex =
528 (mainDim == gig::dimension_velocity)
529 ? iSrcMainBit : iDstZoneIndex;
530 if (uint(iSrcZoneIndex) >= srcZones.size())
531 iSrcZoneIndex = srcZones.size() - 1;
532 srcDimCase[gig::dimension_velocity] = srcZones[iSrcZoneIndex].low; // same zone as used above for target dimension region (no matter what the precise zone ranges are)
533 #if DEBUG_COMBINE_INSTRUMENTS
534 printf("src refilled "); fflush(stdout);
535 #endif
536 fillDimValues(srcDimValues, srcDimCase, inRgn, false);
537 srcDimRgn = inRgn->GetDimensionRegionByValue(srcDimValues);
538 #if DEBUG_COMBINE_INSTRUMENTS
539 printf("reselected srcDimRgn=%lx\n", (uint64_t)srcDimRgn);
540 printf("srcSample='%s'\n",
541 (!srcDimRgn->pSample ? "NULL" : srcDimRgn->pSample->pInfo->Name.c_str())
542 );
543 #endif
544 }
545 }
546
547 // Schedule copy operation of source -> target DimensionRegion for the
548 // time after all nested loops have been traversed. We have to postpone
549 // the actual copy operations this way, because otherwise it would
550 // overwrite informations inside the destination DimensionRegion object
551 // that we need to read in the code block above.
552 e.src = srcDimRgn;
553 e.dst = dstDimRgn;
554 schedule->push_back(e);
555
556 return; // returning from deepest level of function recursion
557 }
558
559 // Copying n dimensions requires n nested loops. That's why this function
560 // is calling itself recursively to provide the required amount of nested
561 // loops. With each call it pops from argument 'dims' and pushes to
562 // argument 'dimCase'.
563
564 Dimensions::iterator itDimension = dims.begin();
565 gig::dimension_t type = itDimension->first;
566 DimensionZones zones = itDimension->second;
567 dims.erase(itDimension);
568
569 int iZone = 0;
570 for (DimensionZones::iterator itZone = zones.begin();
571 itZone != zones.end(); ++itZone, ++iZone)
572 {
573 DLS::range_t zoneRange = *itZone;
574 gig::dimension_def_t* def = outRgn->GetDimensionDefinition(type);
575 dimCase[type] = (def->split_type == gig::split_type_bit) ? iZone : zoneRange.low;
576
577 // recurse until 'dims' is exhausted (and dimCase filled up with concrete value)
578 scheduleCopyDimensionRegions(outRgn, inRgn, dims, mainDim, iDstMainBit, iSrcMainBit, schedule, dimCase);
579 }
580 }
581
582 static OrderedRegionGroup sortRegionGroup(const RegionGroup& group, const std::vector<gig::Instrument*>& instruments) {
583 OrderedRegionGroup result;
584 for (std::vector<gig::Instrument*>::const_iterator it = instruments.begin();
585 it != instruments.end(); ++it)
586 {
587 RegionGroup::const_iterator itRgn = group.find(*it);
588 if (itRgn == group.end()) continue;
589 result.push_back(
590 std::pair<gig::Instrument*, gig::Region*>(
591 itRgn->first, itRgn->second
592 )
593 );
594 }
595 return result;
596 }
597
598 /** @brief Combine given list of instruments to one instrument.
599 *
600 * Takes a list of @a instruments as argument and combines them to one single
601 * new @a output instrument. For this task, it will create a dimension of type
602 * given by @a mainDimension in the new instrument and copies the source
603 * instruments to those dimension zones.
604 *
605 * @param instruments - (input) list of instruments that shall be combined,
606 * they will only be read, so they will be left untouched
607 * @param gig - (input/output) .gig file where the new combined instrument shall
608 * be created
609 * @param output - (output) on success this pointer will be set to the new
610 * instrument being created
611 * @param mainDimension - the dimension that shall be used to combine the
612 * instruments
613 * @throw RIFF::Exception on any kinds of errors
614 */
615 static void combineInstruments(std::vector<gig::Instrument*>& instruments, gig::File* gig, gig::Instrument*& output, gig::dimension_t mainDimension) {
616 output = NULL;
617
618 // divide the individual regions to (probably even smaller) groups of
619 // regions, coping with the fact that the source regions of the instruments
620 // might have quite different range sizes and start and end points
621 RegionGroups groups = groupByRegionIntersections(instruments);
622 #if DEBUG_COMBINE_INSTRUMENTS
623 std::cout << std::endl << "New regions: " << std::flush;
624 printRanges(groups);
625 std::cout << std::endl;
626 #endif
627
628 if (groups.empty())
629 throw gig::Exception(_("No regions found to create a new instrument with."));
630
631 // create a new output instrument
632 gig::Instrument* outInstr = gig->AddInstrument();
633 outInstr->pInfo->Name = _("NEW COMBINATION");
634
635 // Distinguishing in the following code block between 'horizontal' and
636 // 'vertical' regions. The 'horizontal' ones are meant to be the key ranges
637 // in the output instrument, while the 'vertical' regions are meant to be
638 // the set of source regions that shall be layered to that 'horizontal'
639 // region / key range. It is important to know, that the key ranges defined
640 // in the 'horizontal' and 'vertical' regions might differ.
641
642 // merge the instruments to the new output instrument
643 for (RegionGroups::iterator itGroup = groups.begin();
644 itGroup != groups.end(); ++itGroup) // iterate over 'horizontal' / target regions ...
645 {
646 gig::Region* outRgn = outInstr->AddRegion();
647 outRgn->SetKeyRange(itGroup->first.low, itGroup->first.high);
648 #if DEBUG_COMBINE_INSTRUMENTS
649 printf("---> Start target region %d..%d\n", itGroup->first.low, itGroup->first.high);
650 #endif
651
652 // detect the total amount of zones required for the given main
653 // dimension to build up this combi for current key range
654 int iTotalZones = 0;
655 for (RegionGroup::iterator itRgn = itGroup->second.begin();
656 itRgn != itGroup->second.end(); ++itRgn)
657 {
658 gig::Region* inRgn = itRgn->second;
659 gig::dimension_def_t* def = inRgn->GetDimensionDefinition(mainDimension);
660 iTotalZones += (def) ? def->zones : 1;
661 }
662 #if DEBUG_COMBINE_INSTRUMENTS
663 printf("Required total zones: %d\n", iTotalZones);
664 #endif
665
666 // create all required dimensions for this output region
667 // (except the main dimension used for separating the individual
668 // instruments, we create that particular dimension as next step)
669 Dimensions dims = getDimensionsForRegionGroup(itGroup->second);
670 // the given main dimension which is used to combine the instruments is
671 // created separately after the next code block, and the main dimension
672 // should not be part of dims here, because it also used for iterating
673 // all dimensions zones, which would lead to this dimensions being
674 // iterated twice
675 dims.erase(mainDimension);
676 {
677 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
678
679 for (Dimensions::iterator itDim = dims.begin();
680 itDim != dims.end(); ++itDim)
681 {
682 gig::dimension_def_t def;
683 def.dimension = itDim->first; // dimension type
684 def.zones = itDim->second.size();
685 def.bits = zoneCountToBits(def.zones);
686 if (def.zones < 2) {
687 addWarning(
688 "Attempt to create dimension with type=0x%x with only "
689 "ONE zone (because at least one of the source "
690 "instruments seems to have such a velocity dimension "
691 "with only ONE zone, which is odd)! Skipping this "
692 "dimension for now.",
693 (int)itDim->first
694 );
695 skipTheseDimensions.push_back(itDim->first);
696 continue;
697 }
698 #if DEBUG_COMBINE_INSTRUMENTS
699 std::cout << "Adding new regular dimension type=" << std::hex << (int)def.dimension << std::dec << ", zones=" << (int)def.zones << ", bits=" << (int)def.bits << " ... " << std::flush;
700 #endif
701 outRgn->AddDimension(&def);
702 #if DEBUG_COMBINE_INSTRUMENTS
703 std::cout << "OK" << std::endl << std::flush;
704 #endif
705 }
706 // prevent the following dimensions to be processed further below
707 // (since the respective dimension was not created above)
708 for (int i = 0; i < skipTheseDimensions.size(); ++i)
709 dims.erase(skipTheseDimensions[i]);
710 }
711
712 // create the main dimension (if necessary for current key range)
713 if (iTotalZones > 1) {
714 gig::dimension_def_t def;
715 def.dimension = mainDimension; // dimension type
716 def.zones = iTotalZones;
717 def.bits = zoneCountToBits(def.zones);
718 #if DEBUG_COMBINE_INSTRUMENTS
719 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;
720 #endif
721 outRgn->AddDimension(&def);
722 #if DEBUG_COMBINE_INSTRUMENTS
723 std::cout << "OK" << std::endl << std::flush;
724 #endif
725 }
726
727 // for the next task we need to have the current RegionGroup to be
728 // sorted by instrument in the same sequence as the 'instruments' vector
729 // argument passed to this function (because the std::map behind the
730 // 'RegionGroup' type sorts by memory address instead, and that would
731 // sometimes lead to the source instruments' region to be sorted into
732 // the wrong target layer)
733 OrderedRegionGroup currentGroup = sortRegionGroup(itGroup->second, instruments);
734
735 // schedule copying the source dimension regions to the target dimension
736 // regions
737 CopyAssignSchedule schedule;
738 int iDstMainBit = 0;
739 for (OrderedRegionGroup::iterator itRgn = currentGroup.begin();
740 itRgn != currentGroup.end(); ++itRgn) // iterate over 'vertical' / source regions ...
741 {
742 gig::Region* inRgn = itRgn->second;
743 #if DEBUG_COMBINE_INSTRUMENTS
744 printf("[source region of '%s']\n", inRgn->GetParent()->pInfo->Name.c_str());
745 #endif
746
747 // determine how many main dimension zones this input region requires
748 gig::dimension_def_t* def = inRgn->GetDimensionDefinition(mainDimension);
749 const int inRgnMainZones = (def) ? def->zones : 1;
750
751 for (uint iSrcMainBit = 0; iSrcMainBit < inRgnMainZones; ++iSrcMainBit, ++iDstMainBit) {
752 scheduleCopyDimensionRegions(
753 outRgn, inRgn, dims, mainDimension,
754 iDstMainBit, iSrcMainBit, &schedule
755 );
756 }
757 }
758
759 // finally copy the scheduled source -> target dimension regions
760 for (uint i = 0; i < schedule.size(); ++i) {
761 CopyAssignSchedEntry& e = schedule[i];
762
763 // backup the target DimensionRegion's current dimension zones upper
764 // limits (because the target DimensionRegion's upper limits are
765 // already defined correctly since calling AddDimension(), and the
766 // CopyAssign() call next, will overwrite those upper limits
767 // unfortunately
768 DimensionRegionUpperLimits dstUpperLimits = getDimensionRegionUpperLimits(e.dst);
769 DimensionRegionUpperLimits srcUpperLimits = getDimensionRegionUpperLimits(e.src);
770
771 // now actually copy over the current DimensionRegion
772 const gig::Region* const origRgn = e.dst->GetParent(); // just for sanity check below
773 e.dst->CopyAssign(e.src);
774 assert(origRgn == e.dst->GetParent()); // if gigedit is crashing here, then you must update libgig (to at least SVN r2547, v3.3.0.svn10)
775
776 // restore all original dimension zone upper limits except of the
777 // velocity dimension, because the velocity dimension zone sizes are
778 // allowed to differ for individual DimensionRegions in gig v3
779 // format
780 //
781 // if the main dinension is the 'velocity' dimension, then skip
782 // restoring the source's original velocity zone limits, because
783 // dealing with merging that is not implemented yet
784 // TODO: merge custom velocity splits if main dimension is the velocity dimension (for now equal sized velocity zones are used if mainDim is 'velocity')
785 if (srcUpperLimits.count(gig::dimension_velocity) && mainDimension != gig::dimension_velocity) {
786 if (!dstUpperLimits.count(gig::dimension_velocity)) {
787 addWarning("Source instrument seems to have a velocity dimension whereas new target instrument doesn't!");
788 } else {
789 dstUpperLimits[gig::dimension_velocity] =
790 (e.velocityZone >= e.totalSrcVelocityZones)
791 ? 127 : srcUpperLimits[gig::dimension_velocity];
792 }
793 }
794 restoreDimensionRegionUpperLimits(e.dst, dstUpperLimits);
795 }
796 }
797
798 // success
799 output = outInstr;
800 }
801
802 ///////////////////////////////////////////////////////////////////////////
803 // class 'CombineInstrumentsDialog'
804
805 CombineInstrumentsDialog::CombineInstrumentsDialog(Gtk::Window& parent, gig::File* gig)
806 : Gtk::Dialog(_("Combine Instruments"), parent, true),
807 m_gig(gig), m_fileWasChanged(false), m_newCombinedInstrument(NULL),
808 m_cancelButton(Gtk::Stock::CANCEL), m_OKButton(Gtk::Stock::OK),
809 m_descriptionLabel(), m_tableDimCombo(2, 2), m_comboDimType(),
810 m_labelDimType(Glib::ustring(_("Combine by Dimension:")) + " ", Gtk::ALIGN_RIGHT)
811 {
812 get_vbox()->pack_start(m_descriptionLabel, Gtk::PACK_SHRINK);
813 get_vbox()->pack_start(m_tableDimCombo, Gtk::PACK_SHRINK);
814 get_vbox()->pack_start(m_treeView);
815 get_vbox()->pack_start(m_buttonBox, Gtk::PACK_SHRINK);
816
817 #if GTKMM_MAJOR_VERSION >= 3
818 description.set_line_wrap();
819 #endif
820 m_descriptionLabel.set_text(_(
821 "Select at least two instruments below that shall be combined (as "
822 "separate dimension zones of the selected dimension type) as a new "
823 "instrument. The original instruments remain untouched.\n\n"
824 "You may use this tool for example to combine solo instruments into "
825 "a combi sound arrangement by selecting the 'layer' dimension, or you "
826 "might combine similar sounding solo sounds into separate velocity "
827 "split layers by using the 'velocity' dimension, and so on."
828 ));
829
830 // add dimension type combo box
831 {
832 int iLayerDimIndex = -1;
833 Glib::RefPtr<Gtk::ListStore> refComboModel = Gtk::ListStore::create(m_comboDimsModel);
834 for (int i = 0x01, iRow = 0; i < 0xff; i++) {
835 Glib::ustring sType =
836 dimTypeAsString(static_cast<gig::dimension_t>(i));
837 if (sType.find("Unknown") != 0) {
838 Gtk::TreeModel::Row row = *(refComboModel->append());
839 row[m_comboDimsModel.m_type_id] = i;
840 row[m_comboDimsModel.m_type_name] = sType;
841 if (i == gig::dimension_layer) iLayerDimIndex = iRow;
842 iRow++;
843 }
844 }
845 m_comboDimType.set_model(refComboModel);
846 m_comboDimType.pack_start(m_comboDimsModel.m_type_id);
847 m_comboDimType.pack_start(m_comboDimsModel.m_type_name);
848 m_tableDimCombo.attach(m_labelDimType, 0, 1, 0, 1);
849 m_tableDimCombo.attach(m_comboDimType, 1, 2, 0, 1);
850 m_comboDimType.set_active(iLayerDimIndex); // preselect "layer" dimension
851 }
852
853 m_refTreeModel = Gtk::ListStore::create(m_columns);
854 m_treeView.set_model(m_refTreeModel);
855 m_treeView.set_tooltip_text(_(
856 "Use SHIFT + left click or CTRL + left click to select the instruments "
857 "you want to combine."
858 ));
859 m_treeView.append_column("Instrument", m_columns.m_col_name);
860 m_treeView.set_headers_visible(false);
861 m_treeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
862 m_treeView.get_selection()->signal_changed().connect(
863 sigc::mem_fun(*this, &CombineInstrumentsDialog::onSelectionChanged)
864 );
865 m_treeView.show();
866
867 for (int i = 0; true; ++i) {
868 gig::Instrument* instr = gig->GetInstrument(i);
869 if (!instr) break;
870
871 #if DEBUG_COMBINE_INSTRUMENTS
872 {
873 std::cout << "Instrument (" << i << ") '" << instr->pInfo->Name << "' Regions: " << std::flush;
874 for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
875 std::cout << rgn->KeyRange.low << ".." << rgn->KeyRange.high << ", " << std::flush;
876 }
877 std::cout << std::endl;
878 }
879 std::cout << std::endl;
880 #endif
881
882 Glib::ustring name(gig_to_utf8(instr->pInfo->Name));
883 Gtk::TreeModel::iterator iter = m_refTreeModel->append();
884 Gtk::TreeModel::Row row = *iter;
885 row[m_columns.m_col_name] = name;
886 row[m_columns.m_col_instr] = instr;
887 }
888
889 m_buttonBox.set_layout(Gtk::BUTTONBOX_END);
890 m_buttonBox.set_border_width(5);
891 m_buttonBox.pack_start(m_cancelButton, Gtk::PACK_SHRINK);
892 m_buttonBox.pack_start(m_OKButton, Gtk::PACK_SHRINK);
893 m_buttonBox.show();
894
895 m_cancelButton.show();
896 m_OKButton.set_sensitive(false);
897 m_OKButton.show();
898
899 m_cancelButton.signal_clicked().connect(
900 sigc::mem_fun(*this, &CombineInstrumentsDialog::hide)
901 );
902
903 m_OKButton.signal_clicked().connect(
904 sigc::mem_fun(*this, &CombineInstrumentsDialog::combineSelectedInstruments)
905 );
906
907 show_all_children();
908
909 // show a warning to user if he uses a .gig in v2 format
910 if (gig->pVersion->major < 3) {
911 Glib::ustring txt = _(
912 "You are currently using a .gig file in old v2 format. The current "
913 "combine algorithm will most probably fail trying to combine "
914 "instruments in this old format. So better save the file in new v3 "
915 "format before trying to combine your instruments."
916 );
917 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_WARNING);
918 msg.run();
919 }
920 }
921
922 void CombineInstrumentsDialog::combineSelectedInstruments() {
923 std::vector<gig::Instrument*> instruments;
924 std::vector<Gtk::TreeModel::Path> v = m_treeView.get_selection()->get_selected_rows();
925 for (uint i = 0; i < v.size(); ++i) {
926 Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(v[i]);
927 Gtk::TreeModel::Row row = *it;
928 Glib::ustring name = row[m_columns.m_col_name];
929 gig::Instrument* instrument = row[m_columns.m_col_instr];
930 #if DEBUG_COMBINE_INSTRUMENTS
931 printf("Selection '%s' 0x%lx\n\n", name.c_str(), int64_t((void*)instrument));
932 #endif
933 instruments.push_back(instrument);
934 }
935
936 g_warnings.clear();
937
938 try {
939 // which main dimension was selected in the combo box?
940 gig::dimension_t mainDimension;
941 {
942 Gtk::TreeModel::iterator iterType = m_comboDimType.get_active();
943 if (!iterType) throw gig::Exception("No dimension selected");
944 Gtk::TreeModel::Row rowType = *iterType;
945 if (!rowType) throw gig::Exception("Something is wrong regarding dimension selection");
946 int iTypeID = rowType[m_comboDimsModel.m_type_id];
947 mainDimension = static_cast<gig::dimension_t>(iTypeID);
948 }
949
950 // now start the actual cobination task ...
951 combineInstruments(instruments, m_gig, m_newCombinedInstrument, mainDimension);
952 } catch (RIFF::Exception e) {;
953 Gtk::MessageDialog msg(*this, e.Message, false, Gtk::MESSAGE_ERROR);
954 msg.run();
955 return;
956 } catch (...) {
957 Glib::ustring txt = _("An unknown exception occurred!");
958 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
959 msg.run();
960 return;
961 }
962
963 if (!g_warnings.empty()) {
964 Glib::ustring txt = _(
965 "Combined instrument was created successfully, but there were warnings:"
966 );
967 txt += "\n\n";
968 for (Warnings::const_iterator itWarn = g_warnings.begin();
969 itWarn != g_warnings.end(); ++itWarn)
970 {
971 txt += "-> " + *itWarn + "\n";
972 }
973 txt += "\n";
974 txt += _(
975 "You might also want to check the console for further warnings and "
976 "error messages."
977 );
978 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_WARNING);
979 msg.run();
980 }
981
982 // no error occurred
983 m_fileWasChanged = true;
984 hide();
985 }
986
987 void CombineInstrumentsDialog::onSelectionChanged() {
988 std::vector<Gtk::TreeModel::Path> v = m_treeView.get_selection()->get_selected_rows();
989 m_OKButton.set_sensitive(v.size() >= 2);
990 }
991
992 bool CombineInstrumentsDialog::fileWasChanged() const {
993 return m_fileWasChanged;
994 }
995
996 gig::Instrument* CombineInstrumentsDialog::newCombinedInstrument() const {
997 return m_newCombinedInstrument;
998 }

  ViewVC Help
Powered by ViewVC