/[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 3991 - (show annotations) (download)
Tue Aug 31 15:58:57 2021 UTC (2 years, 7 months ago) by schoenebeck
File size: 28437 byte(s)
* wav2gig tool: add --dry-run option.

* Bumped version (4.3.0.svn32).

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 << " --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 }
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 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 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 struct FilenameRegExPatterns {
198 string name1;
199 string name2;
200 string velocityNr;
201 string noteNr;
202 string noteName;
203 };
204
205 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 static WavInfo getWavInfo(string filename,
312 const FilenameRegExPatterns& patterns)
313 {
314 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 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 exit(EXIT_FAILURE);
342 }
343 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 wav.velocity = atoi(sVelocity.c_str());
358 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 }
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 } else {
430 s->MIDIUnityNote = wav->note;
431 }
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 bool bDryRun = false;
443 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
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 const string nextOpt = (iArg + 1 < argc) ? argv[iArg + 1] : "";
461 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 } else if (opt == "--dry-run") {
475 bDryRun = true;
476 } 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 } 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 if (fileExists(outFileName) && !bDryRun) {
560 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 WavInfo wavInfo = getWavInfo(*it, patterns);
574 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 if (bDryRun)
589 return EXIT_SUCCESS;
590
591 // 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