/[svn]/libgig/trunk/src/korg2gig.cpp
ViewVC logotype

Contents of /libgig/trunk/src/korg2gig.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2546 - (show annotations) (download)
Sat May 10 17:13:52 2014 UTC (9 years, 11 months ago) by schoenebeck
File size: 33080 byte(s)
* korg2gig tool: Fixed .gig file corruption (caused i.e. crash in
  gigedit under certain conditions) regarding certain velocity
  split definitions.

1 /***************************************************************************
2 * *
3 * Copyright (C) 2014 Christian Schoenebeck *
4 * <cuse@users.sourceforge.net> *
5 * *
6 * This program is part of libgig. *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the Free Software *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
21 * MA 02111-1307 USA *
22 ***************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #include <iostream>
29 #include <cstdlib>
30 #include <string>
31 #include <set>
32
33 #if !defined(WIN32)
34 # include <unistd.h>
35 #endif
36
37 #include "Korg.h"
38 #include "gig.h"
39
40 using namespace std;
41
42 static string Revision() {
43 string s = "$Revision$";
44 return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword
45 }
46
47 static void printVersion() {
48 cout << "korg2gig revision " << Revision() << endl;
49 cout << "using " << gig::libraryName() << " " << gig::libraryVersion() << endl;
50 }
51
52 static void printUsage() {
53 cout << "korg2gig - convert sound files from KORG to GigaStudio format." << endl;
54 cout << endl;
55 cout << "Usage: korg2gig [-v] [-f] [--interpret-names] FILE1 [ FILE2 ... ] NEWFILE" << endl;
56 cout << endl;
57 cout << " -v Print version and exit." << endl;
58 cout << endl;
59 cout << " -f Overwrite output file if it already exists." << endl;
60 cout << endl;
61 cout << " --interpret-names" << endl;
62 cout << " Try to guess left/right sample pairs and velocity splits." << endl;
63 cout << endl;
64 }
65
66 static bool endsWith(const string& haystack, const string& needle) {
67 return haystack.substr(haystack.size() - needle.size(), needle.size()) == needle;
68 }
69
70 static bool fileExists(const string& filename) {
71 FILE* hFile = fopen(filename.c_str(), "r");
72 if (!hFile) return false;
73 fclose(hFile);
74 return true;
75 }
76
77 // this could also be replaced by fopen(name, "w") to simply truncate the file to zero
78 static void deleteFile(const string& filename) {
79 #if defined(WIN32)
80 DeleteFile(filename.c_str());
81 #else
82 unlink(filename.c_str());
83 #endif
84 }
85
86 /**
87 * Intermediate container class which helps to group .KMP ("multi sample")
88 * instruments together, so that only one .gig instrument will be created per
89 * respective identified .KMP instrument group.
90 */
91 class InstrGroup : public vector<Korg::KMPInstrument*> {
92 public:
93 bool isStereo; ///< True if a stereo pair of sampls was found, will result in a "stereo dimension" being created in the .gig instrument.
94 bool hasVelocitySplits; ///< True if velocity split informations were identified in the .KMP ("multi sample") instrument name, a velocity dimension will be created accordingly.
95 string baseName; ///< This is the .KMP ("multi sample") instrument name with the numeric velecity range hints and stereo pair hints being stripped of, resulting in a shorter left hand string that all KMPinstruments have in common of this group.
96
97 typedef vector<Korg::KMPInstrument*> base_t;
98
99 InstrGroup () :
100 vector<Korg::KMPInstrument*>(),
101 isStereo(false), hasVelocitySplits(false)
102 {
103 }
104
105 InstrGroup (const InstrGroup& x) :
106 vector<Korg::KMPInstrument*>(x)
107 {
108 isStereo = x.isStereo;
109 hasVelocitySplits = x.hasVelocitySplits;
110 baseName = x.baseName;
111 }
112
113 InstrGroup& operator= (const InstrGroup& x) {
114 base_t::operator=(x);
115 isStereo = x.isStereo;
116 hasVelocitySplits = x.hasVelocitySplits;
117 baseName = x.baseName;
118 return *this;
119 }
120 };
121
122 /// Removes spaces from the beginning and end of @a s.
123 inline void stripWhiteSpace(string& s) {
124 // strip white space at the beginning
125 for (int i = 0; i < s.size(); ++i) {
126 if (s[i] != ' ') {
127 s = s.substr(i);
128 break;
129 }
130 }
131 // strip white space at the end
132 for (int i = s.size() - 1; i >= 0; --i) {
133 if (s[i] != ' ') {
134 s = s.substr(0, i+1);
135 break;
136 }
137 }
138 }
139
140 inline bool isDigit(const char& c) {
141 return c >= '0' && c <= '9';
142 }
143
144 inline int digitToInt(const char& c) {
145 return c - '0';
146 }
147
148 /**
149 * I.e. for @a s = "FOO 003-127" it sets @a from = 3, @a to = 127 and
150 * returns "FOO ".
151 *
152 * @param s - input string
153 * @param from - (output) numeric left part of range
154 * @param to - (output) numeric right part of range
155 * @returns input string without the numeric range string component
156 */
157 inline string parseNumberRangeAtEnd(const string& s, int& from, int& to) {
158 enum _State {
159 EXPECT_NUMBER1,
160 EXPECT_NUMBER1_OR_MINUS,
161 EXPECT_NUMBER2,
162 EXPECT_NUMBER2_OR_END
163 };
164 int state = EXPECT_NUMBER1;
165 from = to = -1;
166 int dec = 10;
167 for (int i = s.size() - 1; i >= 0; --i) {
168 switch (state) {
169 case EXPECT_NUMBER1:
170 if (isDigit(s[i])) {
171 to = digitToInt(s[i]);
172 state++;
173 } else return s; // unexpected, error
174 break;
175 case EXPECT_NUMBER1_OR_MINUS:
176 if (s[i] == '-') {
177 state++;
178 } else if (isDigit(s[i])) {
179 to += digitToInt(s[i]) * dec;
180 dec *= 10;
181 } else return s; // unexpected, error
182 break;
183 case EXPECT_NUMBER2:
184 if (isDigit(s[i])) {
185 from = digitToInt(s[i]);
186 dec = 10;
187 state++;
188 } else return s; // unexpected, error
189 break;
190 case EXPECT_NUMBER2_OR_END:
191 if (isDigit(s[i])) {
192 from += digitToInt(s[i]) * dec;
193 dec *= 10;
194 } else return s.substr(0, i+1);
195 break;
196 }
197 }
198 return (state == EXPECT_NUMBER2_OR_END) ? "" : s;
199 }
200
201 inline void stripLeftOrRightMarkerAtEnd(string& s);
202
203 /**
204 * I.e. for @a s = "FOO 003-127 -R" it returns "FOO" and sets @a from = 3 and
205 * @a to = 127. In case no number range was found (that is on parse errors),
206 * the returned string will equal the input string @a s instead.
207 */
208 inline string parseNumberRange(const string& s, int& from, int& to) {
209 string w = s;
210 stripWhiteSpace(w);
211 stripLeftOrRightMarkerAtEnd(w);
212 string result = parseNumberRangeAtEnd(w, from, to);
213 if (result == w) return s; // parse error occured, return original input s
214 stripWhiteSpace(result);
215 return result;
216 }
217
218 /// I.e. for @a s = "FOO 003-127" it returns "FOO".
219 inline string stripNumberRangeAtEnd(string s) {
220 stripWhiteSpace(s);
221 int from, to;
222 s = parseNumberRangeAtEnd(s, from, to);
223 stripWhiteSpace(s);
224 return s;
225 }
226
227 inline bool hasLeftOrRightMarker(string s) {
228 stripWhiteSpace(s);
229 return endsWith(s, "-L") || endsWith(s, "-R");
230 }
231
232 inline bool isLeftMarker(string s) {
233 stripWhiteSpace(s);
234 return endsWith(s, "-L");
235 }
236
237 inline void stripLeftOrRightMarkerAtEnd(string& s) {
238 if (hasLeftOrRightMarker(s)) {
239 s = s.substr(0, s.size() - 2);
240 stripWhiteSpace(s);
241 }
242 }
243
244 /// I.e. for @a name = "FOO 003-127 -R" it returns "FOO".
245 static string interpretBaseName(string name) {
246 // if there is "-L" or "-R" at the end, then remove it
247 stripLeftOrRightMarkerAtEnd(name);
248 // if there is a velocity split range indicator like "008-110", then remove it
249 string s = stripNumberRangeAtEnd(name);
250 return (s.empty()) ? name : s;
251 }
252
253 static bool hasNumberRangeIndicator(string name) {
254 int from, to;
255 string baseName = parseNumberRange(name, from, to);
256 return baseName != name;
257 }
258
259 inline InstrGroup* findGroupWithBaseName(vector<InstrGroup>& groups, const string& baseName) {
260 if (baseName.empty()) return NULL;
261 for (int i = 0; i < groups.size(); ++i)
262 if (groups[i].baseName == baseName)
263 return &groups[i];
264 return NULL;
265 }
266
267 /**
268 * If requested, try to group individual input instruments by looking at their
269 * instrument names (guessing left/right samples and velocity splits by name).
270 */
271 static vector<InstrGroup> groupInstruments(
272 const vector<Korg::KMPInstrument*>& instruments, bool bInterpretNames)
273 {
274 vector<InstrGroup> result;
275 if (!bInterpretNames) {
276 // no grouping requested, so make each input instrument its own group
277 for (vector<Korg::KMPInstrument*>::const_iterator it = instruments.begin();
278 it != instruments.end(); ++it)
279 {
280 InstrGroup g;
281 g.baseName = (*it)->Name();
282 g.push_back(*it);
283 result.push_back(g);
284 }
285 } else {
286 // try grouping the input instruments by instrument name interpretation
287 for (vector<Korg::KMPInstrument*>::const_iterator it = instruments.begin();
288 it != instruments.end(); ++it)
289 {
290 const string baseName = interpretBaseName((*it)->Name());
291 InstrGroup* g = findGroupWithBaseName(result, baseName);
292 if (!g) {
293 result.push_back(InstrGroup());
294 g = &result.back();
295 g->baseName = baseName;
296 }
297 g->push_back(*it);
298 g->isStereo |= hasLeftOrRightMarker( (*it)->Name() );
299 g->hasVelocitySplits |= hasNumberRangeIndicator( (*it)->Name() );
300 }
301 }
302 return result;
303 }
304
305 static set<DLS::range_t> collectVelocitySplits(const InstrGroup& group, DLS::range_t keyRange) {
306 set<DLS::range_t> velocityRanges;
307 for (int i = 0; i < group.size(); ++i) {
308 Korg::KMPInstrument* instr = group[i];
309 int iLowerKey = 0;
310 for (int k = 0; k < instr->GetRegionCount(); ++k) {
311 Korg::KMPRegion* rgn = instr->GetRegion(k);
312 DLS::range_t keyRange2 = { iLowerKey, rgn->TopKey };
313 iLowerKey = rgn->TopKey + 1; // for next loop cycle
314 if (keyRange == keyRange2) {
315 int from, to;
316 string baseName = parseNumberRange(instr->Name(), from, to);
317 if (baseName != instr->Name()) { // number range like "003-120" found in instrument name ...
318 DLS::range_t velRange = { from, to };
319 velocityRanges.insert(velRange);
320 }
321 }
322 }
323 }
324 return velocityRanges;
325 }
326
327 /**
328 * Ensure that the given list of ranges covers the full range between 0 .. 127.
329 *
330 * @param orig - (input) original set of ranges (read only)
331 * @param corrected - (output) corrected set of ranges (will be cleared and refilled)
332 * @return map that pairs respective original range with respective corrected range
333 */
334 static map<DLS::range_t,DLS::range_t> killGapsInRanges(const set<DLS::range_t>& orig, set<DLS::range_t>& corrected) {
335 map<DLS::range_t,DLS::range_t> result;
336 corrected.clear();
337 if (orig.empty()) return result;
338
339 int iLow = 0;
340 int i = 0;
341 for (set<DLS::range_t>::const_iterator it = orig.begin(); it != orig.end(); ++it, ++i) {
342 DLS::range_t r = *it;
343 r.low = iLow;
344 iLow = r.high + 1;
345 if (i == orig.size() - 1) r.high = 127;
346 corrected.insert(r);
347 result[*it] = r;
348 }
349 return result;
350 }
351
352 static void printRanges(const set<DLS::range_t>& ranges) {
353 cout << "{ ";
354 for (set<DLS::range_t>::const_iterator it = ranges.begin(); it != ranges.end(); ++it) {
355 if (it != ranges.begin()) cout << ", ";
356 cout << (int)it->low << ".." << (int)it->high;
357 }
358 cout << " }" << flush;
359 }
360
361 static vector<Korg::KSFSample*> ksfSamples; // input .KSF files
362 static vector<Korg::KMPInstrument*> kmpInstruments; // input .KMP files
363 static gig::File* g_gig = NULL; // output .gig file
364
365 static Korg::KSFSample* findKSFSampleWithFileName(const string& name) {
366 for (int i = 0; i < ksfSamples.size(); ++i)
367 if (ksfSamples[i]->FileName() == name)
368 return ksfSamples[i];
369 return NULL;
370 }
371
372 static map<Korg::KSFSample*,gig::Sample*> sampleRelations;
373
374 /**
375 * First pass: if the respective gig sample does not exist yet, it will be
376 * created, meta informations and size will be copied from the original Korg
377 * sample. However the actual sample data will not yet be copied here yet.
378 * Because the .gig file needs to be saved and resized in file size accordingly
379 * to the total size of all samples. For efficiency reasons the resize opertion
380 * is first just here schedued for all samples, then in a second pass the actual
381 * data written in writeAllSampleData();
382 */
383 static gig::Sample* findOrcreateGigSampleForKSFSample(Korg::KSFSample* ksfSample, gig::Group* gigSampleGroup, const Korg::KMPRegion* kmpRegion = NULL) {
384 if (ksfSample->SamplePoints <= 0) {
385 cout << "Skipping KSF sample '" << ksfSample->FileName() << "' (because of zero length)." << endl;
386 return NULL; // writing a gig sample with zero size to a gig file would throw an exception
387 }
388
389 gig::Sample* gigSample = sampleRelations[ksfSample];
390 if (gigSample) return gigSample;
391
392 // no such gig file yet, create it ...
393
394 // add new gig sample to gig output file
395 gigSample = g_gig->AddSample();
396 gigSampleGroup->AddSample(gigSample);
397 sampleRelations[ksfSample] = gigSample;
398 // convert Korg Sample -> gig Sample
399 gigSample->pInfo->Name = ksfSample->Name;
400 gigSample->Channels = ksfSample->Channels;
401 gigSample->SamplesPerSecond = ksfSample->SampleRate;
402 gigSample->BitDepth = ksfSample->BitDepth;
403 gigSample->FrameSize = ksfSample->Channels * ksfSample->BitDepth / 8;
404 //gigSample->SamplesTotal = ksfSample->SamplePoints;
405 if (kmpRegion) {
406 gigSample->MIDIUnityNote = kmpRegion->OriginalKey;
407 }
408 if (ksfSample->LoopEnd) {
409 gigSample->Loops = 1; // the gig format currently supports only one loop per sample
410 gigSample->LoopType = gig::loop_type_normal;
411 gigSample->LoopStart = ksfSample->LoopStart;
412 gigSample->LoopEnd = ksfSample->LoopEnd;
413 gigSample->LoopSize = gigSample->LoopEnd - gigSample->LoopStart;
414 gigSample->LoopPlayCount = 0; // infinite
415 }
416 // schedule for resize (will be performed when gig->Save() is called)
417 gigSample->Resize(ksfSample->SamplePoints);
418
419 return gigSample;
420 }
421
422 /**
423 * Second pass: see comment of findOrcreateGigSampleForKSFSample().
424 */
425 static void writeAllSampleData() {
426 for (map<Korg::KSFSample*,gig::Sample*>::iterator it = sampleRelations.begin();
427 it != sampleRelations.end(); ++it)
428 {
429 Korg::KSFSample* ksfSample = it->first;
430 gig::Sample* gigSample = it->second;
431 if (!gigSample) continue; // can be NULL if the source KORG file had zero length (attempting to write a .gig file with zero size sample would throw an exception)
432 Korg::buffer_t src = ksfSample->LoadSampleData();
433 gigSample->SetPos(0);
434 gigSample->Write(src.pStart, ksfSample->SamplePoints);
435 ksfSample->ReleaseSampleData();
436 }
437 }
438
439 static gig::Sample* findOrCreateGigSampleForKSFRegion(const Korg::KMPRegion* kmpRegion) {
440 int from, to;
441 const string baseName = parseNumberRange(kmpRegion->GetInstrument()->Name(), from, to);
442
443 gig::Group* gigSampleGroup = g_gig->GetGroup(baseName);
444 if (!gigSampleGroup) {
445 gigSampleGroup = g_gig->AddGroup();
446 gigSampleGroup->Name = baseName;
447 }
448
449 Korg::KSFSample* ksfSample = findKSFSampleWithFileName(kmpRegion->FullSampleFileName());
450 if (!ksfSample)
451 throw Korg::Exception("Internal error: Could not resolve KSFSample object");
452 gig::Sample* gigSample = sampleRelations[ksfSample];
453 if (gigSample) return gigSample;
454 return findOrcreateGigSampleForKSFSample(
455 ksfSample, gigSampleGroup, kmpRegion
456 );
457 }
458
459 static void loadKorgFile(const string& filename, bool bReferenced = false) {
460 try {
461 if (endsWith(filename, ".KMP")) {
462 cout << "Loading KORG Multi Sample file '" << filename << "' ... " << flush;
463 Korg::KMPInstrument* instr = new Korg::KMPInstrument(filename);
464 cout << "OK\n";
465 kmpInstruments.push_back(instr);
466
467 for (int i = 0; i < instr->GetRegionCount(); ++i) {
468 Korg::KMPRegion* rgn = instr->GetRegion(i);
469 // check if the sample referenced by this region was already
470 // loaded, if not then load it ...
471 if (!findKSFSampleWithFileName(rgn->FullSampleFileName()))
472 loadKorgFile(rgn->FullSampleFileName(), true);
473 }
474 } else if (endsWith(filename, ".KSF")) {
475 cout << "Loading " << (bReferenced ? "referenced " : "") << "KORG Sample file '" << filename << "' ... " << flush;
476 Korg::KSFSample* smpl = new Korg::KSFSample(filename);
477 cout << "OK\n";
478 ksfSamples.push_back(smpl);
479 } else if (endsWith(filename, ".PCG")) {
480 cerr << "Error with input file '" << filename << "':" << endl;
481 cerr << "There is no support for .PCG files in this version of korg2gig yet." << endl;
482 exit(EXIT_FAILURE);
483 } else {
484 cerr << "Unknown file type (file name postfix) for input file '" << filename << "'" << endl;
485 exit(EXIT_FAILURE);
486 }
487 } catch (RIFF::Exception e) {
488 cerr << "Failed opening input file '" << filename << "':" << endl;
489 e.PrintMessage();
490 exit(EXIT_FAILURE);
491 } catch (...) {
492 cerr << "Failed opening input file '" << filename << "':" << endl;
493 cerr << "Unknown exception occured while trying to access input file." << endl;
494 exit(EXIT_FAILURE);
495 }
496 }
497
498 static void cleanup() {
499 if (g_gig) {
500 delete g_gig;
501 g_gig = NULL;
502 }
503 for (int i = 0; i < ksfSamples.size(); ++i) delete ksfSamples[i];
504 ksfSamples.clear();
505 for (int i = 0; i < kmpInstruments.size(); ++i) delete kmpInstruments[i];
506 kmpInstruments.clear();
507 }
508
509 inline int getDimensionIndex(gig::Region* region, gig::dimension_t type) {
510 for (int d = 0; d < region->Dimensions; ++d)
511 if (region->pDimensionDefinitions[d].dimension == type)
512 return d;
513 return -1;
514 }
515
516 int main(int argc, char *argv[]) {
517 bool bInterpretNames = false;
518 bool bForce = false;
519
520 // validate & parse arguments provided to this program
521 if (argc < 3) {
522 printUsage();
523 return EXIT_FAILURE;
524 }
525 int iArg;
526 for (iArg = 1; iArg < argc; ++iArg) {
527 const string opt = argv[iArg];
528 if (opt == "--") { // common for all command line tools: separator between initial option arguments and i.e. subsequent file arguments
529 iArg++;
530 break;
531 }
532 if (opt.substr(0, 1) != "-") break;
533
534 if (opt == "-v") {
535 printVersion();
536 return EXIT_SUCCESS;
537 } else if (opt == "-f") {
538 bForce = true;
539 } else if (opt == "--interpret-names") {
540 bInterpretNames = true;
541 } else {
542 cerr << "Unknown option '" << opt << "'" << endl;
543 cerr << endl;
544 printUsage();
545 return EXIT_FAILURE;
546 }
547 }
548
549 set<string> inFileNames;
550 string outFileName;
551
552 // all options have been processed, all subsequent args should be files
553 for (; iArg < argc; ++iArg) {
554 if (iArg == argc - 1) {
555 outFileName = argv[iArg];
556 } else {
557 inFileNames.insert(argv[iArg]);
558 }
559 }
560 if (inFileNames.empty() || outFileName.empty()) {
561 cerr << "You must provide at least one input file (Korg format) and one output file (.gig format)!" << endl;
562 return EXIT_FAILURE;
563 }
564
565 // check if output file already exists
566 if (fileExists(outFileName)) {
567 if (bForce) deleteFile(outFileName);
568 else {
569 cerr << "Output file '" << outFileName << "' already exists. Use -f to overwrite it." << endl;
570 return EXIT_FAILURE;
571 }
572 }
573
574 // open all input (KORG) files
575 for (set<string>::const_iterator it = inFileNames.begin();
576 it != inFileNames.end(); ++it)
577 {
578 loadKorgFile(*it);
579 }
580
581 // create and assemble a new .gig file as output
582 try {
583 // start with an empty .gig file
584 g_gig = new gig::File();
585
586 // if requested, try to group individual input instruments by looking
587 // at their names, and create only one output instrument per group
588 // (guessing left/right samples and velocity splits by name)
589 vector<InstrGroup> instrGroups = groupInstruments(kmpInstruments, bInterpretNames);
590
591 // create the individual instruments in the gig file
592 for (int i = 0; i < instrGroups.size(); ++i) {
593 InstrGroup& group = instrGroups[i];
594 cout << "Adding gig instrument '" << group.baseName << "'" << endl;
595
596 gig::Instrument* instr = g_gig->AddInstrument();
597 instr->pInfo->Name = group.baseName;
598
599 map<DLS::range_t, gig::Region*> regions; // the gig instrument regions to be created, sorted by key ranges
600
601 // apply korg regions to gig regions
602 for (InstrGroup::iterator itInstr = group.begin();
603 itInstr != group.end(); ++itInstr)
604 {
605 Korg::KMPInstrument* kmpInstr = *itInstr;
606 cout << " |---> KMP multi sample '" << kmpInstr->Name() << "'" << endl;
607
608 int iLowKey = 0;
609 for (int k = 0; k < kmpInstr->GetRegionCount(); ++k) {
610 Korg::KMPRegion* kmpRegion = kmpInstr->GetRegion(k);
611 DLS::range_t keyRange = { iLowKey, kmpRegion->TopKey };
612 iLowKey = kmpRegion->TopKey + 1; // for next loop cycle
613 gig::Region* gigRegion = regions[keyRange];
614
615 // if there is no gig region for that key range yet, create it
616 if (!gigRegion) {
617 gigRegion = instr->AddRegion();
618 gigRegion->SetKeyRange(keyRange.low, keyRange.high);
619 regions[keyRange] = gigRegion;
620 }
621
622 uint8_t iDimBits[8] = {}; // used to select the target gig::DimensionRegion for current source KMPRegion to be applied
623
624 bool isStereoSplit = bInterpretNames && hasLeftOrRightMarker(kmpInstr->Name());
625 if (isStereoSplit) { // stereo dimension split required for this region ...
626 int iStereoDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_samplechannel);
627
628 // create stereo dimension if it doesn't exist already ...
629 if (iStereoDimensionIndex < 0) {
630 gig::dimension_def_t dim;
631 dim.dimension = gig::dimension_samplechannel;
632 dim.bits = 1; // 2^(1) = 2
633 dim.zones = 2; // stereo = 2 audio channels = 2 split zones
634 cout << "Adding stereo dimension." << endl;
635 gigRegion->AddDimension(&dim);
636
637 iStereoDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_samplechannel);
638 }
639
640 if (iStereoDimensionIndex < 0)
641 throw gig::Exception("Internal error: Could not resolve target stereo dimension bit");
642
643 // select dimension bit for this stereo dimension split
644 iDimBits[iStereoDimensionIndex] = isLeftMarker(kmpInstr->Name()) ? 0 : 1;
645 }
646
647 int iVelocityDimensionIndex = -1;
648 DLS::range_t velRange = { 0 , 127 };
649 bool isVelocitySplit = bInterpretNames && hasNumberRangeIndicator(kmpInstr->Name());
650 if (isVelocitySplit) { // a velocity split is required for this region ...
651 // get the set of velocity split zones defined for
652 // this particular output instrument and key range
653 set<DLS::range_t> origVelocitySplits = collectVelocitySplits(group, keyRange);
654 // if there are any gaps, that is if the list of velocity
655 // split ranges do not cover the full range completely
656 // between 0 .. 127, then auto correct it by increasing
657 // the split zones accordingly
658 map<DLS::range_t,DLS::range_t> velocityCorrectionMap; // maps original defined velocity range (key) -> corrected velocity range (value)
659 {
660 set<DLS::range_t> correctedSplits;
661 velocityCorrectionMap = killGapsInRanges(origVelocitySplits, correctedSplits);
662 if (correctedSplits != origVelocitySplits) {
663 cout << "WARNING: Velocity splits did not cover the full range 0..127, auto adjusted it from " << flush;
664 printRanges(origVelocitySplits);
665 cout << " to " << flush;
666 printRanges(correctedSplits);
667 cout << endl;
668 }
669 }
670
671 // get the velocity range for current KORG region
672 {
673 int from, to;
674 if (parseNumberRange(kmpInstr->Name(), from, to) == kmpInstr->Name())
675 throw Korg::Exception("Internal error: parsing velocity range failed");
676 velRange.low = from;
677 velRange.high = to;
678 if (velocityCorrectionMap.find(velRange) == velocityCorrectionMap.end())
679 throw Korg::Exception("Internal error: inconsistency in velocity split correction map");
680 velRange = velocityCorrectionMap[velRange]; // corrected
681 }
682
683 // create velocity split dimension if it doesn't exist already ...
684 iVelocityDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_velocity);
685 if (iVelocityDimensionIndex < 0) {
686 gig::dimension_def_t dim;
687 dim.dimension = gig::dimension_velocity;
688 dim.zones = origVelocitySplits.size();
689 // Find the number of bits required to hold the
690 // specified amount of zones.
691 int zoneBits = dim.zones - 1;
692 for (dim.bits = 0; zoneBits > 1; dim.bits += 2, zoneBits >>= 2);
693 dim.bits += zoneBits;
694 cout << "Adding velocity dimension: zones=" << (int)dim.zones << ", bits=" << (int)dim.bits << endl;
695 gigRegion->AddDimension(&dim);
696
697 iVelocityDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_velocity);
698 }
699
700 if (iVelocityDimensionIndex < 0)
701 throw gig::Exception("Internal error: Could not resolve target velocity dimension bit");
702
703 // find the velocity zone for this one
704 int iVelocitySplitZone = -1;
705 {
706 int i = 0;
707 for (map<DLS::range_t,DLS::range_t>::const_iterator itVelSplit = velocityCorrectionMap.begin();
708 itVelSplit != velocityCorrectionMap.end(); ++itVelSplit, ++i)
709 {
710 if (itVelSplit->second == velRange) { // already corrected before, thus second, not first
711 iVelocitySplitZone = i;
712 break;
713 }
714 }
715 if (iVelocitySplitZone == -1)
716 throw gig::Exception("Internal error: Could not resolve target velocity dimension zone");
717 }
718
719 // select dimension bit for this stereo dimension split
720 iDimBits[iVelocityDimensionIndex] = iVelocitySplitZone;
721 }
722
723 // resolve target gig::DimensionRegion for the left/right and velocity split zone detected above
724 gig::DimensionRegion* dimRgn = gigRegion->GetDimensionRegionByBit(iDimBits);
725 if (!dimRgn)
726 throw gig::Exception("Internal error: Could not resolve Dimension Region");
727
728 // if this is a velocity split, apply the precise velocity split range values
729 if (isVelocitySplit) {
730 dimRgn->VelocityUpperLimit = velRange.high; // gig v2
731 dimRgn->DimensionUpperLimits[iVelocityDimensionIndex] = velRange.high; // gig v3 and above
732 }
733
734 // assign the respective gig sample to this dimension region
735 gig::Sample* gigSample = findOrCreateGigSampleForKSFRegion(kmpRegion);
736 dimRgn->pSample = gigSample; // might be NULL (if Korg sample had zero size)
737 if (gigSample) {
738 dimRgn->UnityNote = gigSample->MIDIUnityNote;
739 if (gigSample->Loops) {
740 DLS::sample_loop_t loop;
741 loop.Size = sizeof(loop);
742 loop.LoopType = gig::loop_type_normal;
743 loop.LoopStart = gigSample->LoopStart;
744 loop.LoopLength = gigSample->LoopEnd - gigSample->LoopStart;
745 dimRgn->AddSampleLoop(&loop);
746 }
747 }
748 }
749 }
750 }
751
752 // add the .KSF samples that are not referenced by any instrument
753 for (int i = 0; i < ksfSamples.size(); ++i) {
754 Korg::KSFSample* ksfSample = ksfSamples[i];
755 gig::Sample* gigSample = sampleRelations[ksfSample];
756 if (!gigSample) {
757 // put those "orphaned" samples into a sample group called
758 // "Not referenced", showing the user in gigedit that this
759 // sample is not used (referenced) by any gig instrument
760 const string sNotReferenced = "Not referenced";
761 gig::Group* gigSampleGroup = g_gig->GetGroup(sNotReferenced);
762 if (!gigSampleGroup) {
763 gigSampleGroup = g_gig->AddGroup();
764 gigSampleGroup->Name = sNotReferenced;
765 }
766 // create the gig sample
767 gigSample = findOrcreateGigSampleForKSFSample(ksfSample, gigSampleGroup);
768 }
769 }
770
771 // save result to disk (as .gig file)
772 cout << "Saving converted (.gig) file to '" << outFileName << "' ... " << flush;
773 g_gig->Save(outFileName);
774 cout << "OK\n";
775 cout << "Writing all samples to converted (.gig) file ... " << flush;
776 writeAllSampleData();
777 cout << "OK\n";
778 } catch (RIFF::Exception e) {
779 cerr << "Failed generating output file:" << endl;
780 e.PrintMessage();
781 cleanup();
782 return EXIT_FAILURE;
783 } catch (...) {
784 cerr << "Unknown exception while trying to assemble output file." << endl;
785 cleanup();
786 return EXIT_FAILURE;
787 }
788
789 cleanup();
790 return EXIT_SUCCESS;
791 }

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC