diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index e12406d2695..042a4b00a99 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -553,6 +553,7 @@ class alignas(16) SurgeStorage bool load_wt_wt(std::string filename, Wavetable* wt); // void load_wt_wav(std::string filename, Wavetable* wt); void load_wt_wav_portable(std::string filename, Wavetable *wt); + void export_wt_wav_portable(std::string fbase, Wavetable *wt); void clipboard_copy(int type, int scene, int entry); void clipboard_paste(int type, int scene, int entry); int get_clipboard_type(); diff --git a/src/common/UserInteractions.h b/src/common/UserInteractions.h index 6987915d398..909f5a690ef 100644 --- a/src/common/UserInteractions.h +++ b/src/common/UserInteractions.h @@ -32,6 +32,10 @@ void promptError(const std::string &message, const std::string &title, // And a convenience version which does the same from a Surge::Error void promptError(const Surge::Error &error, SurgeGUIEditor *guiEditor = nullptr); +// Show the user an Info dialog with an OK button; and wait for them to press it +void promptInfo(const std::string &message, const std::string &title, + SurgeGUIEditor *guiEditor = nullptr); + // Prompt the user with an OK/Cance typedef enum MessageResult { diff --git a/src/common/WavSupport.cpp b/src/common/WavSupport.cpp index 39eca3ceaac..e1c884d6c33 100644 --- a/src/common/WavSupport.cpp +++ b/src/common/WavSupport.cpp @@ -24,6 +24,16 @@ #include "SurgeStorage.h" #include +#if LINUX +#include +#elif MAC || TARGET_RACK +#include +#else +#include +#endif + +namespace fs = std::experimental::filesystem; + // Sigh - lets write a portable ntol by hand unsigned int pl_int(char *d) { @@ -105,6 +115,7 @@ void SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) bool hasCLM = false;; bool hasCUE = false; bool hasSRGE = false; + bool hasSRGO = false; int clmLEN = 0; int smplLEN = 0; int cueLEN = 0; @@ -205,6 +216,14 @@ void SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) srgeLEN = pl_int(dp); free(data); } + else if( four_chars(chunkType, 's', 'r', 'g', 'o')) + { + hasSRGO = true; + char *dp = data; + int version = pl_int(dp); dp += 4; + srgeLEN = pl_int(dp); + free(data); + } else if( four_chars(chunkType, 'c', 'u', 'e', ' ' )) { char *dp = data; @@ -393,6 +412,9 @@ void SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) if( wh.flags & wtf_is_sample ) { auto windowSize = 1024; + if( hasSRGO ) + windowSize = srgeLEN; + while( windowSize * 4 > sample_length && windowSize > 8 ) windowSize = windowSize / 2; wh.n_samples = windowSize; @@ -442,3 +464,132 @@ void SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) } return; } + +void SurgeStorage::export_wt_wav_portable(std::string fbase, Wavetable *wt) +{ + std::ostringstream oss; + oss << userDataPath << "/ExportedWaveTables"; + fs::create_directories( oss.str() ); + std::string dirName = oss.str(); + + auto fnamePre = fbase + ".wav"; + auto fname = dirName + "/" + fbase + ".wav"; + int fnum = 1; + while( fs::exists( fs::path( fname ) ) ) { + fname = dirName + "/" + fbase + "_" + std::to_string(fnum) + ".wav"; + fnamePre = fbase + "_" + std::to_string(fnum) + ".wav"; + fnum++; + } + + struct FG { + FILE *f = nullptr; + ~FG() { + if( f ) fclose( f ); + } + }; + std::string errorMessage = "Unknown Error"; + FG fguard; + + { + FILE *wfp = fopen( fname.c_str(), "wb" ); + if( ! wfp ) + { + errorMessage = "Unable to open file '" + fname + "'"; + goto error; + } + fguard.f = wfp; + + auto audioFormat = 3; + auto bitsPerSample = 32; + auto sampleRate = 44100; + auto nChannels = 1; + + auto w4i = [wfp](unsigned int v) { + unsigned char fi[4]; + for( int i=0; i<4; ++i ) + { + fi[i] = (unsigned char)( v & 255 ); + v = v / 256; + } + fwrite( fi, 1, 4, wfp ); + }; + + + auto w2i = [wfp](unsigned int v) { + unsigned char fi[2]; + for( int i=0; i<2; ++i ) + { + fi[i] = (unsigned char)( v & 255 ); + v = v / 256; + } + fwrite( fi, 1, 2, wfp ); + }; + + fwrite( "RIFF", 1, 4, wfp ); + + bool isSample = false; + if( wt->flags & wtf_is_sample ) + isSample = true; + + // OK so how much data do I have. + unsigned int tableSize = nChannels * bitsPerSample / 8 * wt->n_tables * wt->size; + unsigned int dataSize = 4 + // 'WAVE' + 4 + 4 + 18 + // fmt chunk + 4 + 4 + tableSize; + + if( ! isSample ) + dataSize += 4 + 4 + 8; // srge chunk + + + w4i(dataSize); + fwrite( "WAVE", 1, 4, wfp ); + + // OK so format chunk + fwrite( "fmt ", 1, 4, wfp ); + w4i( 16 ); + w2i( audioFormat ); + w2i( nChannels ); + w4i( sampleRate ); + w4i( sampleRate * nChannels * bitsPerSample ); + w2i( bitsPerSample * nChannels ); + w2i( bitsPerSample ); + + if( ! isSample ) + { + fwrite( "srge", 1, 4, wfp ); + w4i( 8 ); + w4i( 1 ); + w4i( wt->size ); + } + else + { + fwrite( "srgo", 1, 4, wfp ); // o for oneshot + w4i( 8 ); + w4i( 1 ); + w4i( wt->size ); + + } + + fwrite( "data", 1, 4, wfp ); + w4i( tableSize ); + for( int i=0; in_tables; ++i ) + { + fwrite( wt->TableF32WeakPointers[0][i], wt->size, bitsPerSample / 8, wfp ); + } + + fclose(wfp); + fguard.f = nullptr; + refresh_wtlist(); + + Surge::UserInteractions::promptInfo( "Exported to your Surge Documents in `ExportedWaveTables/" + fnamePre + "'", + "Export Succeeded" ); + + return; + } + +error: + Surge::UserInteractions::promptError( errorMessage, "Export" ); + return; + + +} diff --git a/src/common/gui/COscillatorDisplay.cpp b/src/common/gui/COscillatorDisplay.cpp index fed5bb879b5..00e038aae40 100644 --- a/src/common/gui/COscillatorDisplay.cpp +++ b/src/common/gui/COscillatorDisplay.cpp @@ -692,7 +692,38 @@ void COscillatorDisplay::populateMenu(COptionMenu* contextMenu, int selectedItem auto action = [this](CCommandMenuItem* item) { this->loadWavetableFromFile(); }; actionItem->setActions(action, nullptr); contextMenu->addEntry(actionItem); - + + auto exportItem = new CCommandMenuItem(CCommandMenuItem::Desc("Export Wave Table to File...")); + auto exportAction = [this](CCommandMenuItem *item) + { + // FIXME - we need to find the scene and osc by iterating (gross). + int oscNum = -1; + int scene = -1; + for( int sc=0;sc<2;sc++ ) + { + auto s = &(storage->getPatch().scene[sc]); + for( int o=0;oosc[o] ) == this->oscdata ) + { + oscNum = o; + scene = sc; + } + } + } + if( scene == -1 || oscNum == -1 ) + { + Surge::UserInteractions::promptError( "Unable to determine which osc I have data for in export", "Export" ); + } + else + { + std::string baseName = storage->getPatch().name + "_osc" + std::to_string(oscNum + 1) + "_scene" + (scene == 0 ? "A" : "B" ); + storage->export_wt_wav_portable( baseName, &( oscdata->wt ) ); + } + }; + exportItem->setActions(exportAction,nullptr); + contextMenu->addEntry(exportItem); + auto contentItem = new CCommandMenuItem(CCommandMenuItem::Desc("Download Additional Content...")); auto contentAction = [](CCommandMenuItem *item) { diff --git a/src/headless/UserInteractionsHeadless.cpp b/src/headless/UserInteractionsHeadless.cpp index 99a5d695247..a5e54c9b0f3 100644 --- a/src/headless/UserInteractionsHeadless.cpp +++ b/src/headless/UserInteractionsHeadless.cpp @@ -20,6 +20,15 @@ void promptError(const Surge::Error &error, SurgeGUIEditor *guiEditor) promptError(error.getMessage(), error.getTitle()); } +void promptInfo(const std::string &message, const std::string &title, + SurgeGUIEditor *guiEditor) +{ + std::cerr << "Surge Info\n" + << title << "\n" + << message << "\n" << std::flush; +} + + MessageResult promptOKCancel(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) { diff --git a/src/linux/UserInteractionsLinux.cpp b/src/linux/UserInteractionsLinux.cpp index 00f00dbd0aa..851b22303ef 100644 --- a/src/linux/UserInteractionsLinux.cpp +++ b/src/linux/UserInteractionsLinux.cpp @@ -74,6 +74,25 @@ void promptError(const Surge::Error &error, SurgeGUIEditor *guiEditor) promptError(error.getMessage(), error.getTitle()); } +void promptInfo(const std::string &message, const std::string &title, + SurgeGUIEditor *guiEditor) +{ + if (vfork()==0) + { + if (execlp("zenity", "zenity", + "--info", + "--text", message.c_str(), + "--title", title.c_str(), + (char*)nullptr) < 0) + { + _exit(0); + } + } + std::cerr << "Surge Error\n" + << title << "\n" + << message << "\n" << std::flush; +} + MessageResult promptOKCancel(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) { diff --git a/src/mac/UserInteractionsMac.mm b/src/mac/UserInteractionsMac.mm index b567f6d2c2a..57b0f88f1c3 100644 --- a/src/mac/UserInteractionsMac.mm +++ b/src/mac/UserInteractionsMac.mm @@ -42,6 +42,30 @@ void promptError(const Surge::Error& error, SurgeGUIEditor* guiEditor) promptError(error.getMessage(), error.getTitle()); } +void promptInfo(const std::string& message, const std::string& title, SurgeGUIEditor* guiEditor) +{ + CFStringRef cfT = + CFStringCreateWithCString(kCFAllocatorDefault, title.c_str(), kCFStringEncodingUTF8); + CFStringRef cfM = + CFStringCreateWithCString(kCFAllocatorDefault, message.c_str(), kCFStringEncodingUTF8); + + SInt32 nRes = 0; + CFUserNotificationRef pDlg = NULL; + const void* keys[] = {kCFUserNotificationAlertHeaderKey, kCFUserNotificationAlertMessageKey}; + const void* vals[] = {cfT, cfM}; + + CFDictionaryRef dict = + CFDictionaryCreate(0, keys, vals, sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + pDlg = CFUserNotificationCreate(kCFAllocatorDefault, 0, kCFUserNotificationNoteAlertLevel, &nRes, + dict); + + CFRelease(cfT); + CFRelease(cfM); +} + + MessageResult promptOKCancel(const std::string& message, const std::string& title, SurgeGUIEditor* guiEditor) { diff --git a/src/windows/UserInteractionsWin.cpp b/src/windows/UserInteractionsWin.cpp index 459173ea8fd..dcf1a98d161 100644 --- a/src/windows/UserInteractionsWin.cpp +++ b/src/windows/UserInteractionsWin.cpp @@ -28,6 +28,16 @@ void promptError(const Surge::Error &error, SurgeGUIEditor *guiEditor) promptError(error.getMessage(), error.getTitle()); } +void promptInfo(const std::string &message, const std::string &title, + SurgeGUIEditor *guiEditor) +{ + MessageBox(::GetActiveWindow(), + message.c_str(), + title.c_str(), + MB_OK | MB_ICONINFORMATION ); +} + + MessageResult promptOKCancel(const std::string &message, const std::string &title, SurgeGUIEditor *guiEditor) {