/[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 3990 - (show annotations) (download)
Mon Aug 30 20:14:15 2021 UTC (2 years, 7 months ago) by schoenebeck
File size: 28111 byte(s)
* wav2gig tool: allow overriding the input .wav file name scheme by
  custom regular expression passed as command line argument(s).

* Bumped version (4.3.0.svn31).

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

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC