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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3175 - (show annotations) (download)
Thu May 11 11:34:19 2017 UTC (6 years, 11 months ago) by schoenebeck
File size: 34677 byte(s)
* Fixed potential crash in command line tools gig2stereo, korg2gig,
  korgdump and sf2extract.

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

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC