Loopp/src/diskreader.cxx
2014-08-10 22:20:57 +01:00

644 lines
18 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "diskreader.hxx"
#include <sstream>
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <libgen.h>
#include <sys/stat.h>
#include "config.hxx"
#include "gui.hxx"
#include "event.hxx"
#include "audiobuffer.hxx"
#include "eventhandler.hxx"
#include "gmastertrack.hxx"
#include "controller/genericmidi.hxx"
#include <sndfile.hh>
#include <samplerate.h>
extern Gui* gui;
using namespace std;
DiskReader::DiskReader()
{
resampleQuality = 1;
// FIXME: could use a config item of sample location?
lastLoadedSamplePath = getenv("HOME");
}
int DiskReader::loadPreferences()
{
stringstream s;
s << getenv("HOME") << "/.config/openAV/luppp/luppp.prfs";
std::ifstream sampleFile( s.str().c_str(), std::ios_base::in|std::ios_base::ate);
long file_length = sampleFile.tellg();
if ( file_length > 0 )
{
sampleFile.seekg(0, std::ios_base::beg);
sampleFile.clear();
char *sampleString = new char[file_length];
sampleFile.read(sampleString, file_length);
cJSON* preferencesJson = cJSON_Parse( sampleString );
if (!preferencesJson)
{
LUPPP_WARN("Preferences JSON not valid");
return LUPPP_RETURN_ERROR;
}
cJSON* resample = cJSON_GetObjectItem( preferencesJson, "resampleQuality" );
if ( resample )
{
resampleQuality = resample->valueint;
if ( resampleQuality == 0 )
{
LUPPP_NOTE("Using Linear resampling, may reduce quality. Check .config/openAV/luppp/luppp.prfs");
}
}
cJSON* ctlrs = cJSON_GetObjectItem( preferencesJson, "defaultControllers" );
if ( ctlrs )
{
//cout << ".ctlr HAS items." << endl;
int nCtlrs = cJSON_GetArraySize( ctlrs );
for(int i = 0; i < nCtlrs; i++ )
{
cJSON* ctlr = cJSON_GetArrayItem( ctlrs, i );
if( ctlr )
{
LUPPP_NOTE("Loading controller %s", ctlr->valuestring);
stringstream s;
s << getenv("HOME") << "/.config/openAV/luppp/controllers/" << ctlr->valuestring;
gui->addMidiControllerToSetup( s.str() );
}
}
}
else
{
LUPPP_NOTE("No default controllers active.");
}
cJSON_Delete( preferencesJson );
delete[] sampleString;
}
else
{
// empty file / file no exists:
LUPPP_WARN("Preferences, file doesn't exist: ~/.config/openAV/luppp/luppp.prefs");
// so write default file
gui->getDiskWriter()->writeDefaultConfigToUserHome();
return LUPPP_RETURN_OK;
}
return LUPPP_RETURN_OK;
}
int DiskReader::showAudioEditor(AudioBuffer* ab)
{
while ( ab->getBeats() == 0 )
{
// FIXME: Cancel = no load sample?
gui->getAudioEditor()->show( ab, true );
while ( gui->getAudioEditor()->shown() ) Fl::wait();
// handle "cancel" return
if ( ab->getBeats() == -1 )
{
return LUPPP_RETURN_ERROR;
}
}
return LUPPP_RETURN_OK;
}
int DiskReader::loadSample( int track, int scene, string path )
{
/// load the sample
SndfileHandle infile( path, SFM_READ );
std::vector<float> buf( infile.frames() * infile.channels() );
infile.read( (float*)&buf[0] , infile.frames() * infile.channels() );
if ( infile.error() )
{
LUPPP_ERROR("File %s, Error %s", path.c_str(), infile.strError() );
return LUPPP_RETURN_ERROR;
}
LUPPP_NOTE("Loading file with %i chnls, frames %i,buffer size %i", infile.channels(), infile.frames(), buf.size() );
/// kick stereo channel?
int chnls = infile.channels();
if ( chnls > 1 )
{
// we're gonna kick all samples that are *not* channel 1
std::vector<float> tmp( buf.size() / chnls );
LUPPP_NOTE("Non mono file: %i chnls found, old size %i, new size %i ", infile.channels(), buf.size(), tmp.size() );
for(size_t i = 0; i < tmp.size(); i++ )
{
tmp.at(i) = buf.at( i * chnls );
}
// swap the buffers
buf.swap( tmp );
}
/// resample?
if ( infile.samplerate() != gui->samplerate )
{
LUPPP_NOTE("%s%i%s%i", "Resampling from ", infile.samplerate(), " to ", gui->samplerate);
float resampleRatio = float(gui->samplerate) / infile.samplerate();
std::vector<float> resampled( infile.frames() * resampleRatio );
SRC_DATA data;
data.data_in = &buf[0];
data.data_out = &resampled[0];
data.input_frames = infile.frames();
data.output_frames = infile.frames() * resampleRatio;
data.end_of_input = 0;
data.src_ratio = resampleRatio;
int q = SRC_SINC_FASTEST;
switch( resampleQuality )
{
case 0: q = SRC_LINEAR; break;
case 1: q = SRC_SINC_FASTEST; break;
case 2: q = SRC_SINC_BEST_QUALITY; break;
}
// resample quality taken from config file,
int ret = src_simple ( &data, q, 1 );
if ( ret == 0 )
LUPPP_NOTE("%s%i%s%i", "Resampling finished, from ", data.input_frames_used, " to ", data.output_frames_gen );
else
LUPPP_ERROR("%s%i%s%i", "Resampling finished, from ", data.input_frames_used, " to ", data.output_frames_gen );
/// exchange buffers, so buf contains the resampled audio
buf.swap( resampled );
}
/// create buffer, and set the data
AudioBuffer* ab = new AudioBuffer();
ab->setAudioFrames( buf.size() );
ab->nonRtSetSample( buf );
//cout << "DiskReader::loadSample() " << path << endl;
bool loadableBuffer = false;
// retrieve sample metadata from sample.cfg using filename as key
char* tmp = strdup( path.c_str() );
char* baseName = basename( tmp );
//cout << "tmp " << tmp << " baseName " << baseName << endl;
ab->setName( baseName );
if ( infile.frames() > 0 )
{
char* basePath = strdup( path.c_str() );
stringstream base;
base << dirname( basePath ) << "/audio.cfg";
free( basePath );
/// open audio.cfg, reading whole file
#ifdef DEBUG_STATE
cout << "loading sample metadata file " << base.str().c_str() << endl;
#endif
std::ifstream sampleFile( base.str().c_str(), std::ios_base::in|std::ios_base::ate);
long file_length = sampleFile.tellg();
if ( file_length > 0 )
{
sampleFile.seekg(0, std::ios_base::beg);
sampleFile.clear();
char *sampleString = new char[file_length];
sampleFile.read(sampleString, file_length);
//cout << "Sample file:" << endl << sampleString << endl;
//cout << "Sample file (parsed):" << endl << cJSON_Parse( sampleString ) << endl;
cJSON* audioJson = cJSON_Parse( sampleString );
if (!audioJson) {
LUPPP_ERROR("%s %s","Error in Sample JSON before: ", cJSON_GetErrorPtr() );
return LUPPP_RETURN_ERROR;
}
cJSON* sample = cJSON_GetObjectItem( audioJson, baseName );
if ( sample )
{
cJSON* beats = cJSON_GetObjectItem( sample, "beats" );
cJSON* name = cJSON_GetObjectItem( sample, "name" );
//cout << "Clip @ " << track << " " << scene << " gets " << beats->valuedouble << " beats."<< endl;
if ( beats )
{
loadableBuffer = true;
ab->setBeats( beats->valuedouble );
}
if ( name )
{
ab->setName( name->valuestring );
}
}
// if we don't find the beats from audio.cfg, show dialog
if ( loadableBuffer == false )
{
LUPPP_NOTE("Warning: audio.cfg has no entry for beats.");
int ret = showAudioEditor( ab );
if ( ret == LUPPP_RETURN_OK )
{
// flag that we can load this sample OK
loadableBuffer = true;
}
else
{
delete ab;
}
}
cJSON_Delete( audioJson );
delete[] sampleString;
free ( tmp );
}
else
{
// this means there's no audio.cfg file found for the sample: show the user
// the file, and ask what the intended beat number is, and load the AudioBuffer
LUPPP_WARN("%s %s","Empty or no audio.cfg found at ",base.str().c_str() );
int error = showAudioEditor( ab );
if ( error == LUPPP_RETURN_ERROR )
{
LUPPP_WARN("cancel clicked, deleting audiobuffer" );
delete ab;
}
else
{
std::string name = path;
int i = name.find_last_of('/') + 1;
std::string sub = name.substr( i );
ab->setName( sub.c_str() );
LUPPP_NOTE("AudioBuffer %s set %i beats", ab->getName(), ab->getBeats() );
loadableBuffer = true;
}
}
if ( loadableBuffer )
{
std::string n = ab->getName();
// write audioBuffer to DSP
EventLooperLoad e = EventLooperLoad( track, scene, ab );
writeToDspRingbuffer( &e );
// now write audiobuffer name to GUI on track, scene
gui->getTrack( track )->getClipSelector()->clipName( scene, n );
char* tmp = strdup( path.c_str() );
lastLoadedSamplePath = dirname( tmp );
free(tmp);
}
else
{
LUPPP_NOTE("AudioBuffer not loaded, missing beats info and dialog was Canceled" );
}
}
return LUPPP_RETURN_OK;
}
std::string DiskReader::getLastLoadedSamplePath()
{
return lastLoadedSamplePath;
}
int DiskReader::readSession( std::string path )
{
cout << "DiskReader::readSession() " << path << endl;
sessionPath = path;
stringstream s;
s << path << "/session.luppp";
stringstream samplePath;
samplePath << path << "/audio/audio.cfg";
cout << "session path: " << s.str() << endl;
cout << "audio path: " << samplePath.str() << endl;
// open session, read all
std::ifstream file( s.str().c_str(), std::ios_base::in|std::ios_base::ate);
long file_length = file.tellg();
if ( file_length < 0 )
{
// empty file / file no exists:
LUPPP_ERROR("no session file exists!");
return LUPPP_RETURN_ERROR;
}
file.seekg(0, std::ios_base::beg);
file.clear();
char *sessionString = new char[file_length];
file.read(sessionString, file_length);
// create cJSON nodes from strings
sessionJson = cJSON_Parse( sessionString );
if (!sessionJson) {
LUPPP_ERROR("%s %s", "Error in Session JSON before: ", cJSON_GetErrorPtr() );
return LUPPP_RETURN_ERROR;
}
int tr = readTracks();
int mr = readMaster();
if( tr == mr ){};
// cleanup
cJSON_Delete( sessionJson );
delete[] sessionString;
return LUPPP_RETURN_OK;
}
int DiskReader::readMaster()
{
cJSON* master = cJSON_GetObjectItem( sessionJson, "master");
if ( master )
{
// bpm
{
cJSON* bpm = cJSON_GetObjectItem( master, "bpm");
if ( bpm ) {
LUPPP_NOTE("%s %i","Session: BPM ",bpm->valueint);
EventTimeBPM e( bpm->valuedouble );
writeToDspRingbuffer( &e );
}
}
// fader
{
cJSON* fader = cJSON_GetObjectItem( master, "fader");
if ( fader ) {
EventTrackVol e( -1, fader->valuedouble );
writeToDspRingbuffer( &e );
}
}
// input volume
{
cJSON* cjson = cJSON_GetObjectItem( master, "inputVolume");
if ( cjson ) {
EventMasterInputVol e( cjson->valuedouble );
writeToDspRingbuffer( &e ); }
}
// input to send
{
cJSON* cjson = cJSON_GetObjectItem( master, "inputToSndVol");
if ( cjson ) {
EventMasterInputTo e( INPUT_TO_SEND, cjson->valuedouble );
writeToDspRingbuffer( &e ); }
}
// input to key
{
cJSON* cjson = cJSON_GetObjectItem( master, "inputToXSide");
if ( cjson ) {
EventMasterInputTo e( INPUT_TO_XSIDE, cjson->valuedouble );
writeToDspRingbuffer( &e ); }
}
// input to mix
{
cJSON* cjson = cJSON_GetObjectItem( master, "inputToMixVol");
if ( cjson ) {
EventMasterInputTo e( INPUT_TO_MIX, cjson->valuedouble );
writeToDspRingbuffer( &e ); }
}
// input to send active
{
cJSON* cjson = cJSON_GetObjectItem( master, "inputToSndActive");
if ( cjson ) {
EventMasterInputTo e( INPUT_TO_SEND, cjson->valuedouble );
writeToDspRingbuffer( &e ); }
}
// input to key active
{
cJSON* cjson = cJSON_GetObjectItem( master, "inputToKeyActive");
if ( cjson ) {
EventMasterInputTo e( INPUT_TO_SIDE_KEY, cjson->valuedouble );
writeToDspRingbuffer( &e ); }
}
// input to mix active
{
cJSON* cjson = cJSON_GetObjectItem( master, "inputToMixActive");
if ( cjson ) {
EventMasterInputTo e( INPUT_TO_MIX, cjson->valuedouble );
writeToDspRingbuffer( &e ); }
}
// reverb
{
cJSON* reverb = cJSON_GetObjectItem( master, "reverb");
if ( reverb )
{
cJSON* active = cJSON_GetObjectItem( reverb, "active");
cJSON* size = cJSON_GetObjectItem( reverb, "size");
cJSON* wet = cJSON_GetObjectItem( reverb, "wet");
cJSON* damping = cJSON_GetObjectItem( reverb, "damping");
if ( active && size && wet && damping )
{
EventFxReverb e( active->valuedouble, size->valuedouble,
wet->valuedouble, damping->valuedouble );
writeToDspRingbuffer( &e );
}
}
else
{
#ifdef DEBUG_STATE
cout << "Session has no reverb element" << endl;
#endif
}
}
// TODO add samplerate to session JSON
// sceneNames
{
cJSON* names = cJSON_GetObjectItem( master, "sceneNames");
if ( names )
{
GMasterTrack* master = gui->getMasterTrack();
Avtk::ClipSelector* clipSelector = master->getClipSelector();
int nscenes = cJSON_GetArraySize( names );
for(int s = 0; s < nscenes; s++ )
{
cJSON* name = cJSON_GetArrayItem( names, s );
clipSelector->clipName( s, name->valuestring );
}
clipSelector->redraw();
}
}
}
else
{
LUPPP_ERROR("%s", "Error getting master from JSON" );
return LUPPP_RETURN_ERROR;
}
return LUPPP_RETURN_OK;
}
int DiskReader::readScenes(int t, cJSON* track)
{
cJSON* clips = cJSON_GetObjectItem( track, "clips");
if ( clips )
{
int nClips = cJSON_GetArraySize( clips );
for(int s = 0; s < nClips; s++ )
{
// get metadata for Clip
cJSON* clip = cJSON_GetArrayItem( clips, s );
if ( strcmp(clip->valuestring, "") != 0 )
{
stringstream sampleFilePath;
sampleFilePath << sessionPath << "/audio/" << clip->valuestring;
#ifdef DEBUG_STATE
LUPPP_NOTE << "clip t " << t << " s " << s << " path " << sampleFilePath.str() << endl;
#endif
// load it, checking for sample.cfg, and using metadata if there
loadSample( t, s, sampleFilePath.str() );
}
// FIXME: check GUI ringbuffer after each sample: with resampling it can
// take quite some time, and the ->GUI ringbuffer can fill up
handleGuiEvents();
} // nClips loop
}
return LUPPP_RETURN_OK;
}
int DiskReader::readTracks()
{
cJSON* tracks = cJSON_GetObjectItem( sessionJson, "tracks");
if ( tracks )
{
int nTracks = cJSON_GetArraySize( tracks );
for(int t = 0; t < nTracks; t++ )
{
cJSON* track = cJSON_GetArrayItem( tracks, t );
if( !track )
{
LUPPP_WARN("Track %i has no name track saved.", t);
}
else
{
readScenes( t, track );
// name
{
cJSON* name = cJSON_GetObjectItem( track, "name");
if( !name )
{
LUPPP_WARN("Track %i has no name data saved.", t);
}
else
{
gui->getTrack(t)->bg.setLabel( name->valuestring );
}
}
// fader
{
cJSON* fader = cJSON_GetObjectItem( track, "fader");
if( !fader )
{
LUPPP_WARN("Track %i has no fader data saved.", t);
}
else
{
EventTrackVol e( t, fader->valuedouble );
writeToDspRingbuffer( &e );
}
}
// sends
{
cJSON* send = cJSON_GetObjectItem( track, "sendAmount");
cJSON* sendActive = cJSON_GetObjectItem( track, "sendActive");
cJSON* xside = cJSON_GetObjectItem( track, "xsideAmount");
cJSON* keyActive = cJSON_GetObjectItem( track, "keyActive");
if( !send || !sendActive || !xside || !keyActive )
{
LUPPP_WARN("Track %i has no send data saved.", t);
}
else
{
EventTrackSendActive e1( t, SEND_POSTFADER, sendActive->valueint );
EventTrackSendActive e2( t, SEND_KEY , keyActive ->valueint );
EventTrackSend e3( t, SEND_XSIDE , xside->valuedouble );
EventTrackSend e4( t, SEND_POSTFADER , send->valuedouble );
writeToDspRingbuffer( &e1 );
writeToDspRingbuffer( &e2 );
writeToDspRingbuffer( &e3 );
}
}
}// if track
} // nTracks loop
}
else
{
LUPPP_ERROR("%s", "Error getting clip" );
return LUPPP_RETURN_ERROR;
}
return LUPPP_RETURN_OK;
}