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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3175 - (hide 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 schoenebeck 2543 /***************************************************************************
2     * *
3 schoenebeck 3175 * Copyright (C) 2014-2017 Christian Schoenebeck *
4 schoenebeck 2543 * <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 schoenebeck 2573 #include "../Korg.h"
38     #include "../gig.h"
39 schoenebeck 2543
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 schoenebeck 2561 static bool beginsWith(const string& haystack, const string& needle) {
67     return haystack.substr(0, needle.size()) == needle;
68     }
69    
70 schoenebeck 2543 static bool endsWith(const string& haystack, const string& needle) {
71 schoenebeck 3175 if (haystack.size() < needle.size()) return false;
72 schoenebeck 2543 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 schoenebeck 3048 if (result == w) return s; // parse error occurred, return original input s
219 schoenebeck 2543 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 persson 2836 uint16_t iLowerKey = 0;
315 schoenebeck 2543 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 persson 2836 DLS::range_t velRange = { uint16_t(from), uint16_t(to) };
324 schoenebeck 2543 velocityRanges.insert(velRange);
325     }
326     }
327     }
328     }
329     return velocityRanges;
330     }
331    
332 schoenebeck 2546 /**
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 schoenebeck 2543 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 schoenebeck 2561 if (kmpRegion->SampleFileName == "SKIPPEDSAMPL" ||
455     (beginsWith(kmpRegion->SampleFileName, "INTERNAL") && !endsWith(kmpRegion->SampleFileName, ".KSF")))
456     {
457     return NULL;
458     }
459    
460 schoenebeck 2543 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 schoenebeck 2561 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 schoenebeck 2543 // 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 schoenebeck 3048 cerr << "Unknown exception occurred while trying to access input file." << endl;
513 schoenebeck 2543 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 schoenebeck 2554 if (argc < 3) {
564     printUsage();
565     return EXIT_FAILURE;
566     }
567 schoenebeck 2543
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 persson 2836 uint16_t iLowKey = 0;
628 schoenebeck 2543 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 schoenebeck 2546 cout << "Adding stereo dimension." << endl;
654 schoenebeck 2543 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 schoenebeck 2546 // 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 schoenebeck 2543
690 schoenebeck 2551 // 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 schoenebeck 2543
707 schoenebeck 2551 // create velocity split dimension if it doesn't exist already ...
708 schoenebeck 2543 iVelocityDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_velocity);
709 schoenebeck 2551 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 schoenebeck 2543
721 schoenebeck 2551 iVelocityDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_velocity);
722     }
723 schoenebeck 2543
724 schoenebeck 2551 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 schoenebeck 2543 {
730 schoenebeck 2551 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 schoenebeck 2543 }
739 schoenebeck 2551 if (iVelocitySplitZone == -1)
740     throw gig::Exception("Internal error: Could not resolve target velocity dimension zone");
741 schoenebeck 2543 }
742 schoenebeck 2551
743     // select dimension bit for this stereo dimension split
744     iDimBits[iVelocityDimensionIndex] = iVelocitySplitZone;
745 schoenebeck 2543 }
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 schoenebeck 2909 dimRgn->FineTune = kmpRegion->Tune;
760    
761 schoenebeck 2543 // assign the respective gig sample to this dimension region
762     gig::Sample* gigSample = findOrCreateGigSampleForKSFRegion(kmpRegion);
763 schoenebeck 2561 dimRgn->pSample = gigSample; // might be NULL (if Korg sample had zero size, or if the original instrument's internal samples were used)
764 schoenebeck 2543 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