Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Patch Wavetable Export #1399

Merged
merged 1 commit into from
Dec 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/common/SurgeStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions src/common/UserInteractions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
151 changes: 151 additions & 0 deletions src/common/WavSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
#include "SurgeStorage.h"
#include <sstream>

#if LINUX
#include <experimental/filesystem>
#elif MAC || TARGET_RACK
#include <filesystem.h>
#else
#include <filesystem>
#endif

namespace fs = std::experimental::filesystem;

// Sigh - lets write a portable ntol by hand
unsigned int pl_int(char *d)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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; i<wt->n_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;


}
33 changes: 32 additions & 1 deletion src/common/gui/COscillatorDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;o<n_oscs; ++o )
{
if( &( s->osc[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)
{
Expand Down
9 changes: 9 additions & 0 deletions src/headless/UserInteractionsHeadless.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
19 changes: 19 additions & 0 deletions src/linux/UserInteractionsLinux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
24 changes: 24 additions & 0 deletions src/mac/UserInteractionsMac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
10 changes: 10 additions & 0 deletions src/windows/UserInteractionsWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down