From 5acf1f33d6bb62803f7d2e1faa03fc948f272ab9 Mon Sep 17 00:00:00 2001 From: Harry van Haaren Date: Thu, 3 Nov 2016 20:09:06 +0000 Subject: [PATCH] clip save implemented, fixes #121 --- src/avtk/clipselector.cxx | 19 ++++++++++++++ src/diskwriter.cxx | 52 ++++++++++++++++++++++----------------- src/diskwriter.hxx | 6 +++-- src/event.hxx | 3 ++- src/eventhandlerdsp.cxx | 14 +++++++++++ src/eventhandlergui.cxx | 13 ++++++++-- src/gui.cxx | 34 +++++++++++++++++++++++++ src/gui.hxx | 6 ++++- src/looperclip.hxx | 2 ++ 9 files changed, 121 insertions(+), 28 deletions(-) diff --git a/src/avtk/clipselector.cxx b/src/avtk/clipselector.cxx index 5b2b64d..b6690b9 100644 --- a/src/avtk/clipselector.cxx +++ b/src/avtk/clipselector.cxx @@ -19,6 +19,8 @@ #include "clipselector.hxx" +#include + #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #include "../gui.hxx" @@ -292,6 +294,7 @@ int ClipSelector::handle(int event) Fl_Menu_Item rclick_menu[] = { { "Load" }, + { "Save" }, { "Special"}, { "Beats", 0, 0, 0, FL_SUBMENU | FL_MENU_DIVIDER }, {"1 "}, @@ -317,6 +320,22 @@ int ClipSelector::handle(int event) { gui->selectLoadSample( ID, clipNum ); } + else if ( strcmp(m->label(), "Save") == 0 ) + { + //gui->saveBufferPath = "/tmp/test.wav"; + char* tmp = gui->selectSavePath(); + if(tmp && strlen(tmp)) { + if( access( tmp, F_OK ) != -1 ) { + int overwrite = fl_choice("Overwrite file?","Cancel","Overwrite",0); + if (!overwrite) { + return 0; + } + } + gui->saveBufferPath = tmp; + free(tmp); + gui->selectSaveSample( ID, clipNum ); + } + } else if ( strcmp(m->label(), "1 ") == 0 ) { EventLooperLoopLength e = EventLooperLoopLength(ID, clipNum ,1); writeToDspRingbuffer( &e ); diff --git a/src/diskwriter.cxx b/src/diskwriter.cxx index 78fa79c..7193596 100644 --- a/src/diskwriter.cxx +++ b/src/diskwriter.cxx @@ -308,34 +308,42 @@ int DiskWriter::writeControllerFile( Controller* c ) return LUPPP_RETURN_OK; } -int DiskWriter::writeAudioBuffer(int track, int scene, AudioBuffer* ab ) +int DiskWriter::writeAudioBuffer(int track, int scene, AudioBuffer* ab, + const char *gui_path) { - if ( !foldersCreated ) + stringstream path; + if ( gui_path && strlen(gui_path) ) { + printf("saving single buffer to %s\n", gui_path); + path << gui_path; + } + else if ( foldersCreated ) + { + stringstream filename; + filename << "t_" << track << "_s_" << scene << ".wav"; + + // store the clip in clipData, we will write the session JSON for it in writeSession + clipData.push_back( ClipData( track, scene, filename.str() ) ); + + // add the AudioBuffer metadata to the sample JSON node + cJSON* sampleClip = cJSON_CreateObject(); + cJSON_AddItemToObject(audioJson, filename.str().c_str(), sampleClip ); + cJSON_AddNumberToObject(sampleClip,"beats", ab->getBeats() ); + + // get pretty name from GUI + std::string clipName = gui->getTrack(track)->getClipSelector()->clipName( scene ); + cJSON_AddItemToObject ( sampleClip, "name", cJSON_CreateString( clipName.c_str() )); + + // write the AudioBuffer contents to /audio/ as .wav + // or alternatively t__s_.wav + + path << audioDir << "/" << filename.str(); + } + else { LUPPP_WARN("%s", "Session folders not created yet, while trying to write audioBuffers."); return LUPPP_RETURN_ERROR; } - stringstream filename; - filename << "t_" << track << "_s_" << scene << ".wav"; - - // store the clip in clipData, we will write the session JSON for it in writeSession - clipData.push_back( ClipData( track, scene, filename.str() ) ); - - // add the AudioBuffer metadata to the sample JSON node - cJSON* sampleClip = cJSON_CreateObject(); - cJSON_AddItemToObject(audioJson, filename.str().c_str(), sampleClip ); - cJSON_AddNumberToObject(sampleClip,"beats", ab->getBeats() ); - - // get pretty name from GUI - std::string clipName = gui->getTrack(track)->getClipSelector()->clipName( scene ); - cJSON_AddItemToObject ( sampleClip, "name", cJSON_CreateString( clipName.c_str() )); - - // write the AudioBuffer contents to /audio/ as .wav - // or alternatively t__s_.wav - - stringstream path; - path << audioDir << "/" << filename.str(); SndfileHandle outfile( path.str(), SFM_WRITE, SF_FORMAT_WAV | SF_FORMAT_FLOAT, 1, gui->samplerate ); diff --git a/src/diskwriter.hxx b/src/diskwriter.hxx index 3cc23e7..14c252c 100644 --- a/src/diskwriter.hxx +++ b/src/diskwriter.hxx @@ -59,8 +59,10 @@ class DiskWriter /// sets up session write path etc void initialize( std::string path, std::string sessionName ); - /// writes a single audio buffer to disk - int writeAudioBuffer(int track, int scene, AudioBuffer* ab ); + /// writes a single audio buffer to disk for saving whole state. + // When gui_path is set, it only saves a single AB* to that path + int writeAudioBuffer(int track, int scene, AudioBuffer* ab, + const char* gui_path = 0); /// flush the JSON to disk, finalizing the save int writeSession(); diff --git a/src/event.hxx b/src/event.hxx index 74d08ca..539c15a 100644 --- a/src/event.hxx +++ b/src/event.hxx @@ -663,8 +663,9 @@ class EventStateSaveBuffer : public EventBase int scene; // pointer to the AudioBuffer to be saved AudioBuffer* ab; + bool no_dealloc; - EventStateSaveBuffer(): track(0), scene(0), ab(0) {} + EventStateSaveBuffer(): track(0), scene(0), ab(0), no_dealloc(0) {} EventStateSaveBuffer(int t, int s, AudioBuffer* a): track(t), scene(s), ab(a) {} }; diff --git a/src/eventhandlerdsp.cxx b/src/eventhandlerdsp.cxx index 2cd6a0d..7a0783d 100644 --- a/src/eventhandlerdsp.cxx +++ b/src/eventhandlerdsp.cxx @@ -104,6 +104,20 @@ void handleDspEvents() jack_ringbuffer_read( rbToDsp, (char*)&ev, sizeof(EventStateReset) ); jack->getState()->reset(); } break; } + case Event::STATE_SAVE_BUFFER: { + if ( availableRead >= sizeof(EventStateReset) ) { + EventStateSaveBuffer ev; + jack_ringbuffer_read( rbToDsp, (char*)&ev, sizeof(EventStateSaveBuffer) ); + printf("jack got save buffer in %d, %d\n", ev.track, ev.scene); + LooperClip* lc = jack->getLooper(ev.track)->getClip(ev.scene); + if(!lc) break; + EventStateSaveBuffer e; + e.track = ev.track; + e.scene = ev.scene; + e.ab = lc->getAudioBuffer(); + e.no_dealloc = 1; + writeToGuiRingbuffer( &e ); + } break; } // ========= MASTER === case Event::MASTER_VOL: { diff --git a/src/eventhandlergui.cxx b/src/eventhandlergui.cxx index d60ea2c..b529b9e 100644 --- a/src/eventhandlergui.cxx +++ b/src/eventhandlergui.cxx @@ -181,8 +181,17 @@ void handleGuiEvents() cout << "EventSaveBuffer: " << ev.track << " " << ev.scene << " " << ev.ab->getID() << endl; #endif gui->getDiskWriter()->writeAudioBuffer( ev.track, ev.scene, ev.ab ); - // de allocate the AudioBuffer - delete ev.ab; + // de allocate the AudioBuffer only if reqested + if(!ev.no_dealloc) { + gui->getDiskWriter()->writeAudioBuffer( ev.track, ev.scene, ev.ab ); + delete ev.ab; + } else + { + gui->getDiskWriter()->writeAudioBuffer(ev.track, ev.scene, ev.ab, + gui->saveBufferPath.c_str()); + gui->saveBufferPath = ""; + } + } break; } case Event::STATE_SAVE_FINISH: { diff --git a/src/gui.cxx b/src/gui.cxx index 50ba96d..d7fec92 100644 --- a/src/gui.cxx +++ b/src/gui.cxx @@ -252,6 +252,40 @@ void Gui::selectLoadController(Fl_Widget* w, void*) } +void Gui::selectSaveSample( int track, int scene ) +{ + EventStateSaveBuffer e; + e.track = track, + e.scene = scene, + writeToDspRingbuffer( &e ); +} + +char * +Gui::selectSavePath() +{ + string path; + Fl_Native_File_Chooser fnfc; + fnfc.title("Save filename?"); + fnfc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); + + std::string defLoadPath = gui->getDiskReader()->getLastLoadedSamplePath(); + fnfc.directory( defLoadPath.c_str() ); // default directory to use + + // Show native chooser + switch ( fnfc.show() ) { + case -1: /*printf("ERROR: %s\n", fnfc.errmsg()); */ break; // ERROR + case 1: /*(printf("CANCEL\n"); */ break; // CANCEL + default: /*printf("Loading directory: %s\n", fnfc.filename()); */ + path = fnfc.filename(); + break; + } + + if ( strcmp( path.c_str(), "" ) == 0 ) + return 0; + + return strdup(path.c_str()); +} + void Gui::selectLoadSample( int track, int scene ) { // FIXME: refactor diff --git a/src/gui.hxx b/src/gui.hxx index 77178d0..a2a011b 100644 --- a/src/gui.hxx +++ b/src/gui.hxx @@ -76,6 +76,8 @@ class Gui /// used to load samples into the grid void selectLoadSample( int track, int clip ); + void selectSaveSample( int track, int clip ); + char* selectSavePath(); /// allows the user to select a Controller definition static void selectLoadController(Fl_Widget* w, void*); @@ -91,7 +93,9 @@ class Gui /// current special clip: int specialTrack; int specialScene; - + + // save a particular sample to path + std::string saveBufferPath; private: vector controllerVector; diff --git a/src/looperclip.hxx b/src/looperclip.hxx index 269d6ba..a419ba4 100644 --- a/src/looperclip.hxx +++ b/src/looperclip.hxx @@ -82,6 +82,8 @@ class LooperClip : public Stately //Return the nr of samples holding actual audio. This is less then getBufferLength(); long getActualAudioLength(); size_t audioBufferSize(); + + AudioBuffer* getAudioBuffer() {return _buffer;} /// set clip state void queuePlay(bool=true);