/* * Author: Harry van Haaren 2013 * harryhaaren@gmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "diskwriter.hxx" #include "config.hxx" #include #include #include #include #include #include // to prompt the user of new filenames in case of overwrite #include #include "gui.hxx" #include "gmastertrack.hxx" #include "controller/genericmidi.hxx" #include extern Gui* gui; using namespace std; DiskWriter::DiskWriter() { sessionJson = cJSON_CreateObject(); audioJson = cJSON_CreateObject(); // setup default controller name / author etc controllerInfo[CONTROLLER_NAME] = "no name"; controllerInfo[CONTROLLER_AUTHOR] = "no author"; controllerInfo[CONTROLLER_LINK] = "no link"; sessionDir = getenv("HOME"); sessionName = "session"; foldersCreated = false; // create .config/soundship/loopp/ directory stringstream dotConfig; dotConfig << getenv("HOME") << "/.config/soundship/"; int dotConfigDir = mkdir( dotConfig.str().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ); if ( errno == EEXIST ) { //LOOPP_NOTE("dotConfigDir exists"); } else if ( dotConfigDir ) { LOOPP_WARN("Error creating dotConfigDir: %s", strerror(errno)); } else { LOOPP_NOTE("Creating .config/soundship/ directory"); } stringstream dotConfigLoopp; dotConfigLoopp << getenv("HOME") << "/.config/soundship/loopp"; int dotConfigLooppDir = mkdir( dotConfigLoopp.str().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ); if ( errno == EEXIST ) { //LOOPP_NOTE("dotConfigLooppDir exists"); } else if ( dotConfigLooppDir ) { LOOPP_WARN("Error creating dotConfigLooppDir: %s", strerror(errno)); } else { LOOPP_NOTE("Creating .config/soundship/loopp directory"); } stringstream dotConfigCtlr; dotConfigCtlr << getenv("HOME") << "/.config/soundship/loopp/controllers/"; int dotConfigCtlrDir = mkdir( dotConfigCtlr.str().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ); if ( errno == EEXIST ) { //LOOPP_NOTE("dotConfigCtlrDir exists"); } else if ( dotConfigCtlrDir ) { LOOPP_WARN("Error creating dotConfigCtlrDir: %s", strerror(errno)); } else { LOOPP_NOTE("Creating .config/soundship/loopp directory"); } } DiskWriter::~DiskWriter() { // reset the cJSON objects cJSON_Delete( sessionJson ); cJSON_Delete( audioJson ); } void DiskWriter::initialize(std::string path, std::string name ) { sessionName = name; sessionPath = path; /* If project dir doesn't exist yet, create it */ int projectDir = mkdir( path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ); if ( errno == EEXIST ) { //LOOPP_NOTE("project dir %s exists, this is good", path.c_str()); } else if ( projectDir ) { LOOPP_WARN("Error creating projectDir: %s", strerror(errno)); } else { LOOPP_NOTE("Creating %s directory", path.c_str()); } // write session.loopp JSON node to /.loopp stringstream sessionDirStream; sessionDirStream << path; if ( !gui->getNsm() ) sessionDirStream << "/" << sessionName << ".loopp"; sessionDir = sessionDirStream.str(); LOOPP_NOTE( "Creating session dir %s", sessionDir.c_str() ); int sessionDirError = mkdir( sessionDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ); if ( sessionDirError ) { // handle by using different filename? LOOPP_WARN("Error creating session directory. Does the path: %s exist?",path.c_str()); } stringstream audioDirStream; audioDirStream << sessionDir << "/audio"; audioDir = audioDirStream.str(); LOOPP_NOTE("Creating audio dir %s", audioDir.c_str() ); int audioDirError = mkdir( audioDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ); if ( audioDirError ) { LOOPP_WARN("Error creating sample directory"); } foldersCreated = true; } std::string DiskWriter::getLastSaveName() { return sessionName; } std::string DiskWriter::getLastSavePath() { return sessionPath; } void DiskWriter::writeControllerInfo( CONTROLLER_INFO c, std::string s ) { controllerInfo[c] = s; } int DiskWriter::writeControllerFile( Controller* c ) { if ( c ) { LOOPP_NOTE("DiskWriter Controller* id: %i", c->getID() ); } else { LOOPP_ERROR("DiskWriter Controller* passed NULL" ); return LOOPP_RETURN_ERROR; } // check if controller of ID is actually a GenericMIDI controller GenericMIDI* g = dynamic_cast( c ); if ( g ) { LOOPP_NOTE("Creating JSON for .ctlr file..."); cJSON* controllerJson = cJSON_CreateObject(); cJSON_AddItemToObject( controllerJson, "name", cJSON_CreateString( controllerInfo[CONTROLLER_NAME].c_str() ) ); cJSON_AddItemToObject( controllerJson, "author", cJSON_CreateString( controllerInfo[CONTROLLER_AUTHOR].c_str() ) ); cJSON_AddItemToObject( controllerJson, "link", cJSON_CreateString( controllerInfo[CONTROLLER_LINK].c_str() ) ); // input bindings std::vector b = g->getMidiToAction(); cJSON* inputBindings = cJSON_CreateArray(); cJSON_AddItemToObject(controllerJson, "inputBindings", inputBindings ); for(unsigned int i = 0; i < b.size(); i++ ) { // create binding cJSON* binding = cJSON_CreateObject(); cJSON_AddItemToArray( inputBindings, binding ); // add metadata to binding const char* actionName = Event::getPrettyName( b.at(i)->action ); if ( actionName ) { cJSON_AddItemToObject( binding, "action", cJSON_CreateString( actionName ) ); cJSON_AddNumberToObject( binding, "status", b.at(i)->status ); cJSON_AddNumberToObject( binding, "data", b.at(i)->data ); // only add JSON elements if they're not the "invalid" defaults if ( b.at(i)->track != -2 ) cJSON_AddNumberToObject( binding, "track", b.at(i)->track ); if ( b.at(i)->scene != -1 ) cJSON_AddNumberToObject( binding, "scene", b.at(i)->scene ); if ( b.at(i)->send != -1 ) cJSON_AddNumberToObject( binding, "send", b.at(i)->send ); if ( b.at(i)->active!= -1 ) cJSON_AddNumberToObject( binding, "active", b.at(i)->active ); LOOPP_NOTE("Creating Binding: action %i == %s!", b.at(i)->action, actionName ); } else { LOOPP_WARN("Binding action %i has no prettyName!", b.at(i)->action ); } } // write the sample JSON node to /sample.cfg stringstream controllerCfgPath; controllerCfgPath << getenv("HOME") << "/.config/soundship/loopp/controllers/" << g->getName() << ".ctlr"; ifstream infile( controllerCfgPath.str().c_str() ); if ( infile.good() ) { // file exists: ask user overwrite or rename? //LOOPP_WARN("Controller filename exists: prompting user to overwrite y/n?"); int action = fl_choice("Controller exists, action?", "Cancel", "Rename", "Overwrite"); if ( action == 0 ) { // return OK, as user has chosen to cancel writing the file return LOOPP_RETURN_OK; } else if ( action == 1 ) { // rename here const char* name = fl_input("New name for .ctlr file:"); if ( name ) { // clear the filename controllerCfgPath.str( "" ); controllerCfgPath << getenv("HOME") << "/.config/soundship/loopp/controllers/" << name << ".ctlr"; LOOPP_NOTE( "New .ctlr filename %s", controllerCfgPath.str().c_str() ); } else { LOOPP_NOTE("No name entered for .ctlr file, canceling!"); return LOOPP_RETURN_ERROR; } } else { // just overwrite the file, no action } } LOOPP_NOTE("Writing %s.ctlr file to disk", g->getName().c_str() ); ofstream controllerCfgFile; controllerCfgFile.open ( controllerCfgPath.str().c_str() ); controllerCfgFile << cJSON_Print( controllerJson ); controllerCfgFile.close(); } else { LOOPP_WARN("Invalid Controller pointer: cannot write %s as is not a GenericMIDI controller!", c->getName().c_str() ); return LOOPP_RETURN_ERROR; } return LOOPP_RETURN_OK; } int DiskWriter::writeAudioBuffer(int track, int scene, AudioBuffer* ab, const char *gui_path) { 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 { LOOPP_WARN("%s", "Session folders not created yet, while trying to write audioBuffers."); return LOOPP_RETURN_ERROR; } SndfileHandle outfile( path.str(), SFM_WRITE, SF_FORMAT_WAV | SF_FORMAT_FLOAT, 2, gui->samplerate ); // FIXME: the size of the buffer is bigger than the audio contained in it: // calculate the length that needs saving using getBeats() * framesPerBeat if ( ab->getAudioFrames() > 0 ) { float frameBuf[ 2 ]; for(int i=0; igetAudioFrames(); i++) { frameBuf[0] = ab->getDataL()[i]; frameBuf[1] = ab->getDataR()[i]; outfile.writef( frameBuf, 1); } } else { LOOPP_WARN("%s","Sample has zero samples"); } return LOOPP_RETURN_OK; } void DiskWriter::writeMaster() { // Master track stuff cJSON* masterTrack = cJSON_CreateObject(); cJSON_AddItemToObject(sessionJson, "master", masterTrack ); GMasterTrack* master = gui->getMasterTrack(); cJSON_AddNumberToObject( masterTrack, "fader", master->getVolume()->value() ); cJSON_AddNumberToObject( masterTrack, "bpm", gui->getMasterTrack()->getBpm() ); // TODO add samplerate to session JSON //cJSON_AddNumberToObject( masterTrack, "samplerate", gui->getMasterTrack()->getBpm() ); cJSON_AddNumberToObject( masterTrack, "inputVolume", gui->getMasterTrack()->getInputVolume()->value() ); cJSON_AddNumberToObject( masterTrack, "inputToSndActive", gui->getMasterTrack()->getInputToSend()->value() ); cJSON_AddNumberToObject( masterTrack, "inputToKeyActive", gui->getMasterTrack()->getInputToSidechainKey()->value() ); cJSON_AddNumberToObject( masterTrack, "inputToMixActive", gui->getMasterTrack()->getInputToMix()->value() ); cJSON_AddNumberToObject( masterTrack, "inputToSndVol", gui->getMasterTrack()->getInputToSendVol()->value() ); cJSON_AddNumberToObject( masterTrack, "inputToXSide", gui->getMasterTrack()->getInputToXSide()->value() ); cJSON_AddNumberToObject( masterTrack, "inputToMixVol", gui->getMasterTrack()->getInputToMixVol()->value() ); // scene names Avtk::ClipSelector* clipSelector = master->getClipSelector(); cJSON* sceneNames = cJSON_CreateArray(); cJSON_AddItemToObject( masterTrack, "sceneNames", sceneNames ); for(int i = 0; i < NSCENES; i++) { cJSON* sceneName = cJSON_CreateString( clipSelector->clipName(i).c_str() ); cJSON_AddItemToArray( sceneNames, sceneName ); } } int DiskWriter::writeSession() { if ( !foldersCreated ) { LOOPP_WARN("%s", "Session folders not created yet, while trying to write session."); return LOOPP_RETURN_ERROR; } // add session metadata cJSON_AddItemToObject ( sessionJson, "session", cJSON_CreateString( sessionName.c_str() )); cJSON_AddNumberToObject( sessionJson, "version_major", 1 ); cJSON_AddNumberToObject( sessionJson, "version_minor", 0 ); cJSON_AddNumberToObject( sessionJson, "version_patch", 0 ); writeMaster(); // add JSON "tracks" array cJSON* trackArray = cJSON_CreateArray(); cJSON_AddItemToObject(sessionJson, "tracks", trackArray ); // write tracks into JSON tracks array for(int t = 0; t < NTRACKS; t++) { cJSON* track = cJSON_CreateObject(); cJSON_AddItemToArray( trackArray, track ); // add track metadata: volumes, sends etc cJSON_AddNumberToObject( track, "ID", t ); cJSON_AddStringToObject( track, "name", gui->getTrack(t)->bg.label() ); cJSON_AddNumberToObject( track, "fader", gui->getTrack(t)->getVolume()->value() ); cJSON_AddNumberToObject( track, "pan", gui->getTrack(t)->getPan()); cJSON_AddNumberToObject( track, "sendAmount", gui->getTrack(t)->getSend() ); cJSON_AddNumberToObject( track, "sendActive", gui->getTrack(t)->getSendActive() ); cJSON_AddNumberToObject( track, "jacksendAmount", gui->getTrack(t)->getJackSend() ); cJSON_AddNumberToObject( track, "jacksendActive", gui->getTrack(t)->getJackSendActivate() ); cJSON_AddNumberToObject( track, "xsideAmount", gui->getTrack(t)->getXSide() ); cJSON_AddNumberToObject( track, "keyActive", gui->getTrack(t)->getKeyActive() ); // write clipData vector into clip placeholder cJSON* clips = cJSON_CreateArray(); cJSON_AddItemToObject( track, "clips", clips ); for(int s = 0; s < NSCENES; s++) { // add empty string to array cJSON* clip = cJSON_CreateString( "" ); cJSON_AddItemToArray( clips, clip ); // replace blank string if clip exists for(unsigned int i = 0; i < clipData.size(); i++) { if ( clipData.at(i).track == t && clipData.at(i).scene == s ) { cJSON* newClip = cJSON_CreateString( clipData.at(i).name.c_str() ); cJSON_ReplaceItemInArray( clips, s, newClip ); } } } } stringstream sessionLoopp; sessionLoopp << sessionDir << "/session.loopp"; //c out << "Session dir: " << sessionDir.str() << "\n" << "Sample dir : " << audioDir.str() << endl; ofstream sessionFile; sessionFile.open ( sessionLoopp.str().c_str() ); sessionFile << cJSON_Print( sessionJson ); sessionFile.close(); // write the sample JSON node to /sample.cfg stringstream audioCfg; audioCfg << audioDir << "/audio.cfg"; ofstream audioCfgFile; audioCfgFile.open ( audioCfg.str().c_str() ); audioCfgFile << cJSON_Print( audioJson ); audioCfgFile.close(); // clear the clipData, clean page for next save clipData.clear(); // reset the cJSON objects cJSON_Delete( sessionJson ); cJSON_Delete( audioJson ); sessionJson = cJSON_CreateObject(); audioJson = cJSON_CreateObject(); return LOOPP_RETURN_OK; } void DiskWriter::writeDefaultConfigToUserHome() { LOOPP_NOTE("Writing default preferences file."); cJSON* prfs = cJSON_CreateObject(); // "__COMMENT__" : "users home + " // "saveDirectory" : "loopp" cJSON_AddItemToObject ( prfs, "__COMMENT__", cJSON_CreateString("users home + ") ); cJSON_AddItemToObject ( prfs, "saveDirectory", cJSON_CreateString( "loopp" )); // "__COMMENT__" : "0 = LINEAR, 1 = SINC_FASTEST, 2 = SINC_BEST", // "resampleQuality" : 2 cJSON_AddItemToObject ( prfs, "__COMMENT__", cJSON_CreateString("0 = LINEAR, 1 = SINC_FASTEST, 2 = SINC_BEST") ); cJSON_AddNumberToObject( prfs, "resampleQuality", 1 ); // "defaultControllers" : [], cJSON* defCtrls = cJSON_CreateArray(); cJSON_AddItemToObject( prfs, "defaultControllers", defCtrls ); // per track send and return option cJSON_AddNumberToObject( prfs, "enablePerTrackSendReturns", 0 ); // test output on console // cout << endl << cJSON_Print( prfs ) << endl << endl; // write JSON to .config/soundship/loopp/loopp.prfs stringstream f; f << getenv("HOME") << "/.config/soundship/loopp/loopp.prfs"; ofstream outFile; outFile.open ( f.str().c_str() ); outFile << cJSON_Print( prfs ); outFile.close(); }