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

Contents of /libgig/trunk/src/gig2mono.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2493 - (show annotations) (download)
Wed Jan 1 17:06:51 2014 UTC (10 years, 2 months ago) by schoenebeck
File size: 15722 byte(s)
* Enabled automatic svn "Revision" macro expansion on command
  line tool sources.

1 /***************************************************************************
2 * *
3 * libgig - C++ cross-platform Gigasampler format file access library *
4 * *
5 * Copyright (C) 2003-2013 by Christian Schoenebeck *
6 * <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 #include "gig.h"
46
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 string s = "$Revision$";
82 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 cerr << "Unknown exception occured while trying to convert file." << endl;
316 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