1 /*
2 ** Portable (using standard fread and so on) support for .wav files generating
3 ** wavetables.
4 **
5 ** Two things which matter in addition to the fmt and data block
6 **
7 ** 1: The `smpl` block
8 ** 2: the `clm ` block - indicates a serum file
9 ** 3: the 'cue' block which apparently NI uses
10 **
11 ** I read them in this order
12 **
13 ** 1. If there is a clm block that wins, we ignore the smpl and cue block, and you get your 2048 wt
14 ** 2. If there is a cue block and no clm, use the offsets if they are power of 2 and regular
15 ** 3. Else if there is no smpl block and you have a smpl block if you have a power of 2 sample
16 *length
17 ** we interpret you as a wt
18 ** 4. otherwise as a one shot or - right now- an error
19 */
20
21 #define WAV_STDOUT_INFO 0
22
23 #include <stdio.h>
24 #include "UserInteractions.h"
25 #include "SurgeStorage.h"
26 #include <sstream>
27 #include <cerrno>
28 #include <cstring>
29
30 #include "filesystem/import.h"
31
32 // Sigh - lets write a portable ntol by hand
pl_int(char * d)33 unsigned int pl_int(char *d)
34 {
35 return (unsigned char)d[0] + (((unsigned char)d[1]) << 8) + (((unsigned char)d[2]) << 16) +
36 (((unsigned char)d[3]) << 24);
37 }
38
pl_short(char * d)39 unsigned short pl_short(char *d) { return (unsigned char)d[0] + (((unsigned char)d[1]) << 8); }
40
four_chars(char * v,char a,char b,char c,char d)41 bool four_chars(char *v, char a, char b, char c, char d)
42 {
43 return v[0] == a && v[1] == b && v[2] == c && v[3] == d;
44 }
45
load_wt_wav_portable(std::string fn,Wavetable * wt)46 bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt)
47 {
48 std::string uitag = "Wavetable Import Error";
49 #if WAV_STDOUT_INFO
50 std::cout << "Loading wt_wav_portable" << std::endl;
51 std::cout << " fn='" << fn << "'" << std::endl;
52 #endif
53
54 std::filebuf fp;
55 if (!fp.open(string_to_path(fn), std::ios::binary | std::ios::in))
56 {
57 std::ostringstream oss;
58 oss << "Unable to open file '" << fn << "'!";
59 Surge::UserInteractions::promptError(oss.str(), uitag);
60 return false;
61 }
62
63 char riff[4], szd[4], wav[4];
64 auto hds = fp.sgetn(riff, sizeof(riff));
65 hds += fp.sgetn(szd, sizeof(szd));
66 hds += fp.sgetn(wav, sizeof(wav));
67 if (hds != 12)
68 {
69 std::ostringstream oss;
70 oss << "'" << fn << "' does not contain a valid RIFF header chunk!";
71 Surge::UserInteractions::promptError(oss.str(), uitag);
72 return false;
73 }
74
75 if (!four_chars(riff, 'R', 'I', 'F', 'F') && !four_chars(wav, 'W', 'A', 'V', 'E'))
76 {
77 std::ostringstream oss;
78 oss << "'" << fn << "' is not a standard RIFF/WAVE file. Header is: " << riff[0] << riff[1]
79 << riff[2] << riff[3] << " " << wav[0] << wav[1] << wav[2] << wav[3] << ".";
80 Surge::UserInteractions::promptError(oss.str(), uitag);
81 return false;
82 }
83
84 // WAV HEADER
85 unsigned short audioFormat, numChannels;
86 unsigned int sampleRate, byteRate;
87 unsigned short blockAlign, bitsPerSample;
88
89 // Result of data read
90 bool hasSMPL = false;
91 bool hasCLM = false;
92 ;
93 bool hasCUE = false;
94 bool hasSRGE = false;
95 bool hasSRGO = false;
96 int clmLEN = 0;
97 int smplLEN = 0;
98 int cueLEN = 0;
99 int srgeLEN = 0;
100
101 // Now start reading chunks
102 int tbr = 4;
103 char *wavdata = nullptr;
104 int datasz = 0, datasamples;
105 while (true)
106 {
107 char chunkType[4], chunkSzD[4];
108 int br;
109 if (fp.sgetn(chunkType, sizeof(chunkType)) != sizeof(chunkType))
110 {
111 break;
112 }
113 br = fp.sgetn(chunkSzD, sizeof(chunkSzD));
114 // FIXME - deal with br
115 int cs = pl_int(chunkSzD);
116
117 #if WAV_STDOUT_INFO
118 std::cout << " CHUNK `";
119 for (int i = 0; i < 4; ++i)
120 std::cout << chunkType[i];
121 std::cout << "` sz=" << cs << std::endl;
122 #endif
123 tbr += 8 + cs;
124
125 char *data = (char *)malloc(cs);
126 br = fp.sgetn(data, cs);
127 if (br != cs)
128 {
129 free(data);
130
131 break;
132 }
133
134 if (four_chars(chunkType, 'f', 'm', 't', ' '))
135 {
136 char *dp = data;
137 audioFormat = pl_short(dp);
138 dp += 2; // 1 is PCM; 3 is IEEE Float
139 numChannels = pl_short(dp);
140 dp += 2;
141 sampleRate = pl_int(dp);
142 dp += 4;
143 byteRate = pl_int(dp);
144 dp += 4;
145 blockAlign = pl_short(dp);
146 dp += 2;
147 bitsPerSample = pl_short(dp);
148 dp += 2;
149
150 #if WAV_STDOUT_INFO
151 std::cout << " FMT=" << audioFormat << " x " << numChannels << " at "
152 << bitsPerSample << " bits" << std::endl;
153 #endif
154
155 free(data);
156
157 // Do a format check here to bail out
158 if (!((numChannels == 1) &&
159 (((audioFormat == 1 /* WAVE_FORMAT_PCM */) && (bitsPerSample == 16)) ||
160 ((audioFormat == 3 /* IEEE_FLOAT */) && (bitsPerSample == 32)))))
161 {
162 std::string formname = "Unknown (" + std::to_string(audioFormat) + ")";
163 if (audioFormat == 1)
164 formname = "PCM";
165 if (audioFormat == 3)
166 formname = "float";
167
168 std::ostringstream oss;
169 oss << "Currently, Surge only supports 16-bit PCM or 32-bit float mono WAV files. "
170 << " You provided a " << bitsPerSample << "-bit " << formname << " "
171 << numChannels << "-channel file.";
172
173 Surge::UserInteractions::promptError(oss.str(), uitag);
174 return false;
175 }
176 }
177 else if (four_chars(chunkType, 'c', 'l', 'm', ' '))
178 {
179 // These all begin '<!>dddd' where d is 2048 it seems
180 char *dp = data + 3;
181 if (four_chars(dp, '2', '0', '4', '8'))
182 {
183 // 2048 CLM detected
184 hasCLM = true;
185 clmLEN = 2048;
186 }
187 free(data);
188 }
189 else if (four_chars(chunkType, 'u', 'h', 'W', 'T'))
190 {
191 // This is HIVE metadata so treat it just like CLM / Serum
192 hasCLM = true;
193 clmLEN = 2048;
194
195 free(data);
196 }
197 else if (four_chars(chunkType, 's', 'r', 'g', 'e'))
198 {
199 hasSRGE = true;
200 char *dp = data;
201 int version = pl_int(dp);
202 dp += 4;
203 srgeLEN = pl_int(dp);
204 free(data);
205 }
206 else if (four_chars(chunkType, 's', 'r', 'g', 'o'))
207 {
208 hasSRGO = true;
209 char *dp = data;
210 int version = pl_int(dp);
211 dp += 4;
212 srgeLEN = pl_int(dp);
213 free(data);
214 }
215 else if (four_chars(chunkType, 'c', 'u', 'e', ' '))
216 {
217 char *dp = data;
218 int numCues = pl_int(dp);
219 dp += 4;
220 std::vector<int> chunkStarts;
221 for (int i = 0; i < numCues; ++i)
222 {
223 for (int j = 0; j < 6; ++j)
224 {
225 auto d = pl_int(dp);
226 if (j == 5)
227 chunkStarts.push_back(d);
228
229 dp += 4;
230 }
231 }
232
233 // Now are my chunkstarts regular
234 int d = -1;
235 bool regular = true;
236 for (auto i = 1; i < chunkStarts.size(); ++i)
237 {
238 if (d == -1)
239 d = chunkStarts[i] - chunkStarts[i - 1];
240 else
241 {
242 if (d != chunkStarts[i] - chunkStarts[i - 1])
243 regular = false;
244 }
245 }
246
247 if (regular)
248 {
249 hasCUE = true;
250 cueLEN = d;
251 }
252
253 free(data);
254 }
255 else if (four_chars(chunkType, 'd', 'a', 't', 'a'))
256 {
257 datasz = cs;
258 datasamples = cs * 8 / bitsPerSample / numChannels;
259 wavdata = data;
260 }
261 else if (four_chars(chunkType, 's', 'm', 'p', 'l'))
262 {
263 char *dp = data;
264 unsigned int samplechunk[9];
265 for (int i = 0; i < 9; ++i)
266 {
267 samplechunk[i] = pl_int(dp);
268 dp += 4;
269 }
270 unsigned int nloops = samplechunk[7];
271 unsigned int sdsz = samplechunk[8];
272 #if WAV_STDOUT_INFO
273 std::cout << " SMPL: nloops=" << nloops << " " << sdsz << std::endl;
274 #endif
275
276 if (nloops == 0)
277 {
278 // It seems RAPID uses a smpl block with no samples to indicate a 2048.
279 hasSMPL = true;
280 smplLEN = 2048;
281 }
282
283 if (nloops > 1)
284 {
285 // We don't support this. Indicate somehow.
286 // FIXME
287 }
288
289 for (int i = 0; i < nloops && i < 1; ++i)
290 {
291 unsigned int loopdata[6];
292 for (int j = 0; j < 6; ++j)
293 {
294 loopdata[j] = pl_int(dp);
295 dp += 4;
296 #if WAV_STDOUT_INFO
297 std::cout << " loopdata[" << j << "] = " << loopdata[j] << std::endl;
298 #endif
299 }
300 hasSMPL = true;
301 smplLEN = loopdata[3] - loopdata[2] + 1;
302
303 if (smplLEN == 0)
304 smplLEN = 2048;
305 }
306 }
307 else
308 {
309 /*std::cout << "Default Dump\n";
310 for( int i=0; i<cs; ++i ) std::cout << data[i];
311 std::cout << std::endl; */
312 free(data);
313 }
314 }
315
316 #if WAV_STDOUT_INFO
317 std::cout << " hasCLM =" << hasCLM << " / " << clmLEN << std::endl;
318 std::cout << " hasCUE =" << hasCUE << " / " << cueLEN << std::endl;
319 std::cout << " hasSMPL=" << hasSMPL << "/" << smplLEN << std::endl;
320 std::cout << " hasSRGE=" << hasSRGE << "/" << srgeLEN << std::endl;
321 #endif
322
323 bool loopData = hasSMPL || hasCLM || hasSRGE;
324 int loopLen =
325 hasCLM ? clmLEN : (hasCUE ? cueLEN : (hasSRGE ? srgeLEN : (hasSMPL ? smplLEN : -1)));
326 if (loopLen == 0)
327 {
328 std::ostringstream oss;
329 oss << "Surge cannot understand this particular .wav file. Please consult the Surge Wiki "
330 "for"
331 << " information on .wav file metadata.";
332
333 Surge::UserInteractions::promptError(oss.str(), uitag);
334
335 if (wavdata)
336 free(wavdata);
337 return false;
338 }
339
340 int loopCount = datasamples / loopLen;
341
342 #if WAV_STDOUT_INFO
343 std::cout << " samples=" << datasamples << " loopLen=" << loopLen << " loopCount=" << loopCount
344 << std::endl;
345 #endif
346
347 // wt format header (surge internal)
348 wt_header wh;
349 memset(&wh, 0, sizeof(wt_header));
350 wh.flags = wtf_is_sample;
351
352 int sh = 0;
353 if (loopData)
354 {
355 wh.flags = 0;
356 switch (loopLen)
357 {
358 case 4096:
359 sh = 12;
360 break;
361 case 2048:
362 sh = 11;
363 break;
364 case 1024:
365 sh = 10;
366 break;
367 case 512:
368 sh = 9;
369 break;
370 case 256:
371 sh = 8;
372 break;
373 case 128:
374 sh = 7;
375 break;
376 case 64:
377 sh = 6;
378 break;
379 case 32:
380 sh = 5;
381 break;
382 case 16:
383 sh = 4;
384 break;
385 case 8:
386 sh = 3;
387 break;
388 case 4:
389 sh = 2;
390 break;
391 case 2:
392 sh = 1;
393 break;
394 default:
395 #if WAV_STDOUT_INFO
396 std::cout << "Setting style to sample" << std::endl;
397 #endif
398 wh.flags = wtf_is_sample;
399 break;
400 }
401 }
402
403 if (loopLen != -1 && (sh == 0 || loopCount < 1))
404 {
405 std::ostringstream oss;
406 oss << "Currently, Surge only supports wavetables with at least a single frame, with up to "
407 "4096 samples per frame in power-of-two increments. You provided a wavetable with "
408 << loopCount << (loopCount == 1 ? " frame" : " frames") << " of " << loopLen
409 << " samples. '" << fn << "'";
410 Surge::UserInteractions::promptError(oss.str(), uitag);
411
412 if (wavdata)
413 free(wavdata);
414 return false;
415 }
416
417 wh.n_samples = 1 << sh;
418 int mask = wt->size - 1;
419 int sample_length = std::min(datasamples, max_wtable_size * max_subtables);
420 wh.n_tables = std::min(max_subtables, (sample_length >> sh));
421
422 if (wh.flags & wtf_is_sample)
423 {
424 auto windowSize = 1024;
425 if (hasSRGO)
426 windowSize = srgeLEN;
427
428 while (windowSize * 4 > sample_length && windowSize > 8)
429 windowSize = windowSize / 2;
430 wh.n_samples = windowSize;
431 wh.n_tables = (int)(sample_length / windowSize);
432 }
433
434 int channels = 1;
435
436 if ((audioFormat == 1 /* WAVE_FORMAT_PCM */) && (bitsPerSample == 16) && numChannels == 1)
437 {
438 // assert(wh.n_samples * wh.n_tables * 2 <= datasz);
439 wh.flags |= wtf_int16;
440 }
441 else if ((audioFormat == 3 /* WAVE_FORMAT_IEEE_FLOAT */) && (bitsPerSample == 32) &&
442 numChannels == 1)
443 {
444 // assert(wh.n_samples * wh.n_tables * 4 <= datasz);
445 }
446 else
447 {
448 std::ostringstream oss;
449 oss << "Currently, Surge only supports 16-bit PCM or 32-bit float mono .wav files. "
450 << " You provided a " << bitsPerSample << "-bit" << audioFormat << " " << numChannels
451 << "-channel file.";
452
453 Surge::UserInteractions::promptError(oss.str(), uitag);
454
455 if (wavdata)
456 free(wavdata);
457 return false;
458 }
459
460 if (wavdata && wt)
461 {
462 waveTableDataMutex.lock();
463 wt->BuildWT(wavdata, wh, wh.flags & wtf_is_sample);
464 waveTableDataMutex.unlock();
465 free(wavdata);
466 }
467 return true;
468 }
469
export_wt_wav_portable(std::string fbase,Wavetable * wt)470 void SurgeStorage::export_wt_wav_portable(std::string fbase, Wavetable *wt)
471 {
472 std::string path;
473 path = Surge::Storage::appendDirectory(userDataPath, "Exported Wavetables");
474 fs::create_directories(string_to_path(path));
475
476 auto fnamePre = fbase + ".wav";
477 auto fname = Surge::Storage::appendDirectory(path, fbase + ".wav");
478 int fnum = 1;
479 while (fs::exists(fs::path(fname)))
480 {
481 fname = Surge::Storage::appendDirectory(path, fbase + "_" + std::to_string(fnum) + ".wav");
482 fnamePre = fbase + "_" + std::to_string(fnum) + ".wav";
483 fnum++;
484 }
485
486 std::string errorMessage = "Unknown error!";
487
488 {
489 std::filebuf wfp;
490 if (!wfp.open(string_to_path(fname), std::ios::binary | std::ios::out))
491 {
492 errorMessage = "Unable to open file " + fname + "!";
493 errorMessage += std::strerror(errno);
494
495 Surge::UserInteractions::promptError(errorMessage, "Wavetable Export");
496 return;
497 }
498
499 auto audioFormat = 3;
500 auto bitsPerSample = 32;
501 auto sampleRate = 44100;
502 auto nChannels = 1;
503
504 auto w4i = [&wfp](unsigned int v) {
505 unsigned char fi[4];
506 for (int i = 0; i < 4; ++i)
507 {
508 fi[i] = (unsigned char)(v & 255);
509 v = v / 256;
510 }
511 wfp.sputn(reinterpret_cast<char *>(fi), sizeof(fi));
512 };
513
514 auto w2i = [&wfp](unsigned int v) {
515 unsigned char fi[2];
516 for (int i = 0; i < 2; ++i)
517 {
518 fi[i] = (unsigned char)(v & 255);
519 v = v / 256;
520 }
521 wfp.sputn(reinterpret_cast<char *>(fi), sizeof(fi));
522 };
523
524 wfp.sputn("RIFF", 4);
525
526 bool isSample = false;
527 if (wt->flags & wtf_is_sample)
528 isSample = true;
529
530 // OK so how much data do I have.
531 unsigned int tableSize = nChannels * bitsPerSample / 8 * wt->n_tables * wt->size;
532 unsigned int dataSize = 4 + // 'WAVE'
533 4 + 4 + 18 + // fmt chunk
534 4 + 4 + tableSize;
535
536 if (!isSample)
537 dataSize += 4 + 4 + 8; // srge chunk
538
539 w4i(dataSize);
540 wfp.sputn("WAVE", 4);
541
542 // OK so format chunk
543 wfp.sputn("fmt ", 4);
544 w4i(16);
545 w2i(audioFormat);
546 w2i(nChannels);
547 w4i(sampleRate);
548 w4i(sampleRate * nChannels * bitsPerSample);
549 w2i(bitsPerSample * nChannels);
550 w2i(bitsPerSample);
551
552 if (!isSample)
553 {
554 wfp.sputn("srge", 4);
555 w4i(8);
556 w4i(1);
557 w4i(wt->size);
558 }
559 else
560 {
561 wfp.sputn("srgo", 4); // o for oneshot
562 w4i(8);
563 w4i(1);
564 w4i(wt->size);
565 }
566
567 wfp.sputn("data", 4);
568 w4i(tableSize);
569 for (int i = 0; i < wt->n_tables; ++i)
570 {
571 wfp.sputn(reinterpret_cast<char *>(wt->TableF32WeakPointers[0][i]),
572 wt->size * bitsPerSample / 8);
573 }
574 }
575
576 refresh_wtlist();
577
578 Surge::UserInteractions::promptInfo("Exported to " + fname, "Export Succeeded!");
579 }
580