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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3175 - (hide annotations) (download)
Thu May 11 11:34:19 2017 UTC (6 years, 11 months ago) by schoenebeck
File size: 32895 byte(s)
* Fixed potential crash in command line tools gig2stereo, korg2gig,
  korgdump and sf2extract.

1 schoenebeck 2685 /***************************************************************************
2     * *
3     * libgig - C++ cross-platform Gigasampler format file access library *
4     * *
5 schoenebeck 3175 * Copyright (C) 2003-2017 by Christian Schoenebeck *
6 schoenebeck 2685 * <cuse@users.sourceforge.net> *
7     * *
8     * This program is part of libgig. *
9     * *
10     * This program is free software; you can redistribute it and/or modify *
11     * it under the terms of the GNU General Public License as published by *
12     * the Free Software Foundation; either version 2 of the License, or *
13     * (at your option) any later version. *
14     * *
15     * This program is distributed in the hope that it will be useful, *
16     * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18     * GNU General Public License for more details. *
19     * *
20     * You should have received a copy of the GNU General Public License *
21     * along with this program; if not, write to the Free Software *
22     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
23     * MA 02111-1307 USA *
24     ***************************************************************************/
25    
26     #ifdef HAVE_CONFIG_H
27     # include <config.h>
28     #endif
29     #include <errno.h>
30     #include <sys/stat.h>
31     #include <dirent.h>
32     #include <string.h>
33     #include <iostream>
34     #include <cstdlib>
35     #include <string>
36     #include <set>
37     #include <vector>
38     #include <map>
39     #include <utility>
40    
41     #ifdef WIN32
42     # define DIR_SEPARATOR '\\'
43     #else
44     # define DIR_SEPARATOR '/'
45     #endif
46    
47     #include "../gig.h"
48    
49     using namespace std;
50    
51     static set<string> g_files;
52    
53     static void printUsage() {
54     cout << "gig2stereo - converts Gigasampler files from mono sample pairs to" << endl;
55     cout << " true stereo interleaved samples." << endl;
56     cout << endl;
57     cout << "Usage: gig2stereo [-r] [--keep] [--verbose [LEVEL]] [--force-replace] FILE_OR_DIR1 [ FILE_OR_DIR2 ... ]" << endl;
58     cout << " gig2stereo -v" << endl;
59     cout << endl;
60     cout << " --force-replace Replace all old mono references by the new stereo ones." << endl;
61     cout << endl;
62 schoenebeck 2693 cout << " --incompatible Also match sample pairs that seem to be incompatible." << endl;
63     cout << endl;
64 schoenebeck 2685 cout << " --keep Keep orphaned mono samples after conversion." << endl;
65     cout << endl;
66     cout << " -r Recurse through subdirectories." << endl;
67     cout << endl;
68     cout << " -v Print version and exit." << endl;
69     cout << endl;
70 schoenebeck 3048 cout << " --verbose Print additional information while converting." << endl;
71 schoenebeck 2685 cout << endl;
72     cout << "Read `man gig2stereo' for details." << endl;
73     cout << endl;
74     }
75    
76     static string programRevision() {
77 schoenebeck 2706 string s = "$Revision$";
78 schoenebeck 2685 return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword
79     }
80    
81     static void printVersion() {
82     cout << "gig2stereo revision " << programRevision() << endl;
83     cout << "using " << gig::libraryName() << " " << gig::libraryVersion() << endl;
84     }
85    
86     static bool isGigFileName(const string path) {
87     const string t = ".gig";
88     return path.substr(path.length() - t.length()) == t;
89     }
90    
91     static bool endsWith(const string& haystack, const string& needle, bool caseSensitive) {
92 schoenebeck 3175 if (haystack.size() < needle.size()) return false;
93 schoenebeck 2685 const string sub = haystack.substr(haystack.size() - needle.size(), needle.size());
94     return (caseSensitive) ? (sub == needle) : (!strcasecmp(sub.c_str(), needle.c_str()));
95     }
96    
97     /// Removes spaces from the beginning and end of @a s.
98     static string stripWhiteSpace(string s) {
99     // strip white space at the beginning
100     for (int i = 0; i < s.size(); ++i) {
101     if (s[i] != ' ') {
102     s = s.substr(i);
103     break;
104     }
105     }
106     // strip white space at the end
107     for (int i = s.size() - 1; i >= 0; --i) {
108     if (s[i] != ' ') {
109     s = s.substr(0, i+1);
110     break;
111     }
112     }
113     return s;
114     }
115    
116     static void collectFilesOfDir(string path, bool bRecurse, bool* pbError = NULL) {
117     DIR* d = opendir(path.c_str());
118     if (!d) {
119     if (pbError) *pbError = true;
120     cerr << strerror(errno) << " : '" << path << "'" << endl;
121     return;
122     }
123    
124     for (struct dirent* e = readdir(d); e; e = readdir(d)) {
125     if (string(e->d_name) == "." || string(e->d_name) == "..")
126     continue;
127    
128     const string fullName = path + DIR_SEPARATOR + e->d_name;
129    
130     struct stat s;
131     if (stat(fullName.c_str(), &s)) {
132     if (pbError) *pbError = true;
133     cerr << strerror(errno) << " : '" << fullName << "'" << endl;
134     continue;
135     }
136    
137     if (S_ISREG(s.st_mode) && isGigFileName(fullName)) {
138     g_files.insert(fullName);
139     } else if (S_ISDIR(s.st_mode) && bRecurse) {
140     collectFilesOfDir(fullName, bRecurse, pbError);
141     }
142     }
143    
144     closedir(d);
145     }
146    
147     static void collectFiles(string path, bool bRecurse, bool* pbError = NULL) {
148     struct stat s;
149     if (stat(path.c_str(), &s)) {
150     if (pbError) *pbError = true;
151     cerr << strerror(errno) << " : '" << path << "'" << endl;
152     return;
153     }
154     if (S_ISREG(s.st_mode) && isGigFileName(path)) {
155     g_files.insert(path);
156     } else if (S_ISDIR(s.st_mode)) {
157     collectFilesOfDir(path, bRecurse, pbError);
158     } else {
159     if (pbError) *pbError = true;
160     cerr << "Neither a regular (.gig) file nor directory : '" << path << "'" << endl;
161     }
162     }
163    
164     static string stripAudioChannelFromName(const string& s) {
165     if (endsWith(s, "-L", false) || endsWith(s, "-R", false) ||
166     endsWith(s, "_L", false) || endsWith(s, "_R", false) ||
167     endsWith(s, " L", false) || endsWith(s, " R", false) )
168     {
169     return s.substr(0, s.size() - 2);
170     }
171     if (endsWith(s, "-LEFT", false) || endsWith(s, "_LEFT", false) || endsWith(s, " LEFT", false) ) {
172     return s.substr(0, s.size() - 5);
173     }
174     if (endsWith(s, "-RIGHT", false) || endsWith(s, "_RIGHT", false) || endsWith(s, " RIGHT", false) ) {
175     return s.substr(0, s.size() - 6);
176     }
177     return s;
178     }
179    
180 schoenebeck 2693 #define OPTIONAL_SKIP_CHECK() \
181     if (skipIncompatible) { \
182     cerr << " Skipping!\n"; \
183     continue; /* skip to convert this sample pair */ \
184     } else { \
185     cerr << " Merging anyway (upon request)!\n"; \
186     }
187    
188 schoenebeck 2685 /**
189     * Converts .gig file given by @a path towards using true stereo interleaved
190     * samples.
191     *
192     * @param path - path and file name of .gig file to be converted
193     * @param keep - if true: do not delete the old mono samples, even if they are
194     * not referenced at all anymore after conversion
195     * @param forceReplace - By default certain references of the old mono samples
196     * are not replaced by the new true stereo samples,
197     * usually because the respective mono reference is been
198     * used in an instrument context that seems to be entirely
199     * mono, not stereo. By enabling this argument all those
200     * old references will be replaced by the new stereo
201     * sample reference instead as well.
202 schoenebeck 2693 * @param matchIncompatible - if set to true, only mono sample pairs will be
203     * qualified to be merged to true stereo samples if
204     * their main sample characteristics match, if set
205     * to true this sanity check will be skipped
206 schoenebeck 2685 * @param verbosity - verbosity level, defines whether and how much additional
207 schoenebeck 3048 * information to be printed to the console while doing the
208 schoenebeck 2685 * conversion (0 .. 2)
209     */
210 schoenebeck 2693 static bool convertFileToStereo(const string path, bool keep, bool forceReplace, bool skipIncompatible, int verbose) {
211 schoenebeck 2685 try {
212     // open .gig file
213     RIFF::File riff(path);
214     gig::File gig(&riff);
215    
216     typedef pair<gig::DimensionRegion*, gig::DimensionRegion*> DimRgnPair;
217     typedef pair<gig::Sample*, gig::Sample*> SamplePair;
218    
219     // find and collect all dimension regions with a 2 mono samples pair
220     if (verbose) cout << "Searching mono sample pairs ... " << flush;
221     if (verbose >= 2) cout << endl;
222     map<SamplePair, vector<DimRgnPair> > samplePairs;
223     map<gig::Sample*, SamplePair> samplePairsByL;
224     map<gig::Sample*, SamplePair> samplePairsByR;
225     for (gig::Instrument* instr = gig.GetFirstInstrument(); instr; instr = gig.GetNextInstrument()) {
226     for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
227     int bits = 0;
228     for (int d = 0; d < 8; ++d) {
229     if (rgn->pDimensionDefinitions[d].dimension == gig::dimension_samplechannel) {
230     for (int dr = 0; dr < rgn->DimensionRegions && rgn->pDimensionRegions[dr]; ++dr) {
231     if (!rgn->pDimensionRegions[dr]->pSample) continue; // no sample assigned, ignore
232     if (rgn->pDimensionRegions[dr]->pSample->Channels != 1) continue; // seems already to be a true stereo sample, ignore
233    
234 schoenebeck 2693 const int channel = (dr & (1 << bits)) ? 1 : 0;
235 schoenebeck 2685 if (channel == 1) continue; // ignore right audio channel dimension region here
236    
237     // so current dimension region is for left channel, so now get its matching right channel dimension region ...
238     const int drR = dr | (1 << bits);
239     if (drR >= rgn->DimensionRegions || !rgn->pDimensionRegions[drR]) {
240     cerr << "WARNING: Could not find matching right channel for left channel dimension region!" << endl;
241     continue;
242     }
243     if (!rgn->pDimensionRegions[drR]->pSample) continue; // only a sample for left channel, but none for right channel, ignore
244     if (rgn->pDimensionRegions[drR]->pSample->Channels != 1) {
245     cerr << "WARNING: Right channel seems already to be a true stereo sample!" << endl;
246     continue;
247     }
248     if (rgn->pDimensionRegions[dr]->pSample == rgn->pDimensionRegions[drR]->pSample) continue; // both channels use same sample, thus actually a mono thing here, ignore
249    
250     // found a valid mono pair, remember it
251     SamplePair samplePair(
252     rgn->pDimensionRegions[dr]->pSample,
253     rgn->pDimensionRegions[drR]->pSample
254     );
255     SamplePair samplePairInversed(
256     rgn->pDimensionRegions[drR]->pSample,
257     rgn->pDimensionRegions[dr]->pSample
258     );
259     DimRgnPair dimRgnpair(
260     rgn->pDimensionRegions[dr],
261     rgn->pDimensionRegions[drR]
262     );
263     if (samplePairs.count(samplePairInversed)) {
264     cerr << "WARNING: Sample pair ['" << rgn->pDimensionRegions[dr]->pSample->pInfo->Name << "', '" << rgn->pDimensionRegions[drR]->pSample->pInfo->Name << "'] already referenced in inverse channel order!\n";
265     } else {
266     if (samplePairsByL.count(rgn->pDimensionRegions[drR]->pSample)) {
267     cerr << "WARNING: Right mono sample '" << rgn->pDimensionRegions[drR]->pSample->pInfo->Name << "' already referenced as left sample!\n";
268     }
269     if (samplePairsByR.count(rgn->pDimensionRegions[dr]->pSample)) {
270     cerr << "WARNING: Left mono sample '" << rgn->pDimensionRegions[dr]->pSample->pInfo->Name << "' already referenced as right sample!\n";
271     }
272     }
273     samplePairs[samplePair].push_back(dimRgnpair);
274     if (verbose >= 2) cout << "Found mono sample pair ['" << rgn->pDimensionRegions[dr]->pSample->pInfo->Name << "', '" << rgn->pDimensionRegions[drR]->pSample->pInfo->Name << "'].\n" << flush;
275     }
276     goto nextRegion;
277     }
278     bits += rgn->pDimensionDefinitions[d].bits;
279     }
280     nextRegion:;
281     }
282     }
283     if (verbose) cout << samplePairs.size() << " mono pairs found.\n" << flush;
284    
285     // create a true stereo (interleaved) sample for each one of the found
286     // mono sample pairs
287     if (verbose) cout << "Creating true stereo samples ... " << flush;
288     if (verbose >= 2) cout << endl;
289     map<SamplePair, vector<DimRgnPair> > samplePairsFiltered;
290     map<SamplePair, gig::Sample*> targetStereoSamples;
291     for (map<SamplePair, vector<DimRgnPair> >::iterator it = samplePairs.begin();
292     it != samplePairs.end(); ++it)
293     {
294     gig::Sample* pSampleL = it->first.first;
295     gig::Sample* pSampleR = it->first.second;
296    
297     // bunch of sanity checks which ensure that both mono samples are
298     // using the same base parameter characteristics
299     if (pSampleL->SamplesPerSecond != pSampleR->SamplesPerSecond) {
300 schoenebeck 2693 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different sample rate! Skipping!\n";
301     continue;
302 schoenebeck 2685 }
303     if (pSampleL->FrameSize != pSampleR->FrameSize) {
304 schoenebeck 2693 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different frame size! Skipping!\n";
305     continue;
306 schoenebeck 2685 }
307     if (pSampleL->MIDIUnityNote != pSampleR->MIDIUnityNote) {
308 schoenebeck 2693 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different unity notes!";
309     OPTIONAL_SKIP_CHECK();
310 schoenebeck 2685 }
311     if (pSampleL->FineTune != pSampleR->FineTune) {
312 schoenebeck 2693 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different fine tune!";
313     OPTIONAL_SKIP_CHECK();
314 schoenebeck 2685 }
315     if (pSampleL->Loops != pSampleR->Loops) {
316 schoenebeck 2693 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different loop amount! Skipping!\n";
317 schoenebeck 2685 continue; // skip to convert this sample pair
318     }
319     if (pSampleL->Loops == 1 && pSampleR->Loops == 1) {
320     if (pSampleL->LoopType != pSampleR->LoopType) {
321 schoenebeck 2693 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different loop type!";
322     OPTIONAL_SKIP_CHECK();
323 schoenebeck 2685 }
324     if (pSampleL->LoopStart != pSampleR->LoopStart) {
325 schoenebeck 3115 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different loop start!";
326     OPTIONAL_SKIP_CHECK();
327 schoenebeck 2685 }
328     if (pSampleL->LoopEnd != pSampleR->LoopEnd) {
329 schoenebeck 3115 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different loop end!";
330     OPTIONAL_SKIP_CHECK();
331 schoenebeck 2685 }
332     if (pSampleL->LoopSize != pSampleR->LoopSize) {
333 schoenebeck 3115 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different loop size!";
334     OPTIONAL_SKIP_CHECK();
335 schoenebeck 2685 }
336     if (pSampleL->LoopPlayCount != pSampleR->LoopPlayCount) {
337 schoenebeck 2693 cerr << "WARNING: Sample pair ['" << pSampleL->pInfo->Name << "', '" << pSampleR->pInfo->Name << "'] with different loop play count!";
338     OPTIONAL_SKIP_CHECK();
339 schoenebeck 2685 }
340     }
341    
342     // create the stereo sample
343     gig::Sample* pStereoSample = gig.AddSample();
344    
345     // add the new stereo sample to an appropriate sample group
346     const string groupName = stripWhiteSpace(stripAudioChannelFromName(pSampleL->GetGroup()->Name)) + " STEREO";
347     gig::Group* pGroup = NULL;
348     for (gig::Group* gr = gig.GetFirstGroup(); gr; gr = gig.GetNextGroup()) {
349     if (gr->Name == groupName) {
350     pGroup = gr;
351     break;
352     }
353     }
354     if (!pGroup) {
355     pGroup = gig.AddGroup();
356     pGroup->Name = groupName;
357     }
358     pGroup->AddSample(pStereoSample);
359    
360     pStereoSample->pInfo->Name = stripWhiteSpace(stripAudioChannelFromName(pSampleL->pInfo->Name));
361     pStereoSample->Channels = 2;
362     pStereoSample->SamplesPerSecond = pSampleL->SamplesPerSecond;
363     pStereoSample->BitDepth = pSampleL->BitDepth;
364     pStereoSample->FrameSize = pSampleL->FrameSize * 2;
365     pStereoSample->MIDIUnityNote = pSampleL->MIDIUnityNote;
366     pStereoSample->Loops = pSampleL->Loops;
367     pStereoSample->LoopType = pSampleL->LoopType;
368     pStereoSample->LoopStart = pSampleL->LoopStart;
369     pStereoSample->LoopEnd = pSampleL->LoopEnd;
370     pStereoSample->LoopSize = pSampleL->LoopSize;
371     pStereoSample->LoopPlayCount = pSampleL->LoopPlayCount;
372    
373     // libgig does not support saving of compressed samples
374     pStereoSample->Compressed = false;
375    
376     // schedule new sample for resize (will be performed when gig.Save() is called)
377     const long newStereoSamplesTotal = max(pSampleL->SamplesTotal, pSampleR->SamplesTotal);
378     if (verbose >= 2) cout << "Resize new stereo sample '" << pStereoSample->pInfo->Name << "' to " << newStereoSamplesTotal << " sample points.\n" << flush;
379     pStereoSample->Resize(newStereoSamplesTotal);
380    
381     samplePairsFiltered[it->first] = it->second;
382     targetStereoSamples[it->first] = pStereoSample;
383     }
384     if (verbose) cout << "Done.\n" << flush;
385    
386     // increase the .gig file size appropriately
387     if (verbose) cout << "Increase file size ... " << flush;
388     gig.Save();
389     if (verbose) cout << "Done.\n" << flush;
390    
391     vector<uint8_t> bufferMono[2];
392     vector<uint8_t> stereoBuffer;
393     gig::buffer_t decompressionBuffer;
394     unsigned long decompressionBufferSize = 0; // in sample points, not bytes
395    
396     // do the actual sample conversion of each mono -> stereo sample by
397     // reading/writing directly from/to disk
398     if (verbose) cout << "Convert mono pair data to true stereo data ... " << flush;
399     if (verbose >= 2) cout << endl;
400     set<gig::Sample*> allAffectedMonoSamples;
401     for (map<SamplePair, vector<DimRgnPair> >::iterator it = samplePairsFiltered.begin();
402     it != samplePairsFiltered.end(); ++it)
403     {
404     gig::Sample* pSampleL = it->first.first;
405     gig::Sample* pSampleR = it->first.second;
406     gig::Sample* pSampleMono[2] = { pSampleL, pSampleR };
407     gig::Sample* pStereoSample = targetStereoSamples[it->first];
408    
409     if ((pSampleMono[0]->SamplesTotal != pStereoSample->GetSize() &&
410     pSampleMono[1]->SamplesTotal != pStereoSample->GetSize()) || pStereoSample->GetSize() <= 0)
411     {
412     cerr << "Stereo length " << pStereoSample->GetSize() << ", mono L size " << pSampleL->SamplesTotal << ", mono R size " << pSampleR->SamplesTotal << "\n" << flush;
413     cerr << "CRITICAL ERROR: Ill sample sizes detected! Aborting.\n";
414     exit(-1);
415     }
416    
417     allAffectedMonoSamples.insert(pSampleL);
418     allAffectedMonoSamples.insert(pSampleR);
419    
420     const long neededStereoSize = pStereoSample->GetSize() * pStereoSample->FrameSize;
421     if (stereoBuffer.size() < neededStereoSize)
422     stereoBuffer.resize(neededStereoSize);
423    
424     if (bufferMono[0].size() < neededStereoSize / 2) {
425     bufferMono[0].resize(neededStereoSize / 2);
426     bufferMono[1].resize(neededStereoSize / 2);
427     }
428     memset(&bufferMono[0][0], 0, bufferMono[0].size());
429     memset(&bufferMono[1][0], 0, bufferMono[1].size());
430    
431     // read mono samples from disk
432     for (int iChan = 0; iChan < 2; ++iChan) {
433     pSampleMono[iChan]->SetPos(0);
434     unsigned long pointsRead = 0;
435     if (pSampleMono[iChan]->Compressed) {
436     if (decompressionBufferSize < pSampleMono[iChan]->SamplesTotal) {
437     decompressionBufferSize = pSampleMono[iChan]->SamplesTotal;
438     gig::Sample::DestroyDecompressionBuffer(decompressionBuffer);
439     decompressionBuffer = gig::Sample::CreateDecompressionBuffer(pSampleMono[iChan]->SamplesTotal);
440     }
441     pointsRead = pSampleMono[iChan]->Read(&bufferMono[iChan][0], pSampleMono[iChan]->SamplesTotal, &decompressionBuffer);
442     } else {
443     pointsRead = pSampleMono[iChan]->Read(&bufferMono[iChan][0], pSampleMono[iChan]->SamplesTotal);
444     }
445     if (pointsRead != pSampleMono[iChan]->SamplesTotal) {
446     cerr << "Read " << pointsRead << " sample points from mono sample " << ((iChan == 0) ? "L" : "R") << ".\n";
447     cerr << "CRITICAL ERROR: Reading entire mono sample data from disk failed! Aborting.\n";
448     exit(-1);
449     }
450     }
451    
452     if (pSampleL->FrameSize != pSampleR->FrameSize || pStereoSample->FrameSize / 2 != pSampleL->FrameSize || pStereoSample->FrameSize < 1) {
453     cerr << "Stereo frame size " << pStereoSample->FrameSize << ", mono L frame size " << pSampleL->FrameSize << ", mono R frame size " << pSampleL->FrameSize << "\n" << flush;
454     cerr << "CRITICAL ERROR: Ill frame sizes detected! Aborting.\n";
455     exit(-1);
456     }
457    
458     // convert from the 2 mono buffers to the stereo buffer
459     for (int s = 0; s < pStereoSample->GetSize(); ++s) {
460     memcpy(&stereoBuffer[s * pStereoSample->FrameSize], &bufferMono[0][s * pSampleL->FrameSize], pSampleL->FrameSize);
461     memcpy(&stereoBuffer[s * pStereoSample->FrameSize + pSampleL->FrameSize], &bufferMono[1][s * pSampleR->FrameSize], pSampleR->FrameSize);
462     }
463    
464     // write converted stereo sample data directly to disk
465     if (verbose >= 2) cout << "Writing stereo sample '" << pStereoSample->pInfo->Name << "' (" << pStereoSample->GetSize() << " sample points).\n" << flush;
466     pStereoSample->SetPos(0);
467     unsigned long pointsWritten = pStereoSample->Write(&stereoBuffer[0], pStereoSample->GetSize());
468     if (verbose >= 2) cout << "Written " << pointsWritten << " sample points.\n" << flush;
469     }
470     if (verbose) cout << "Done.\n" << flush;
471    
472     // decompression buffer not needed anymore
473     gig::Sample::DestroyDecompressionBuffer(decompressionBuffer);
474    
475     // replace the old mono sample references by the new stereo sample
476     // references for all instruments of the .gig file
477     if (verbose) cout << "Replace old mono sample references by new stereo sample references ... " << flush;
478     for (map<SamplePair, vector<DimRgnPair> >::iterator it = samplePairsFiltered.begin();
479     it != samplePairsFiltered.end(); ++it)
480     {
481     if (forceReplace) {
482     // replace EVERY occurence of the old mono sample references by
483     // the new true stereo ones
484     gig::Sample* pMonoSamples[2] = { it->first.first, it->first.second };
485     gig::Sample* pStereoSample = targetStereoSamples[it->first];
486     for (int iChan = 0; iChan < 2; ++iChan) {
487     gig::Sample* pMonoSample = pMonoSamples[iChan];
488     // iterate through all instruments and replace all references of that sample
489     for (gig::Instrument* instr = gig.GetFirstInstrument(); instr; instr = gig.GetNextInstrument()) {
490     for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
491     for (int dr = 0; dr < rgn->DimensionRegions; ++dr) {
492     if (!rgn->pDimensionRegions[dr])
493     continue;
494     if (rgn->pDimensionRegions[dr]->pSample == pMonoSample) {
495     rgn->pDimensionRegions[dr]->pSample = pStereoSample;
496     }
497     }
498     }
499     }
500     }
501     } else {
502     // only replace old mono sample references of instruments if
503     // they really seem to be used in a stereo context
504     const vector<DimRgnPair>& dimRgnPairs = it->second;
505     gig::Sample* pStereoSample = targetStereoSamples[it->first];
506     for (int drp = 0; drp < dimRgnPairs.size(); ++drp) {
507     dimRgnPairs[drp].first->pSample = pStereoSample;
508     dimRgnPairs[drp].second->pSample = pStereoSample;
509     }
510     }
511     }
512     if (verbose) cout << "Done.\n" << flush;
513    
514     if (!keep) {
515     if (verbose) cout << "Removing orphaned mono samples ... " << flush;
516     int iDeletedSamples = 0;
517     // remove all old 2 mono sample pairs that are not referenced anymore
518     for (map<SamplePair, vector<DimRgnPair> >::iterator it = samplePairsFiltered.begin();
519     it != samplePairsFiltered.end(); ++it)
520     {
521     gig::Sample* pMonoSamples[2] = { it->first.first, it->first.second };
522     for (int iChan = 0; iChan < 2; ++iChan) {
523     gig::Sample* pMonoSample = pMonoSamples[iChan];
524    
525     // iterate through all instruments and check if that
526     // old mono sample is still referenced somewhere
527     for (gig::Instrument* instr = gig.GetFirstInstrument(); instr; instr = gig.GetNextInstrument()) {
528     for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
529     for (int dr = 0; dr < rgn->DimensionRegions; ++dr) {
530     if (!rgn->pDimensionRegions[dr])
531     continue;
532     if (rgn->pDimensionRegions[dr]->pSample == pMonoSample) {
533     std::cout << "NOTE: Mono sample '" + pMonoSample->pInfo->Name + "' is still referenced by instrument '" << instr->pInfo->Name << "'. Thus not deleting sample.\n";
534     goto nextSample; // still referenced, don't delete sample
535     }
536     }
537     }
538     }
539    
540     // if this point is reached, then pMonoSample is not referenced
541     // by any instrument anymore, so delete this old mono sample now
542     {
543     gig::Group* pGroup = pMonoSample->GetGroup();
544     gig.DeleteSample(pMonoSample);
545     iDeletedSamples++;
546    
547     // if deleted sample's group is now empty, delete the group
548     if (!pGroup->GetFirstSample()) gig.DeleteGroup(pGroup);
549     }
550    
551     nextSample:;
552     }
553     }
554     if (verbose) {
555     cout << "Done (deleted " << iDeletedSamples << " of " << allAffectedMonoSamples.size() << " mono samples).\n" << flush;
556     if (iDeletedSamples < allAffectedMonoSamples.size())
557     cout << "(Use --force-replace to replace all old mono references.)\n" << flush;
558     }
559     }
560    
561     // save sample reference changes (and drop deleted mono samples)
562     if (verbose) cout << "Saving final sample references to disk and shrink file ... " << flush;
563     gig.Save();
564     if (verbose) cout << "Done.\n" << flush;
565    
566     } catch (RIFF::Exception e) {
567     cerr << "Failed converting file:" << endl;
568     e.PrintMessage();
569     return false; // error
570     } catch (...) {
571 schoenebeck 3048 cerr << "Unknown exception occurred while trying to convert file." << endl;
572 schoenebeck 2685 return false; // error
573     }
574     return true; // success
575     }
576    
577     int main(int argc, char *argv[]) {
578     int nOptions = 0;
579     bool bOptionRecurse = false;
580     bool bOptionKeep = false;
581     bool bOptionForceReplace = false;
582 schoenebeck 2693 bool bOptionMatchIncompatible = false;
583 schoenebeck 2685 int iVerbose = 0;
584    
585     // validate & parse arguments provided to this program
586     if (argc <= 1) {
587     printUsage();
588     return EXIT_FAILURE;
589     }
590     for (int i = 1; i < argc; ++i) {
591     const string opt = argv[i];
592     if (opt == "--") { // common for all command line tools: separator between initial option arguments and i.e. subsequent file arguments
593     nOptions++;
594     break;
595     }
596     if (opt.substr(0, 1) != "-") break;
597    
598     if (opt == "-v") {
599     printVersion();
600     return EXIT_SUCCESS;
601     } else if (opt == "-r") {
602     nOptions++;
603     bOptionRecurse = true;
604     } else if (opt == "--keep") {
605     nOptions++;
606     bOptionKeep = true;
607     } else if (opt == "--force-replace") {
608     nOptions++;
609     bOptionForceReplace = true;
610 schoenebeck 2693 } else if (opt == "--incompatible") {
611     nOptions++;
612     bOptionMatchIncompatible = true;
613 schoenebeck 2685 } else if (opt == "--verbose") {
614     nOptions++;
615     iVerbose = 1;
616     if (i+1 < argc) {
617     char* s = argv[i+1];
618     char* endptr = NULL;
619     errno = 0;
620     long val = strtol(s, &endptr, 10);
621     if (errno != EINVAL && errno != ERANGE && endptr != s) {
622     if (val < 0) {
623     cerr << "Numeric argument for --verbose must not be negative!\n";
624     return -1;
625     }
626     iVerbose = val;
627     nOptions++;
628     i++;
629     }
630     }
631    
632     }
633     }
634     const int nFileArguments = argc - nOptions - 1;
635     if (nFileArguments < 1) {
636     printUsage();
637     return EXIT_FAILURE;
638     }
639    
640     bool bError = false;
641    
642     // assemble list of .gig file names to be converted
643     for (int i = nOptions + 1; i < argc; ++i) {
644     collectFiles(argv[i], bOptionRecurse, &bError);
645     }
646     if (g_files.size() < 1) {
647     cerr << "No file given to be converted!" << endl;
648     return EXIT_FAILURE;
649     }
650     if (bError) {
651     cerr << "Aborted due to error. No file has been changed." << endl;
652     return EXIT_FAILURE;
653     }
654    
655     // convert each file in the assembled list of file names
656     {
657     int i = 0;
658     for (set<string>::const_iterator it = g_files.begin(); it != g_files.end(); ++it, ++i) {
659     cout << "Converting file " << (i+1) << "/" << int(g_files.size()) << ": " << *it << " ... " << flush;
660     if (iVerbose) cout << endl;
661 schoenebeck 2693 bool bSuccess = convertFileToStereo(
662     *it, bOptionKeep, bOptionForceReplace, !bOptionMatchIncompatible,
663     iVerbose
664     );
665 schoenebeck 2685 if (!bSuccess) return EXIT_FAILURE;
666     cout << "OK" << endl;
667     }
668     }
669    
670     return EXIT_SUCCESS;
671     }

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC