/[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 3993 - (show annotations) (download)
Thu Sep 2 15:34:11 2021 UTC (2 years, 7 months ago) by schoenebeck
File size: 29773 byte(s)
wav2gig: minor output format cosmetics

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 << 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 Internal Content]" << endl;
431 cout << "\tFine Tune " << s->FineTune << endl;
432 }
433 if (wav->sfinst.loop_count && wav->sfinst.loops[0].mode != SF_LOOP_NONE) {
434 s->Loops = 1;
435 if (bVerbose) cout << "\t";
436 switch (wav->sfinst.loops[0].mode) {
437 case SF_LOOP_FORWARD:
438 s->LoopType = gig::loop_type_normal;
439 if (bVerbose) cout << "Normal ";
440 break;
441 case SF_LOOP_BACKWARD:
442 s->LoopType = gig::loop_type_backward;
443 if (bVerbose) cout << "Backward ";
444 break;
445 case SF_LOOP_ALTERNATING:
446 s->LoopType = gig::loop_type_bidirectional;
447 if (bVerbose) cout << "Pingpong ";
448 break;
449 }
450 s->LoopStart = wav->sfinst.loops[0].start;
451 s->LoopEnd = wav->sfinst.loops[0].end;
452 s->LoopPlayCount = wav->sfinst.loops[0].count;
453 s->LoopSize = s->LoopEnd - s->LoopStart + 1;
454 if (bVerbose) {
455 cout << "Loop " << s->LoopPlayCount << " times from "
456 << s->LoopStart << " to " << s->LoopEnd
457 << " (Size " << s->LoopSize << ")" << endl;
458 }
459 }
460 } else {
461 s->MIDIUnityNote = wav->note;
462 cout << "\tRoot Note " << s->MIDIUnityNote << " [Source: .WAV Filename Schema]" << endl;
463 }
464
465 // schedule for resize (will be performed when gig->Save() is called)
466 s->Resize(wav->sfinfo.frames);
467
468 return s;
469 }
470
471 int main(int argc, char *argv[]) {
472 bool bForce = false;
473 bool bRecursive = false;
474 bool bDryRun = false;
475 bool bVerbose = false;
476 FilenameRegExPatterns patterns = {
477 // name 1 (e.g. "BSTEIN18")
478 .name1 = "^([^-]+) - [^-]+ - [^-]+ - [^-]+ - [^.]+",
479 // name 2 (e.g. "noname")
480 .name2 = "^[^-]+ - ([^-]+) - [^-]+ - [^-]+ - [^.]+",
481 // velocity value (e.g. "18")
482 .velocityNr = "^[^-]+ - [^-]+ - ([^-]+) - [^-]+ - [^.]+",
483 // note number (e.g. "021")
484 .noteNr = "^[^-]+ - [^-]+ - [^-]+ - ([^-]+) - [^.]+",
485 // note name (e.g. "a-1")
486 .noteName = "^[^-]+ - [^-]+ - [^-]+ - [^-]+ - ([^.]+)",
487 };
488
489 // validate & parse arguments provided to this program
490 int iArg;
491 for (iArg = 1; iArg < argc; ++iArg) {
492 const string opt = argv[iArg];
493 const string nextOpt = (iArg + 1 < argc) ? argv[iArg + 1] : "";
494 if (opt == "--") { // common for all command line tools: separator between initial option arguments and subsequent file arguments
495 iArg++;
496 break;
497 }
498 if (opt.substr(0, 1) != "-") break;
499
500 if (opt == "-v") {
501 printVersion();
502 return EXIT_SUCCESS;
503 } else if (opt == "-f") {
504 bForce = true;
505 } else if (opt == "-r") {
506 bRecursive = true;
507 } else if (opt == "--dry-run") {
508 bDryRun = true;
509 } else if (opt == "--verbose") {
510 bVerbose = true;
511 } else if (opt == "--regex-name1") {
512 if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
513 cerr << "Missing argument for option '" << opt << "'" << endl;
514 return EXIT_FAILURE;
515 }
516 patterns.name1 = nextOpt;
517 } else if (opt == "--regex-name2") {
518 if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
519 cerr << "Missing argument for option '" << opt << "'" << endl;
520 return EXIT_FAILURE;
521 }
522 patterns.name2 = nextOpt;
523 } else if (opt == "--regex-velocity-nr") {
524 if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
525 cerr << "Missing argument for option '" << opt << "'" << endl;
526 return EXIT_FAILURE;
527 }
528 patterns.velocityNr = nextOpt;
529 } else if (opt == "--regex-note-nr") {
530 if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
531 cerr << "Missing argument for option '" << opt << "'" << endl;
532 return EXIT_FAILURE;
533 }
534 patterns.noteNr = nextOpt;
535 } else if (opt == "--regex-note-name") {
536 if (nextOpt.empty() || beginsWith(nextOpt, "-")) {
537 cerr << "Missing argument for option '" << opt << "'" << endl;
538 return EXIT_FAILURE;
539 }
540 patterns.noteName = nextOpt;
541 } else {
542 cerr << "Unknown option '" << opt << "'" << endl;
543 cerr << endl;
544 printUsage();
545 return EXIT_FAILURE;
546 }
547 }
548 if (argc < 3) {
549 printUsage();
550 return EXIT_FAILURE;
551 }
552
553 set<string> inNames; // may be file names and/or dir names
554 string outFileName;
555
556 // all options have been processed, all subsequent args should be file/dir arguments
557 for (int i = 0; iArg < argc; ++iArg, ++i) {
558 if (i == 0) {
559 outFileName = argv[iArg];
560 } else {
561 inNames.insert(argv[iArg]);
562 }
563 }
564 if (outFileName.empty()) {
565 cerr << "You must provide one output file (.gig format)!" << endl;
566 return EXIT_FAILURE;
567 }
568 if (inNames.empty()) {
569 cerr << "You must provide at least one input WAV file or directory!" << endl;
570 return EXIT_FAILURE;
571 }
572 if (!isGigFileName(outFileName)) {
573 cerr << "Provided output file name should end with \".gig\"!" << endl;
574 return EXIT_FAILURE;
575 }
576
577 // now collect the actual list of input WAV files
578 set<string> wavFileNames;
579 cout << "Scanning for input WAV files ... " << flush;
580 for (set<string>::const_iterator it = inNames.begin();
581 it != inNames.end(); ++it)
582 {
583 bool error = false;
584 collectWavFiles(wavFileNames, *it, bRecursive, &error);
585 if (error) return EXIT_FAILURE;
586 }
587 if (wavFileNames.empty()) {
588 cerr << "No input WAV file provided (or found)!" << endl;
589 return EXIT_FAILURE;
590 }
591 cout << "(" << int(wavFileNames.size()) << " found).\n";
592
593 // check if output file already exists
594 if (fileExists(outFileName) && !bDryRun) {
595 if (bForce) deleteFile(outFileName);
596 else {
597 cerr << "Output file '" << outFileName << "' already exists. Use -f to overwrite it." << endl;
598 return EXIT_FAILURE;
599 }
600 }
601
602 // order all input wav files into regions and velocity splits
603 WavInstrument wavInstrument;
604 cout << "Preprocessing input WAV files by their names ... " << flush;
605 for (set<string>::const_iterator it = wavFileNames.begin();
606 it != wavFileNames.end(); ++it)
607 {
608 WavInfo wavInfo = getWavInfo(*it, patterns);
609 wavInfo.assertValid(); // make sure collected informations are OK
610 if (wavInstrument[wavInfo.note].count(wavInfo.velocity)) {
611 cerr << "Velocity conflict between file '" << wavInfo.fileName
612 << "' and file '" << wavInstrument[wavInfo.note][wavInfo.velocity].fileName << "'!" << endl;
613 return EXIT_FAILURE;
614 }
615 wavInstrument[wavInfo.note][wavInfo.velocity] = wavInfo;
616 }
617 if (wavInstrument.empty()) {
618 cerr << "After sorting the WAV files around, there is no single WAV left to create a GIG file with!" << endl;
619 return EXIT_FAILURE;
620 }
621 cout << "OK\n";
622
623 // create and assemble a new .gig file as output
624 try {
625 cout << "Creating new gig file and one new gig instrument ... " << flush;
626
627 // start with an empty .gig file
628 gig::File gig;
629
630 gig::Instrument* instr = gig.AddInstrument();
631 instr->pInfo->Name = "Unnamed by wav2gig";
632
633 cout << "OK\n";
634
635 map<gig::Sample*,WavInfo> queuedSamples;
636
637 cout << "Assembling new gig instrument with interpreted multi sample structure ... " << flush;
638 for (auto& itWavRgn : wavInstrument) {
639 const int note = itWavRgn.first;
640 WavRegion& wavRgn = itWavRgn.second;
641
642 gig::Region* gigRegion = instr->AddRegion();
643 gigRegion->SetKeyRange(note/*low*/, note/*high*/);
644
645 if (wavRgn.isStereo()) {
646 gig::dimension_def_t dim;
647 dim.dimension = gig::dimension_samplechannel;
648 dim.bits = 1; // 2^(1) = 2
649 dim.zones = 2; // stereo = 2 audio channels = 2 split zones
650 gigRegion->AddDimension(&dim);
651 }
652
653 if (wavRgn.size() > 1) {
654 gig::dimension_def_t dim;
655 dim.dimension = gig::dimension_velocity;
656 dim.zones = wavRgn.size();
657 // Find the number of bits required to hold the
658 // specified amount of zones.
659 int zoneBits = dim.zones - 1;
660 for (dim.bits = 0; zoneBits > 1; dim.bits += 2, zoneBits >>= 2);
661 dim.bits += zoneBits;
662 gigRegion->AddDimension(&dim);
663 }
664
665 const int iStereoDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_samplechannel);
666 const int iVelocityDimensionIndex = getDimensionIndex(gigRegion, gig::dimension_velocity);
667
668 int iVelocitySplitZone = 0;
669 for (auto& itWav : wavRgn) {
670 const int velocity = itWav.first;
671 WavInfo& wav = itWav.second;
672 gig::Sample* gigSample = createSample(&gig, &wav, bVerbose);
673 queuedSamples[gigSample] = wav;
674
675 uint8_t iDimBits[8] = {};
676
677 for (int iAudioChannel = 0; iAudioChannel < (wavRgn.isStereo() ? 2 : 1); ++iAudioChannel) {
678
679 // if region has velocity splits, select the respective velocity split zone
680 if (wavRgn.size() > 1) {
681 if (iVelocityDimensionIndex < 0)
682 throw gig::Exception("Could not resolve velocity dimension index");
683 iDimBits[iVelocityDimensionIndex] = iVelocitySplitZone;
684 }
685
686 // select dimension bit for this stereo dimension split
687 if (iAudioChannel > 0) {
688 if (iStereoDimensionIndex < 0)
689 throw gig::Exception("Could not resolve stereo dimension index");
690 iDimBits[iStereoDimensionIndex] = 1;
691 }
692
693 gig::DimensionRegion* dimRgn = gigRegion->GetDimensionRegionByBit(iDimBits);
694 if (!dimRgn)
695 throw gig::Exception("Internal error: Could not resolve Dimension Region");
696
697 // if this is a velocity split, apply the precise velocity split range values
698 if (wavRgn.size() > 1) {
699 dimRgn->VelocityUpperLimit = velocity; // gig v2
700 dimRgn->DimensionUpperLimits[iVelocityDimensionIndex] = velocity; // gig v3 and above
701 }
702
703 dimRgn->pSample = gigSample;
704 if (gigSample) {
705 dimRgn->UnityNote = gigSample->MIDIUnityNote;
706 if (gigSample->Loops) {
707 DLS::sample_loop_t loop;
708 loop.Size = sizeof(loop);
709 loop.LoopType = gigSample->LoopType;
710 loop.LoopStart = gigSample->LoopStart;
711 loop.LoopLength = gigSample->LoopEnd - gigSample->LoopStart;
712 dimRgn->AddSampleLoop(&loop);
713 }
714 }
715
716 dimRgn->FineTune = gigSample->FineTune;
717 }
718
719 iVelocitySplitZone++;
720 }
721 }
722 cout << "OK\n";
723
724 if (bDryRun)
725 return EXIT_SUCCESS;
726
727 cout << "Saving initial gig file layout ... " << flush;
728 // save result to disk (as .gig file)
729 gig.Save(outFileName);
730 cout << "OK\n";
731
732 cout << "Copying audio sample data ... " << flush;
733 // finally write the actual wav sample data directly to the created gig file
734 for (auto& itSmpl : queuedSamples) {
735 gig::Sample* gigSample = itSmpl.first;
736 WavInfo& wav = itSmpl.second;
737
738 SF_INFO info = {};
739 SNDFILE* hFile = sf_open(wav.fileName.c_str(), SFM_READ, &info);
740 sf_command(hFile, SFC_SET_SCALE_FLOAT_INT_READ, 0, SF_TRUE);
741 if (!hFile) throw gig::Exception("could not open file");
742 // determine sample's bit depth
743 int bitdepth;
744 switch (info.format & 0xff) {
745 case SF_FORMAT_PCM_S8:
746 case SF_FORMAT_PCM_16:
747 case SF_FORMAT_PCM_U8:
748 bitdepth = 16;
749 break;
750 case SF_FORMAT_PCM_24:
751 case SF_FORMAT_PCM_32:
752 case SF_FORMAT_FLOAT:
753 case SF_FORMAT_DOUBLE:
754 bitdepth = 24;
755 break;
756 default:
757 sf_close(hFile); // close sound file
758 throw gig::Exception("format not supported");
759 }
760
761 const int bufsize = 10000;
762 switch (bitdepth) {
763 case 16: {
764 short* buffer = new short[bufsize * info.channels];
765 sf_count_t cnt = info.frames;
766 while (cnt) {
767 // libsndfile does the conversion for us (if needed)
768 int n = sf_readf_short(hFile, buffer, bufsize);
769 // write from buffer directly (physically) into .gig file
770 gigSample->Write(buffer, n);
771 cnt -= n;
772 }
773 delete[] buffer;
774 break;
775 }
776 case 24: {
777 int* srcbuf = new int[bufsize * info.channels];
778 uint8_t* dstbuf = new uint8_t[bufsize * 3 * info.channels];
779 sf_count_t cnt = info.frames;
780 while (cnt) {
781 // libsndfile returns 32 bits, convert to 24
782 int n = sf_readf_int(hFile, srcbuf, bufsize);
783 int j = 0;
784 for (int i = 0 ; i < n * info.channels ; i++) {
785 dstbuf[j++] = srcbuf[i] >> 8;
786 dstbuf[j++] = srcbuf[i] >> 16;
787 dstbuf[j++] = srcbuf[i] >> 24;
788 }
789 // write from buffer directly (physically) into .gig file
790 gigSample->Write(dstbuf, n);
791 cnt -= n;
792 }
793 delete[] srcbuf;
794 delete[] dstbuf;
795 break;
796 }
797 }
798 sf_close(hFile);
799 }
800 cout << "OK\n";
801
802 } catch (RIFF::Exception e) {
803 cerr << "Failed generating output file:" << endl;
804 e.PrintMessage();
805 return EXIT_FAILURE;
806 } catch (...) {
807 cerr << "Unknown exception while trying to assemble output file." << endl;
808 return EXIT_FAILURE;
809 }
810
811 return EXIT_SUCCESS;
812 }

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC