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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3991 - (hide annotations) (download)
Tue Aug 31 15:58:57 2021 UTC (2 years, 8 months ago) by schoenebeck
File size: 28437 byte(s)
* wav2gig tool: add --dry-run option.

* Bumped version (4.3.0.svn32).

1 schoenebeck 3980 /***************************************************************************
2     * *
3     * Copyright (C) 2021 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 schoenebeck 3981 #include <errno.h>
28     #include <sys/stat.h>
29     #include <dirent.h>
30     #include <string.h>
31 schoenebeck 3980 #include <iostream>
32     #include <cstdlib>
33     #include <string>
34     #include <set>
35     #include <map>
36 schoenebeck 3981 #include <regex>
37 schoenebeck 3980
38     #if !defined(WIN32)
39     # include <unistd.h>
40     #endif
41    
42     #include "../gig.h"
43     #include "../helper.h" // for ToString()
44    
45     // only libsndfile is available for Windows, so we use that for writing the sound files
46     #ifdef WIN32
47     # define HAVE_SNDFILE 1
48     #endif // WIN32
49    
50     // abort compilation here if libsndfile is not available
51     #if !HAVE_SNDFILE
52     # error "It seems as if libsndfile is not available!"
53     # error "(HAVE_SNDFILE is false)"
54     #endif
55    
56     #if HAVE_SNDFILE
57     # ifdef LIBSNDFILE_HEADER_FILE
58     # include LIBSNDFILE_HEADER_FILE(sndfile.h)
59     # else
60     # include <sndfile.h>
61     # endif
62     #endif // HAVE_SNDFILE
63    
64     #ifdef WIN32
65     # define DIR_SEPARATOR '\\'
66     #else
67     # define DIR_SEPARATOR '/'
68     #endif
69    
70     using namespace std;
71    
72     static string Revision() {
73     string s = "$Revision$";
74     return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword
75     }
76    
77     static void printVersion() {
78     cout << "wav2gig revision " << Revision() << endl;
79     cout << "using " << gig::libraryName() << " " << gig::libraryVersion() << endl;
80     }
81    
82     static void printUsage() {
83     cout << "wav2gig - Create GigaStudio file from a set of WAV files." << endl;
84     cout << endl;
85 schoenebeck 3990 cout << "Usage: wav2gig [OPTIONS] GIGFILE WAVFILEORDIR1 [ WAVFILEORDIR2 ... ]" << endl;
86 schoenebeck 3980 cout << endl;
87     cout << " -v Print version and exit." << endl;
88     cout << endl;
89     cout << " -f Overwrite output gig file if it already exists." << endl;
90     cout << endl;
91     cout << " -r Recurse through all subdirs of provided input WAV dirs." << endl;
92     cout << endl;
93 schoenebeck 3991 cout << " --dry-run" << endl;
94     cout << endl;
95     cout << " Scan input sample (.wav) files, but exit before creating any .gig file." << endl;
96     cout << endl;
97 schoenebeck 3990 cout << " --regex-name1 PATTERN" << endl;
98     cout << endl;
99     cout << " Regular expression for overriding the NAME1 part of the input sample file name scheme." << endl;
100     cout << endl;
101     cout << " --regex-name2 PATTERN" << endl;
102     cout << endl;
103     cout << " Regular expression for overriding the NAME2 part of the input sample file name scheme." << endl;
104     cout << endl;
105     cout << " --regex-velocity-nr PATTERN" << endl;
106     cout << endl;
107     cout << " Regular expression for overriding the VELOCITY_NR part of the input sample file name scheme." << endl;
108     cout << endl;
109     cout << " --regex-note-nr PATTERN" << endl;
110     cout << endl;
111     cout << " Regular expression for overriding the NOTE_NR part of the input sample file name scheme." << endl;
112     cout << endl;
113     cout << " --regex-note-name PATTERN" << endl;
114     cout << endl;
115     cout << " Regular expression for overriding the NOTE_NAME part of the input sample file name scheme." << endl;
116     cout << endl;
117     cout << "Read 'man wav2gig' for detailed help." << endl;
118     cout << endl;
119 schoenebeck 3980 }
120    
121     static bool beginsWith(const string& haystack, const string& needle) {
122     return haystack.substr(0, needle.size()) == needle;
123     }
124    
125     static bool endsWith(const string& haystack, const string& needle) {
126     return haystack.substr(haystack.size() - needle.size(), needle.size()) == needle;
127     }
128    
129 schoenebeck 3990 static string tokenByRegExGroup(const string& haystack, const string& pattern,
130     size_t group = 1)
131     {
132     regex rx(pattern);
133     smatch m;
134     regex_search(haystack, m, rx);
135     return (m.size() <= group) ? (string) "" : (string) m[group];
136     }
137    
138 schoenebeck 3980 static bool fileExists(const string& filename) {
139     FILE* hFile = fopen(filename.c_str(), "r");
140     if (!hFile) return false;
141     fclose(hFile);
142     return true;
143     }
144    
145     static bool isDir(const string& dirname) {
146     struct stat sb;
147     return (stat(dirname.c_str(), &sb) == 0) && S_ISDIR(sb.st_mode);
148     }
149    
150     static bool isRegularFile(const string& filename) {
151     struct stat sb;
152     return (stat(filename.c_str(), &sb) == 0) && S_ISREG(sb.st_mode);
153     }
154    
155     // this could also be replaced by fopen(name, "w") to simply truncate the file to zero
156     static void deleteFile(const string& filename) {
157     #if defined(WIN32)
158     DeleteFile(filename.c_str());
159     #else
160     unlink(filename.c_str());
161     #endif
162     }
163    
164     static bool isGigFileName(const string& filename) {
165     return endsWith(filename, ".gig") || endsWith(filename, ".GIG");
166     }
167    
168     static bool isWavFileName(const string& filename) {
169     return endsWith(filename, ".wav") || endsWith(filename, ".WAV");
170     }
171    
172     static bool isValidWavFile(const string& filename) {
173     SF_INFO info;
174     info.format = 0;
175     SNDFILE* hFile = sf_open(filename.c_str(), SFM_READ, &info);
176     if (!hFile) {
177     cerr << "Could not open input wav file \"" << filename << "\"" << endl;
178     return false;
179     }
180     sf_close(hFile);
181     switch (info.format & 0xff) {
182     case SF_FORMAT_PCM_S8:
183     case SF_FORMAT_PCM_16:
184     case SF_FORMAT_PCM_U8:
185     case SF_FORMAT_PCM_24:
186     case SF_FORMAT_PCM_32:
187     case SF_FORMAT_FLOAT:
188     case SF_FORMAT_DOUBLE:
189     return true;
190     default:
191     cerr << "Format of input wav file \"" << filename << "\" not supported!" << endl;
192     return false;
193     }
194     return false;
195     }
196    
197 schoenebeck 3990 struct FilenameRegExPatterns {
198     string name1;
199     string name2;
200     string velocityNr;
201     string noteNr;
202     string noteName;
203     };
204    
205 schoenebeck 3980 static void collectWavFilesOfDir(set<string>& result, string path, bool bRecurse, bool* pbError = NULL) {
206     DIR* d = opendir(path.c_str());
207     if (!d) {
208     if (pbError) *pbError = true;
209     cerr << strerror(errno) << " : '" << path << "'" << endl;
210     return;
211     }
212    
213     for (struct dirent* e = readdir(d); e; e = readdir(d)) {
214     if (string(e->d_name) == "." || string(e->d_name) == "..")
215     continue;
216    
217     const string fullName = path + DIR_SEPARATOR + e->d_name;
218    
219     struct stat s;
220     if (stat(fullName.c_str(), &s)) {
221     if (pbError) *pbError = true;
222     cerr << strerror(errno) << " : '" << fullName << "'" << endl;
223     continue;
224     }
225    
226     if (S_ISREG(s.st_mode) && isWavFileName(fullName) && isValidWavFile(fullName)) {
227     result.insert(fullName);
228     } else if (S_ISDIR(s.st_mode) && bRecurse) {
229     collectWavFilesOfDir(result, fullName, bRecurse, pbError);
230     }
231     }
232    
233     closedir(d);
234     }
235    
236     static void collectWavFiles(set<string>& result, string path, bool bRecurse, bool* pbError = NULL) {
237     struct stat s;
238     if (stat(path.c_str(), &s)) {
239     if (pbError) *pbError = true;
240     cerr << strerror(errno) << " : '" << path << "'" << endl;
241     return;
242     }
243     if (S_ISREG(s.st_mode) && isWavFileName(path) && isValidWavFile(path)) {
244     result.insert(path);
245     } else if (S_ISDIR(s.st_mode)) {
246     collectWavFilesOfDir(result, path, bRecurse, pbError);
247     } else {
248     if (pbError) *pbError = true;
249     cerr << "Neither a regular (.wav) file nor directory : '" << path << "'" << endl;
250     }
251     }
252    
253     struct WavInfo {
254     string fileName;
255     int note;
256     int velocity;
257     SF_INFO sfinfo;
258     string noteName;
259     string name1;
260     string name2;
261     SF_INSTRUMENT sfinst;
262     bool hasSfInst;
263    
264     bool isStereo() const { return sfinfo.channels == 2; }
265    
266     string outputSampleName() const {
267     return name1 + "_" + noteName + "_" + ToString(velocity);
268     }
269    
270     void assertValid() const {
271     if (note < 0 || note > 127) {
272     cerr << "ERROR: note number " << note << " of \"" << fileName << "\" is invalid!" << endl;
273     exit(EXIT_FAILURE);
274     }
275     if (velocity < 0 || velocity > 127) {
276     cerr << "ERROR: velocity number " << velocity << " of \"" << fileName << "\" is invalid!" << endl;
277     exit(EXIT_FAILURE);
278     }
279     }
280     };
281    
282     class WavRegion : public map<int,WavInfo> {
283     public:
284     typedef map<int,WavInfo> base_t;
285    
286     // WavRegion () :
287     // map<int,WavInfo>()
288     // {
289     // }
290     //
291     // WavRegion (const WavRegion& x) :
292     // map<int,WavInfo>(x)
293     // {
294     // }
295     //
296     // WavRegion& operator= (const WavRegion& x) {
297     // base_t::operator=(x);
298     // return *this;
299     // }
300    
301     bool isStereo() const {
302     for (const auto& it : *this)
303     if (it.second.isStereo())
304     return true;
305     return false;
306     }
307     };
308    
309     typedef map<int,WavRegion> WavInstrument;
310    
311 schoenebeck 3990 static WavInfo getWavInfo(string filename,
312     const FilenameRegExPatterns& patterns)
313     {
314 schoenebeck 3980 WavInfo wav;
315     wav.fileName = filename;
316     wav.sfinfo = {};
317     {
318     SNDFILE* hFile = sf_open(filename.c_str(), SFM_READ, &wav.sfinfo);
319     if (!hFile) {
320     cerr << "Could not open input wav file \"" << filename << "\"" << endl;
321     exit(EXIT_FAILURE);
322     }
323     wav.hasSfInst = (sf_command(hFile, SFC_GET_INSTRUMENT,
324     &wav.sfinst, sizeof(wav.sfinst)) != SF_FALSE);
325     sf_close(hFile);
326     switch (wav.sfinfo.channels) {
327     case 1:
328     case 2:
329     break;
330     default:
331     cerr << int(wav.sfinfo.channels) << " audio channels in WAV file \"" << filename << "\"; this is not supported!" << endl;
332     exit(EXIT_FAILURE);
333     }
334     }
335     {
336 schoenebeck 3990 wav.name1 = tokenByRegExGroup(filename, patterns.name1);
337     if (wav.name1.empty()) {
338     cerr << "Unexpected file name format: \"" << filename
339     << "\" for 'name1' RegEx pattern \"" << patterns.name1
340     << "\" !" << endl;
341 schoenebeck 3980 exit(EXIT_FAILURE);
342     }
343 schoenebeck 3990 wav.name2 = tokenByRegExGroup(filename, patterns.name2);
344     if (wav.name2.empty()) {
345     cerr << "Unexpected file name format: \"" << filename
346     << "\" for 'name2' RegEx pattern \"" << patterns.name2
347     << "\" !" << endl;
348     exit(EXIT_FAILURE);
349     }
350     string sVelocity = tokenByRegExGroup(filename, patterns.velocityNr);
351     if (sVelocity.empty()) {
352     cerr << "Unexpected file name format: \"" << filename
353     << "\" for 'velocity-nr' RegEx pattern \"" << patterns.velocityNr
354     << "\" !" << endl;
355     exit(EXIT_FAILURE);
356     }
357 schoenebeck 3980 wav.velocity = atoi(sVelocity.c_str());
358 schoenebeck 3990 string sNoteNr = tokenByRegExGroup(filename, patterns.noteNr);
359     if (sNoteNr.empty()) {
360     cerr << "Unexpected file name format: \"" << filename
361     << "\" for 'note-nr' RegEx pattern \"" << patterns.noteNr
362     << "\" !" << endl;
363     exit(EXIT_FAILURE);
364     }
365     wav.note = atoi(sNoteNr.c_str());
366     wav.noteName = tokenByRegExGroup(filename, patterns.noteName);
367     if (wav.noteName.empty()) {
368     cerr << "Unexpected file name format: \"" << filename
369     << "\" for 'note-name' RegEx pattern \"" << patterns.noteName
370     << "\" !" << endl;
371     exit(EXIT_FAILURE);
372     }
373 schoenebeck 3980 }
374     return wav;
375     }
376    
377     inline int getDimensionIndex(gig::Region* region, gig::dimension_t type) {
378     for (int d = 0; d < region->Dimensions; ++d)
379     if (region->pDimensionDefinitions[d].dimension == type)
380     return d;
381     return -1;
382     }
383    
384     static gig::Sample* createSample(gig::File* gig, WavInfo* wav) {
385     gig::Sample* s = gig->AddSample();
386    
387     s->pInfo->Name = wav->outputSampleName();
388     s->Channels = wav->sfinfo.channels;
389     s->SamplesPerSecond = wav->sfinfo.samplerate;
390    
391     switch (wav->sfinfo.format & 0xff) {
392     case SF_FORMAT_PCM_S8:
393     case SF_FORMAT_PCM_16:
394     case SF_FORMAT_PCM_U8:
395     s->BitDepth = 16;
396     break;
397     case SF_FORMAT_PCM_24:
398     case SF_FORMAT_PCM_32:
399     case SF_FORMAT_FLOAT:
400     case SF_FORMAT_DOUBLE:
401     s->BitDepth = 24;
402     break;
403     default:
404     throw gig::Exception("format not supported");
405     }
406    
407     s->FrameSize = s->Channels * s->BitDepth / 8;
408     if (wav->hasSfInst) {
409     s->MIDIUnityNote = wav->sfinst.basenote;
410     s->FineTune = wav->sfinst.detune;
411     if (wav->sfinst.loop_count && wav->sfinst.loops[0].mode != SF_LOOP_NONE) {
412     s->Loops = 1;
413     switch (wav->sfinst.loops[0].mode) {
414     case SF_LOOP_FORWARD:
415     s->LoopType = gig::loop_type_normal;
416     break;
417     case SF_LOOP_BACKWARD:
418     s->LoopType = gig::loop_type_backward;
419     break;
420     case SF_LOOP_ALTERNATING:
421     s->LoopType = gig::loop_type_bidirectional;
422     break;
423     }
424     s->LoopStart = wav->sfinst.loops[0].start;
425     s->LoopEnd = wav->sfinst.loops[0].end;
426     s->LoopPlayCount = wav->sfinst.loops[0].count;
427     s->LoopSize = s->LoopEnd - s->LoopStart + 1;
428     }
429 schoenebeck 3989 } else {
430     s->MIDIUnityNote = wav->note;
431 schoenebeck 3980 }
432    
433     // schedule for resize (will be performed when gig->Save() is called)
434     s->Resize(wav->sfinfo.frames);
435    
436     return s;
437     }
438    
439     int main(int argc, char *argv[]) {
440     bool bForce = false;
441     bool bRecursive = false;
442 schoenebeck 3991 bool bDryRun = false;
443 schoenebeck 3990 FilenameRegExPatterns patterns = {
444     // name 1 (e.g. "BSTEIN18")
445     .name1 = "^([^-]+) - [^-]+ - [^-]+ - [^-]+ - [^.]+",
446     // name 2 (e.g. "noname")
447     .name2 = "^[^-]+ - ([^-]+) - [^-]+ - [^-]+ - [^.]+",
448     // velocity value (e.g. "18")
449     .velocityNr = "^[^-]+ - [^-]+ - ([^-]+) - [^-]+ - [^.]+",
450     // note number (e.g. "021")
451     .noteNr = "^[^-]+ - [^-]+ - [^-]+ - ([^-]+) - [^.]+",
452     // note name (e.g. "a-1")
453     .noteName = "^[^-]+ - [^-]+ - [^-]+ - [^-]+ - ([^.]+)",
454     };
455 schoenebeck 3980
456     // validate & parse arguments provided to this program
457     int iArg;
458     for (iArg = 1; iArg < argc; ++iArg) {
459     const string opt = argv[iArg];
460 schoenebeck 3990 const string nextOpt = (iArg + 1 < argc) ? argv[iArg + 1] : "";
461 schoenebeck 3980 if (opt == "--") { // common for all command line tools: separator between initial option arguments and subsequent file arguments
462     iArg++;
463     break;
464     }
465     if (opt.substr(0, 1) != "-") break;
466    
467     if (opt == "-v") {
468     printVersion();
469     return EXIT_SUCCESS;
470     } else if (opt == "-f") {
471     bForce = true;
472     } else if (opt == "-r") {
473     bRecursive = true;
474 schoenebeck 3991 } else if (opt == "--dry-run") {
475     bDryRun = true;
476 schoenebeck 3990 } else if (opt == "--regex-name1") {
477     if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
478     cerr << "Missing argument for option '" << opt << "'" << endl;
479     return EXIT_FAILURE;
480     }
481     patterns.name1 = nextOpt;
482     } else if (opt == "--regex-name2") {
483     if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
484     cerr << "Missing argument for option '" << opt << "'" << endl;
485     return EXIT_FAILURE;
486     }
487     patterns.name2 = nextOpt;
488     } else if (opt == "--regex-velocity-nr") {
489     if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
490     cerr << "Missing argument for option '" << opt << "'" << endl;
491     return EXIT_FAILURE;
492     }
493     patterns.velocityNr = nextOpt;
494     } else if (opt == "--regex-note-nr") {
495     if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
496     cerr << "Missing argument for option '" << opt << "'" << endl;
497     return EXIT_FAILURE;
498     }
499     patterns.noteNr = nextOpt;
500     } else if (opt == "--regex-note-name") {
501     if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
502     cerr << "Missing argument for option '" << opt << "'" << endl;
503     return EXIT_FAILURE;
504     }
505     patterns.noteName = nextOpt;
506 schoenebeck 3980 } else {
507     cerr << "Unknown option '" << opt << "'" << endl;
508     cerr << endl;
509     printUsage();
510     return EXIT_FAILURE;
511     }
512     }
513     if (argc < 3) {
514     printUsage();
515     return EXIT_FAILURE;
516     }
517    
518     set<string> inNames; // may be file names and/or dir names
519     string outFileName;
520    
521     // all options have been processed, all subsequent args should be file/dir arguments
522     for (int i = 0; iArg < argc; ++iArg, ++i) {
523     if (i == 0) {
524     outFileName = argv[iArg];
525     } else {
526     inNames.insert(argv[iArg]);
527     }
528     }
529     if (outFileName.empty()) {
530     cerr << "You must provide one output file (.gig format)!" << endl;
531     return EXIT_FAILURE;
532     }
533     if (inNames.empty()) {
534     cerr << "You must provide at least one input WAV file or directory!" << endl;
535     return EXIT_FAILURE;
536     }
537     if (!isGigFileName(outFileName)) {
538     cerr << "Provided output file name should end with \".gig\"!" << endl;
539     return EXIT_FAILURE;
540     }
541    
542     // now collect the actual list of input WAV files
543     set<string> wavFileNames;
544     cout << "Scanning for input WAV files ... " << flush;
545     for (set<string>::const_iterator it = inNames.begin();
546     it != inNames.end(); ++it)
547     {
548     bool error = false;
549     collectWavFiles(wavFileNames, *it, bRecursive, &error);
550     if (error) return EXIT_FAILURE;
551     }
552     if (wavFileNames.empty()) {
553     cerr << "No input WAV file provided (or found)!" << endl;
554     return EXIT_FAILURE;
555     }
556     cout << "(" << int(wavFileNames.size()) << " found).\n";
557    
558     // check if output file already exists
559 schoenebeck 3991 if (fileExists(outFileName) && !bDryRun) {
560 schoenebeck 3980 if (bForce) deleteFile(outFileName);
561     else {
562     cerr << "Output file '" << outFileName << "' already exists. Use -f to overwrite it." << endl;
563     return EXIT_FAILURE;
564     }
565     }
566    
567     // order all input wav files into regions and velocity splits
568     WavInstrument wavInstrument;
569     cout << "Preprocessing input WAV files by their names ... " << flush;
570     for (set<string>::const_iterator it = wavFileNames.begin();
571     it != wavFileNames.end(); ++it)
572     {
573 schoenebeck 3990 WavInfo wavInfo = getWavInfo(*it, patterns);
574 schoenebeck 3980 wavInfo.assertValid(); // make sure collected informations are OK
575     if (wavInstrument[wavInfo.note].count(wavInfo.velocity)) {
576     cerr << "Velocity conflict between file '" << wavInfo.fileName
577     << "' and file '" << wavInstrument[wavInfo.note][wavInfo.velocity].fileName << "'!" << endl;
578     return EXIT_FAILURE;
579     }
580     wavInstrument[wavInfo.note][wavInfo.velocity] = wavInfo;
581     }
582     if (wavInstrument.empty()) {
583     cerr << "After sorting the WAV files around, there is no single WAV left to create a GIG file with!" << endl;
584     return EXIT_FAILURE;
585     }
586     cout << "OK\n";
587    
588 schoenebeck 3991 if (bDryRun)
589     return EXIT_SUCCESS;
590    
591 schoenebeck 3980 // create and assemble a new .gig file as output
592     try {
593     cout << "Creating new gig file and one new gig instrument ... " << flush;
594    
595     // start with an empty .gig file
596     gig::File gig;
597    
598     gig::Instrument* instr = gig.AddInstrument();
599     instr->pInfo->Name = "Unnamed by wav2gig";
600    
601     cout << "OK\n";
602    
603     map<gig::Sample*,WavInfo> queuedSamples;
604    
605     cout << "Assembling new gig instrument with interpreted multi sample structure ... " << flush;
606     for (auto& itWavRgn : wavInstrument) {
607     const int note = itWavRgn.first;
608     WavRegion& wavRgn = itWavRgn.second;
609    
610     gig::Region* gigRegion = instr->AddRegion();
611     gigRegion->SetKeyRange(note/*low*/, note/*high*/);
612    
613     if (wavRgn.isStereo()) {
614     gig::dimension_def_t dim;
615     dim.dimension = gig::dimension_samplechannel;
616     dim.bits = 1; // 2^(1) = 2
617     dim.zones = 2; // stereo = 2 audio channels = 2 split zones
618     gigRegion->AddDimension(&dim);
619     }
620    
621     if (wavRgn.size() > 1) {
622     gig::dimension_def_t dim;
623     dim.dimension = gig::dimension_velocity;
624     dim.zones = wavRgn.size();
625     // Find the number of bits required to hold the
626     // specified amount of zones.
627     int zoneBits = dim.zones - 1;
628     for (dim.bits = 0; zoneBits > 1; dim.bits += 2, zoneBits >>= 2);
629     dim.bits += zoneBits;
630     gigRegion->AddDimension(&dim);
631     }
632    
633     const int iStereoDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_samplechannel);
634     const int iVelocityDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_velocity);
635    
636     int iVelocitySplitZone = 0;
637     for (auto& itWav : wavRgn) {
638     const int velocity = itWav.first;
639     WavInfo& wav = itWav.second;
640     gig::Sample* gigSample = createSample(&gig, &wav);
641     queuedSamples[gigSample] = wav;
642    
643     uint8_t iDimBits[8] = {};
644    
645     for (int iAudioChannel = 0; iAudioChannel < (wavRgn.isStereo() ? 2 : 1); ++iAudioChannel) {
646    
647     // if region has velocity splits, select the respective velocity split zone
648     if (wavRgn.size() > 1) {
649     if (iVelocityDimensionIndex < 0)
650     throw gig::Exception("Could not resolve velocity dimension index");
651     iDimBits[iVelocityDimensionIndex] = iVelocitySplitZone;
652     }
653    
654     // select dimension bit for this stereo dimension split
655     if (iAudioChannel > 0) {
656     if (iStereoDimensionIndex < 0)
657     throw gig::Exception("Could not resolve stereo dimension index");
658     iDimBits[iStereoDimensionIndex] = 1;
659     }
660    
661     gig::DimensionRegion* dimRgn = gigRegion->GetDimensionRegionByBit(iDimBits);
662     if (!dimRgn)
663     throw gig::Exception("Internal error: Could not resolve Dimension Region");
664    
665     // if this is a velocity split, apply the precise velocity split range values
666     if (wavRgn.size() > 1) {
667     dimRgn->VelocityUpperLimit = velocity; // gig v2
668     dimRgn->DimensionUpperLimits[iVelocityDimensionIndex] = velocity; // gig v3 and above
669     }
670    
671     dimRgn->pSample = gigSample;
672     if (gigSample) {
673     dimRgn->UnityNote = gigSample->MIDIUnityNote;
674     if (gigSample->Loops) {
675     DLS::sample_loop_t loop;
676     loop.Size = sizeof(loop);
677     loop.LoopType = gigSample->LoopType;
678     loop.LoopStart = gigSample->LoopStart;
679     loop.LoopLength = gigSample->LoopEnd - gigSample->LoopStart;
680     dimRgn->AddSampleLoop(&loop);
681     }
682     }
683    
684     dimRgn->FineTune = gigSample->FineTune;
685     }
686    
687     iVelocitySplitZone++;
688     }
689     }
690     cout << "OK\n";
691    
692     cout << "Saving initial gig file layout ... " << flush;
693     // save result to disk (as .gig file)
694     gig.Save(outFileName);
695     cout << "OK\n";
696    
697     cout << "Copying audio sample data ... " << flush;
698     // finally write the actual wav sample data directly to the created gig file
699     for (auto& itSmpl : queuedSamples) {
700     gig::Sample* gigSample = itSmpl.first;
701     WavInfo& wav = itSmpl.second;
702    
703     SF_INFO info = {};
704     SNDFILE* hFile = sf_open(wav.fileName.c_str(), SFM_READ, &info);
705     sf_command(hFile, SFC_SET_SCALE_FLOAT_INT_READ, 0, SF_TRUE);
706     if (!hFile) throw gig::Exception("could not open file");
707     // determine sample's bit depth
708     int bitdepth;
709     switch (info.format & 0xff) {
710     case SF_FORMAT_PCM_S8:
711     case SF_FORMAT_PCM_16:
712     case SF_FORMAT_PCM_U8:
713     bitdepth = 16;
714     break;
715     case SF_FORMAT_PCM_24:
716     case SF_FORMAT_PCM_32:
717     case SF_FORMAT_FLOAT:
718     case SF_FORMAT_DOUBLE:
719     bitdepth = 24;
720     break;
721     default:
722     sf_close(hFile); // close sound file
723     throw gig::Exception("format not supported");
724     }
725    
726     const int bufsize = 10000;
727     switch (bitdepth) {
728     case 16: {
729     short* buffer = new short[bufsize * info.channels];
730     sf_count_t cnt = info.frames;
731     while (cnt) {
732     // libsndfile does the conversion for us (if needed)
733     int n = sf_readf_short(hFile, buffer, bufsize);
734     // write from buffer directly (physically) into .gig file
735     gigSample->Write(buffer, n);
736     cnt -= n;
737     }
738     delete[] buffer;
739     break;
740     }
741     case 24: {
742     int* srcbuf = new int[bufsize * info.channels];
743     uint8_t* dstbuf = new uint8_t[bufsize * 3 * info.channels];
744     sf_count_t cnt = info.frames;
745     while (cnt) {
746     // libsndfile returns 32 bits, convert to 24
747     int n = sf_readf_int(hFile, srcbuf, bufsize);
748     int j = 0;
749     for (int i = 0 ; i < n * info.channels ; i++) {
750     dstbuf[j++] = srcbuf[i] >> 8;
751     dstbuf[j++] = srcbuf[i] >> 16;
752     dstbuf[j++] = srcbuf[i] >> 24;
753     }
754     // write from buffer directly (physically) into .gig file
755     gigSample->Write(dstbuf, n);
756     cnt -= n;
757     }
758     delete[] srcbuf;
759     delete[] dstbuf;
760     break;
761     }
762     }
763     sf_close(hFile);
764     }
765     cout << "OK\n";
766    
767     } catch (RIFF::Exception e) {
768     cerr << "Failed generating output file:" << endl;
769     e.PrintMessage();
770     return EXIT_FAILURE;
771     } catch (...) {
772     cerr << "Unknown exception while trying to assemble output file." << endl;
773     return EXIT_FAILURE;
774     }
775    
776     return EXIT_SUCCESS;
777     }

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC