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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3994 - (show 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 /***************************************************************************
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 #include <errno.h>
28 #include <sys/stat.h>
29 #include <dirent.h>
30 #include <string.h>
31 #include <iostream>
32 #include <cstdlib>
33 #include <string>
34 #include <set>
35 #include <map>
36 #include <regex>
37
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 cout << "Usage: wav2gig [OPTIONS] GIGFILE WAVFILEORDIR1 [ WAVFILEORDIR2 ... ]" << endl;
86 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 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 cout << " --verbose" << endl;
98 cout << endl;
99 cout << " Increase amount of info being shown." << endl;
100 cout << endl;
101 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 }
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 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 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 struct FilenameRegExPatterns {
202 string name1;
203 string name2;
204 string velocityNr;
205 string noteNr;
206 string noteName;
207 };
208
209 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 static WavInfo getWavInfo(string filename,
316 const FilenameRegExPatterns& patterns)
317 {
318 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 wav.name1 = tokenByRegExGroup(filename, patterns.name1);
341 if (wav.name1.empty()) {
342 cerr << "Unexpected file name format \"" << filename
343 << "\" for 'name1' RegEx pattern \"" << patterns.name1
344 << "\" !" << endl;
345 exit(EXIT_FAILURE);
346 }
347 wav.name2 = tokenByRegExGroup(filename, patterns.name2);
348 if (wav.name2.empty()) {
349 cerr << "Unexpected file name format \"" << filename
350 << "\" 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 cerr << "Unexpected file name format \"" << filename
357 << "\" for 'velocity-nr' RegEx pattern \"" << patterns.velocityNr
358 << "\" !" << endl;
359 exit(EXIT_FAILURE);
360 }
361 wav.velocity = atoi(sVelocity.c_str());
362 string sNoteNr = tokenByRegExGroup(filename, patterns.noteNr);
363 if (sNoteNr.empty()) {
364 cerr << "Unexpected file name format \"" << filename
365 << "\" 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 cerr << "Unexpected file name format \"" << filename
373 << "\" for 'note-name' RegEx pattern \"" << patterns.noteName
374 << "\" !" << endl;
375 exit(EXIT_FAILURE);
376 }
377 }
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 static gig::Sample* createSample(gig::File* gig, WavInfo* wav, bool bVerbose) {
389 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
395 if (bVerbose) {
396 cout << "Add Sample [" << gig->CountSamples() << "] '"
397 << s->pInfo->Name << "' to gig file:" << endl;
398 }
399
400 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
418 if (bVerbose) {
419 cout << "\t" << s->BitDepth << " Bits, "
420 << s->SamplesPerSecond << " Hz, "
421 << s->Channels << " Channels"
422 << " [Source: .WAV File Content]" << endl;
423 }
424
425 if (wav->hasSfInst) {
426 s->MIDIUnityNote = wav->sfinst.basenote;
427 s->FineTune = wav->sfinst.detune;
428 if (bVerbose) {
429 cout << "\tRoot Note " << s->MIDIUnityNote
430 << " [Source: .WAV File Content]" << endl;
431 cout << "\tFine Tune " << s->FineTune
432 << " [Source: .WAV File Content]" << endl;
433 }
434 if (wav->sfinst.loop_count && wav->sfinst.loops[0].mode != SF_LOOP_NONE) {
435 s->Loops = 1;
436 if (bVerbose) cout << "\t";
437 switch (wav->sfinst.loops[0].mode) {
438 case SF_LOOP_FORWARD:
439 s->LoopType = gig::loop_type_normal;
440 if (bVerbose) cout << "Normal ";
441 break;
442 case SF_LOOP_BACKWARD:
443 s->LoopType = gig::loop_type_backward;
444 if (bVerbose) cout << "Backward ";
445 break;
446 case SF_LOOP_ALTERNATING:
447 s->LoopType = gig::loop_type_bidirectional;
448 if (bVerbose) cout << "Pingpong ";
449 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 if (bVerbose) {
456 cout << "Loop " << s->LoopPlayCount << " times from "
457 << s->LoopStart << " to " << s->LoopEnd
458 << " (Size " << s->LoopSize << ")" << endl;
459 }
460 }
461 } else {
462 s->MIDIUnityNote = wav->note;
463 cout << "\tRoot Note " << s->MIDIUnityNote << " [Source: .WAV Filename Schema]" << endl;
464 }
465 cout << "\tVelocity " << wav->velocity
466 << " [Source: .WAV Filename Schema]" << endl;
467 cout << "\tRegion " << wav->note
468 << " [Source: .WAV Filename Schema]" << endl;
469
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 bool bDryRun = false;
480 bool bVerbose = false;
481 // 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 FilenameRegExPatterns patterns = {
484 // name 1 (e.g. "BSTEIN18")
485 .name1 = R"RegEx(([^-\/\\]+) - [^-]+ - [^-]+ - [^-]+ - [^.]+)RegEx",
486 // name 2 (e.g. "noname")
487 .name2 = R"RegEx([^-\/\\]+ - ([^-]+) - [^-]+ - [^-]+ - [^.]+)RegEx",
488 // velocity value (e.g. "18")
489 .velocityNr = R"RegEx([^-\/\\]+ - [^-]+ - ([^-]+) - [^-]+ - [^.]+)RegEx",
490 // note number (e.g. "021")
491 .noteNr = R"RegEx([^-\/\\]+ - [^-]+ - [^-]+ - ([^-]+) - [^.]+)RegEx",
492 // note name (e.g. "a-1")
493 .noteName = R"RegEx([^-\/\\]+ - [^-]+ - [^-]+ - [^-]+ - ([^.]+))RegEx",
494 };
495
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 const string nextOpt = (iArg + 1 < argc) ? argv[iArg + 1] : "";
501 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 } else if (opt == "--dry-run") {
515 bDryRun = true;
516 } else if (opt == "--verbose") {
517 bVerbose = true;
518 } 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 ++iArg;
525 } 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 ++iArg;
532 } 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 ++iArg;
539 } 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 ++iArg;
546 } 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 ++iArg;
553 } 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 if (fileExists(outFileName) && !bDryRun) {
607 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 WavInfo wavInfo = getWavInfo(*it, patterns);
621 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 gig::Sample* gigSample = createSample(&gig, &wav, bVerbose);
685 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
736 if (bDryRun)
737 return EXIT_SUCCESS;
738
739 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