/[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 3994 - (hide annotations) (download)
Thu Sep 2 16:22:36 2021 UTC (2 years, 7 months ago) by schoenebeck
File size: 30379 byte(s)
* src/tools/wav2gig.cpp: fix default RegEx patterns to ignore leading path.

* src/tools/wav2gig.cpp: fix --regex-* argument parsing.

* src/tools/wav2gig.cpp: print source of parameters if --verbose is used.

* man/wav2gig.1.in: point out where samples' root note is taken from.

* Bumped version (4.3.0.svn34).

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

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC