/[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 2571 - (show annotations) (download)
Wed May 21 20:33:45 2014 UTC (9 years, 10 months ago) by schoenebeck
File size: 34527 byte(s)
* moved source files of command line tools to new subdir src/tools

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

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC