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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3048 - (hide annotations) (download)
Fri Nov 25 18:34:45 2016 UTC (7 years, 4 months ago) by schoenebeck
File size: 15726 byte(s)
- Fixed various spelling mistakes (patch by Debian maintainer).

1 schoenebeck 2484 /***************************************************************************
2     * *
3     * libgig - C++ cross-platform Gigasampler format file access library *
4     * *
5 schoenebeck 2573 * Copyright (C) 2003-2014 by Christian Schoenebeck *
6 schoenebeck 2484 * <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    
39     #ifdef WIN32
40     # define DIR_SEPARATOR '\\'
41     #else
42     # define DIR_SEPARATOR '/'
43     #endif
44    
45 schoenebeck 2573 #include "../gig.h"
46 schoenebeck 2484
47     using namespace std;
48    
49     /**
50     * How the Gigasampler samples shall be converted from stereo to mono.
51     */
52     enum conversion_method_t {
53     CONVERT_MIX_CHANNELS, ///< Mix left & right audio channel together.
54     CONVERT_LEFT_CHANNEL, ///< Only pick left audio channel for mono output.
55     CONVERT_RIGHT_CHANNEL ///< Only pick right audio channel for mono output.
56     };
57    
58     static set<string> g_files;
59    
60     static conversion_method_t g_conversionMethod = CONVERT_MIX_CHANNELS;
61    
62     static void printUsage() {
63     cout << "gig2mono - converts Gigasampler files from stereo to mono." << endl;
64     cout << endl;
65     cout << "Usage: gig2mono [-r] [--left | --right | --mix] FILE_OR_DIR1 [ FILE_OR_DIR2 ... ]" << endl;
66     cout << " gig2mono -v" << endl;
67     cout << endl;
68     cout << " -r Recurse through subdirectories." << endl;
69     cout << endl;
70     cout << " --mix Convert by mixing left & right audio channels (default)." << endl;
71     cout << endl;
72     cout << " --left Convert by using left audio channel data." << endl;
73     cout << endl;
74     cout << " --right Convert by using right audio channel data." << endl;
75     cout << endl;
76     cout << " -v Print version and exit." << endl;
77     cout << endl;
78     }
79    
80     static string programRevision() {
81 schoenebeck 2493 string s = "$Revision$";
82 schoenebeck 2484 return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword
83     }
84    
85     static void printVersion() {
86     cout << "gig2mono revision " << programRevision() << endl;
87     cout << "using " << gig::libraryName() << " " << gig::libraryVersion() << endl;
88     }
89    
90     static bool isGigFileName(const string path) {
91     const string t = ".gig";
92     return path.substr(path.length() - t.length()) == t;
93     }
94    
95     static void collectFilesOfDir(string path, bool bRecurse, bool* pbError = NULL) {
96     DIR* d = opendir(path.c_str());
97     if (!d) {
98     if (pbError) *pbError = true;
99     cerr << strerror(errno) << " : '" << path << "'" << endl;
100     return;
101     }
102    
103     for (struct dirent* e = readdir(d); e; e = readdir(d)) {
104     if (string(e->d_name) == "." || string(e->d_name) == "..")
105     continue;
106    
107     const string fullName = path + DIR_SEPARATOR + e->d_name;
108    
109     struct stat s;
110     if (stat(fullName.c_str(), &s)) {
111     if (pbError) *pbError = true;
112     cerr << strerror(errno) << " : '" << fullName << "'" << endl;
113     continue;
114     }
115    
116     if (S_ISREG(s.st_mode) && isGigFileName(fullName)) {
117     g_files.insert(fullName);
118     } else if (S_ISDIR(s.st_mode) && bRecurse) {
119     collectFilesOfDir(fullName, bRecurse, pbError);
120     }
121     }
122    
123     closedir(d);
124     }
125    
126     static void collectFiles(string path, bool bRecurse, bool* pbError = NULL) {
127     struct stat s;
128     if (stat(path.c_str(), &s)) {
129     if (pbError) *pbError = true;
130     cerr << strerror(errno) << " : '" << path << "'" << endl;
131     return;
132     }
133     if (S_ISREG(s.st_mode) && isGigFileName(path)) {
134     g_files.insert(path);
135     } else if (S_ISDIR(s.st_mode)) {
136     collectFilesOfDir(path, bRecurse, pbError);
137     } else {
138     if (pbError) *pbError = true;
139     cerr << "Neither a regular (.gig) file nor directory : '" << path << "'" << endl;
140     }
141     }
142    
143     static bool convertFileToMono(const string path) {
144     try {
145     RIFF::File riff(path);
146     gig::File gig(&riff);
147    
148     gig::buffer_t decompressionBuffer;
149     unsigned long decompressionBufferSize = 0; // in sample points, not bytes
150     vector< vector<uint8_t> > audioBuffers;
151     vector<uint8_t> stereoBuffer;
152    
153     // count the total amount of samples
154     int nSamples = 0;
155     for (gig::Sample* pSample = gig.GetFirstSample(); pSample; pSample = gig.GetNextSample())
156     nSamples++;
157    
158     audioBuffers.resize(nSamples);
159    
160     // read all original (stereo) sample audio data, convert it to mono and
161     // keep the mono audio data in tempoary buffers in RAM
162     bool bModified = false;
163     int i = 0;
164     for (gig::Sample* pSample = gig.GetFirstSample(); pSample; pSample = gig.GetNextSample(), ++i) {
165     if (pSample->Channels != 2)
166     continue; // ignore samples not being stereo
167    
168     // ensure audio conversion buffer is large enough for this sample
169     const long neededStereoSize = pSample->SamplesTotal * pSample->FrameSize;
170     const long neededMonoSize = neededStereoSize / 2;
171     if (stereoBuffer.size() < neededStereoSize)
172     stereoBuffer.resize(neededStereoSize);
173     audioBuffers[i].resize(neededMonoSize);
174    
175     // read sample's current (stereo) audio data
176     if (pSample->Compressed) {
177     if (decompressionBufferSize < pSample->SamplesTotal) {
178     decompressionBufferSize = pSample->SamplesTotal;
179     gig::Sample::DestroyDecompressionBuffer(decompressionBuffer);
180     decompressionBuffer = gig::Sample::CreateDecompressionBuffer(pSample->SamplesTotal);
181     }
182     pSample->Read(&stereoBuffer[0], pSample->SamplesTotal, &decompressionBuffer);
183     } else {
184     pSample->Read(&stereoBuffer[0], pSample->SamplesTotal);
185     }
186    
187     const int stereoFrameSize = pSample->FrameSize;
188     const int monoFrameSize = stereoFrameSize / 2;
189    
190     // This condition must be satisfied for the conversion for() loops
191     // below to not end up beyond buffer boundaries. This check should
192     // only fail on corrupt .gig files.
193     if (stereoBuffer.size() % stereoFrameSize != 0) {
194     cerr << "Internal error: Invalid stereo data length (" << int(stereoBuffer.size()) << " bytes)" << endl;
195     return false; // error
196     }
197    
198     // convert stereo -> mono
199     switch (g_conversionMethod) {
200     case CONVERT_LEFT_CHANNEL: {
201     const int n = pSample->SamplesTotal;
202     for (int m = 0; m < n; ++m) {
203     for (int k = 0; k < monoFrameSize; ++k) {
204     audioBuffers[i][m * monoFrameSize + k] = stereoBuffer[m * stereoFrameSize + k];
205     }
206     }
207     break;
208     }
209    
210     case CONVERT_RIGHT_CHANNEL: {
211     const int n = pSample->SamplesTotal;
212     for (int m = 0; m < n; ++m) {
213     for (int k = 0; k < monoFrameSize; ++k) {
214     audioBuffers[i][m * monoFrameSize + k] = stereoBuffer[m * stereoFrameSize + k + monoFrameSize];
215     }
216     }
217     break;
218     }
219    
220     case CONVERT_MIX_CHANNELS:
221     switch (pSample->BitDepth) {
222     case 16: {
223     int16_t* in = (int16_t*) &stereoBuffer[0];
224     int16_t* out = (int16_t*) &audioBuffers[i][0];
225     const int n = pSample->SamplesTotal;
226     for (int m = 0; m < n; ++m) {
227     const float l = in[m*2];
228     const float r = in[m*2+1];
229     out[m] = int16_t((l + r) * 0.5f);
230     }
231     break;
232     }
233    
234     case 24: {
235     int8_t* p = (int8_t*) &stereoBuffer[0];
236     int16_t* out = (int16_t*) &audioBuffers[i][0];
237     const int n = pSample->SamplesTotal;
238     for (int t = 0, m = 0; m < n; t += 6, ++m) {
239     #if WORDS_BIGENDIAN
240     const float l = p[t + 0] << 8 | p[t + 1] << 16 | p[t + 2] << 24;
241     const float r = p[t + 3] << 8 | p[t + 4] << 16 | p[t + 5] << 24;
242     #else
243     // 24bit read optimization:
244     // a misaligned 32bit read and subquent 8 bit shift is faster (on x86) than reading 3 single bytes and shifting them
245     const float l = (*((int32_t *)(&p[t]))) << 8;
246     const float r = (*((int32_t *)(&p[t+3]))) << 8;
247     #endif
248     out[m] = int16_t((l + r) * 0.5f);
249     }
250     break;
251     }
252    
253     default:
254     cerr << "Internal error: Invalid sample bit depth (" << int(pSample->BitDepth) << " bit)" << endl;
255     return false; // error
256     }
257     break;
258    
259     default:
260     cerr << "Internal error: Invalid conversion method (#" << int(g_conversionMethod) << ")" << endl;
261     return false; // error
262     }
263    
264     // update sample's meta informations for mono
265     pSample->Channels = 1;
266     if (pSample->FrameSize) pSample->FrameSize /= 2;
267     // libgig does not support saving of compressed samples
268     pSample->Compressed = false;
269    
270     // schedule sample for physical resize operation
271     pSample->Resize(pSample->SamplesTotal);
272    
273     bModified = true;
274     }
275    
276     if (!bModified) {
277     cout << "[ignored: not stereo] " << flush;
278     return true; // success
279     }
280    
281     // remove all stereo dimensions (if any)
282     for (gig::Instrument* instr = gig.GetFirstInstrument(); instr; instr = gig.GetNextInstrument()) {
283     for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
284     for (int k = 0; k < 8; ++k) {
285     if (rgn->pDimensionDefinitions[k].dimension == gig::dimension_samplechannel) {
286     rgn->DeleteDimension(&rgn->pDimensionDefinitions[k]);
287     goto nextRegion;
288     }
289     }
290     nextRegion:;
291     }
292     }
293    
294     // Make sure sample chunks are resized to the required size on disk,
295     // so we can directly write the audio data directly to the file on disk
296     // as next step. This is required because in worst case the original
297     // stereo sample audio data might be compressed and might be smaller in
298     // size than the final (always uncompressed) mono audio data size.
299     gig.Save();
300    
301     // now convert and write the samples' mono audio data directly to the file
302     i = 0;
303     for (gig::Sample* pSample = gig.GetFirstSample(); pSample; pSample = gig.GetNextSample(), ++i) {
304     if (audioBuffers[i].empty())
305     continue; // was not a stereo sample before, so ignore this one
306    
307     const int n = audioBuffers[i].size() / pSample->FrameSize;
308     pSample->Write(&audioBuffers[i][0], n);
309     }
310     } catch (RIFF::Exception e) {
311     cerr << "Failed converting file:" << endl;
312     e.PrintMessage();
313     return false; // error
314     } catch (...) {
315 schoenebeck 3048 cerr << "Unknown exception occurred while trying to convert file." << endl;
316 schoenebeck 2484 return false; // error
317     }
318     return true; // success
319     }
320    
321     int main(int argc, char *argv[]) {
322     int nOptions = 0;
323     bool bOptionRecurse = false;
324    
325     // validate & parse arguments provided to this program
326     if (argc <= 1) {
327     printUsage();
328     return EXIT_FAILURE;
329     }
330     for (int i = 1; i < argc; ++i) {
331     const string opt = argv[i];
332     if (opt == "--") { // common for all command line tools: separator between initial option arguments and i.e. subsequent file arguments
333     nOptions++;
334     break;
335     }
336     if (opt.substr(0, 1) != "-") break;
337    
338     if (opt == "-v") {
339     printVersion();
340     return EXIT_SUCCESS;
341     } else if (opt == "-r") {
342     nOptions++;
343     bOptionRecurse = true;
344     } else if (opt == "--left") {
345     nOptions++;
346     g_conversionMethod = CONVERT_LEFT_CHANNEL;
347     } else if (opt == "--right") {
348     nOptions++;
349     g_conversionMethod = CONVERT_RIGHT_CHANNEL;
350     } else if (opt == "--mix") {
351     nOptions++;
352     g_conversionMethod = CONVERT_MIX_CHANNELS;
353     }
354     }
355     const int nFileArguments = argc - nOptions - 1;
356     if (nFileArguments < 1) {
357     printUsage();
358     return EXIT_FAILURE;
359     }
360    
361     bool bError = false;
362    
363     // assemble list of .gig file names to be converted
364     for (int i = nOptions + 1; i < argc; ++i) {
365     collectFiles(argv[i], bOptionRecurse, &bError);
366     }
367     if (g_files.size() < 1) {
368     cerr << "No file given to be converted!" << endl;
369     return EXIT_FAILURE;
370     }
371     if (bError) {
372     cerr << "Aborted due to error. No file has been changed." << endl;
373     return EXIT_FAILURE;
374     }
375    
376     // convert each file in the assembled list of file names
377     {
378     int i = 0;
379     for (set<string>::const_iterator it = g_files.begin(); it != g_files.end(); ++it, ++i) {
380     cout << "Converting file " << (i+1) << "/" << int(g_files.size()) << ": " << *it << " ... " << flush;
381     bool bSuccess = convertFileToMono(*it);
382     if (!bSuccess) return EXIT_FAILURE;
383     cout << "OK" << endl;
384     }
385     }
386    
387     return EXIT_SUCCESS;
388     }

Properties

Name Value
svn:keywords Revision

  ViewVC Help
Powered by ViewVC