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