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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3990 - (hide annotations) (download)
Mon Aug 30 20:14:15 2021 UTC (17 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 schoenebeck 3980 /***************************************************************************
2     * *
3     * Copyright (C) 2021 Christian Schoenebeck *
4     * <cuse@users.sourceforge.net> *
5     * *
6     * This program is part of libgig. *
7     * *
8     * This program is free software; you can redistribute it and/or modify *
9     * it under the terms of the GNU General Public License as published by *
10     * the Free Software Foundation; either version 2 of the License, or *
11     * (at your option) any later version. *
12     * *
13     * This program is distributed in the hope that it will be useful, *
14     * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16     * GNU General Public License for more details. *
17     * *
18     * You should have received a copy of the GNU General Public License *
19     * along with this program; if not, write to the Free Software *
20     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
21     * MA 02111-1307 USA *
22     ***************************************************************************/
23    
24     #ifdef HAVE_CONFIG_H
25     # include <config.h>
26     #endif
27 schoenebeck 3981 #include <errno.h>
28     #include <sys/stat.h>
29     #include <dirent.h>
30     #include <string.h>
31 schoenebeck 3980 #include <iostream>
32     #include <cstdlib>
33     #include <string>
34     #include <set>
35     #include <map>
36 schoenebeck 3981 #include <regex>
37 schoenebeck 3980
38     #if !defined(WIN32)
39     # include <unistd.h>
40     #endif
41    
42     #include "../gig.h"
43     #include "../helper.h" // for ToString()
44    
45     // only libsndfile is available for Windows, so we use that for writing the sound files
46     #ifdef WIN32
47     # define HAVE_SNDFILE 1
48     #endif // WIN32
49    
50     // abort compilation here if libsndfile is not available
51     #if !HAVE_SNDFILE
52     # error "It seems as if libsndfile is not available!"
53     # error "(HAVE_SNDFILE is false)"
54     #endif
55    
56     #if HAVE_SNDFILE
57     # ifdef LIBSNDFILE_HEADER_FILE
58     # include LIBSNDFILE_HEADER_FILE(sndfile.h)
59     # else
60     # include <sndfile.h>
61     # endif
62     #endif // HAVE_SNDFILE
63    
64     #ifdef WIN32
65     # define DIR_SEPARATOR '\\'
66     #else
67     # define DIR_SEPARATOR '/'
68     #endif
69    
70     using namespace std;
71    
72     static string Revision() {
73     string s = "$Revision$";
74     return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword
75     }
76    
77     static void printVersion() {
78     cout << "wav2gig revision " << Revision() << endl;
79     cout << "using " << gig::libraryName() << " " << gig::libraryVersion() << endl;
80     }
81    
82     static void printUsage() {
83     cout << "wav2gig - Create GigaStudio file from a set of WAV files." << endl;
84     cout << endl;
85 schoenebeck 3990 cout << "Usage: wav2gig [OPTIONS] GIGFILE WAVFILEORDIR1 [ WAVFILEORDIR2 ... ]" << endl;
86 schoenebeck 3980 cout << endl;
87     cout << " -v Print version and exit." << endl;
88     cout << endl;
89     cout << " -f Overwrite output gig file if it already exists." << endl;
90     cout << endl;
91     cout << " -r Recurse through all subdirs of provided input WAV dirs." << endl;
92     cout << endl;
93 schoenebeck 3990 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 schoenebeck 3980 }
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 schoenebeck 3990 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 schoenebeck 3980 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 schoenebeck 3990 struct FilenameRegExPatterns {
194     string name1;
195     string name2;
196     string velocityNr;
197     string noteNr;
198     string noteName;
199     };
200    
201 schoenebeck 3980 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 schoenebeck 3990 static WavInfo getWavInfo(string filename,
308     const FilenameRegExPatterns& patterns)
309     {
310 schoenebeck 3980 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 schoenebeck 3990 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 schoenebeck 3980 exit(EXIT_FAILURE);
338     }
339 schoenebeck 3990 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 schoenebeck 3980 wav.velocity = atoi(sVelocity.c_str());
354 schoenebeck 3990 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 schoenebeck 3980 }
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 schoenebeck 3989 } else {
426     s->MIDIUnityNote = wav->note;
427 schoenebeck 3980 }
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 schoenebeck 3990 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 schoenebeck 3980
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 schoenebeck 3990 const string nextOpt = (iArg + 1 < argc) ? argv[iArg + 1] : "";
456 schoenebeck 3980 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 schoenebeck 3990 } 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 schoenebeck 3980 } 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 schoenebeck 3990 WavInfo wavInfo = getWavInfo(*it, patterns);
567 schoenebeck 3980 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