<commit_before>/**
* projectM -- Milkdrop-esque visualisation SDK
* Copyright (C)2003-2004 projectM Team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
* See 'LICENSE.txt' included within this release
*
*/

#include "RenderItemMatcher.hpp"
#include "RenderItemMergeFunction.hpp"
#include "fatal.h"
#include "Common.hpp"

#ifdef WIN32
#include "dirent.h"
#endif

#include "timer.h"
#include <iostream>
#ifdef __unix__
#include "time.h"
#endif

#ifdef WIN32
#include <time.h>
#endif
#include "PipelineContext.hpp"
#include <iostream>
#include "projectM.hpp"
#include "BeatDetect.hpp"
#include "Preset.hpp"
#include "PipelineMerger.hpp"
#include "PCM.hpp"                    //Sound data handler (buffering, FFT, etc.)

#include <map>

#include "Renderer.hpp"
#include "PresetChooser.hpp"
#include "ConfigFile.h"
#include "TextureManager.hpp"
#include "TimeKeeper.hpp"
#include "RenderItemMergeFunction.hpp"

#ifdef USE_THREADS
#include "pthread.h"

#include "BackgroundWorker.h"

pthread_t thread;
BackgroundWorkerSync worker_sync;

#ifdef SYNC_PRESET_SWITCHES
pthread_mutex_t preset_mutex;
#endif
#endif

projectM::~projectM()
{
#ifdef USE_THREADS
    void *status;
    worker_sync.finish_up();
    pthread_join(thread, &status);
    #ifdef SYNC_PRESET_SWITCHES
    pthread_mutex_destroy( &preset_mutex );
    #endif
    std::cout << std::endl;
#endif
    destroyPresetTools();

    if ( renderer )
        delete ( renderer );
    if ( beatDetect )
        delete ( beatDetect );
    if ( _pcm ) {
        delete ( _pcm );
        _pcm = 0;
    }

    if(timeKeeper) {
        delete timeKeeper;
        timeKeeper = NULL;
    }

    delete(_pipelineContext);
    delete(_pipelineContext2);
}

unsigned projectM::initRenderToTexture()
{
    return renderer->initRenderToTexture();
}

void projectM::projectM_resetTextures()
{
    renderer->ResetTextures();
}


projectM::projectM ( std::string config_file, int flags) :
        _pcm(0), beatDetect ( 0 ), renderer ( 0 ), _pipelineContext(new PipelineContext()), _pipelineContext2(new PipelineContext()), m_presetPos(0),
        timeKeeper(NULL), m_flags(flags), _matcher(NULL), _merger(NULL)
{
    readConfig(config_file);
    projectM_reset();
    projectM_resetGL(_settings.windowWidth, _settings.windowHeight);

}

projectM::projectM(Settings settings, int flags):
        _pcm(0), beatDetect ( 0 ), renderer ( 0 ), _pipelineContext(new PipelineContext()), _pipelineContext2(new PipelineContext()), m_presetPos(0),
        timeKeeper(NULL), m_flags(flags), _matcher(NULL), _merger(NULL)
{
    readSettings(settings);
    projectM_reset();
    projectM_resetGL(_settings.windowWidth, _settings.windowHeight);
}


bool projectM::writeConfig(const std::string & configFile, const Settings & settings) {

    ConfigFile config ( configFile );

    config.add("Mesh X", settings.meshX);
    config.add("Mesh Y", settings.meshY);
    config.add("Texture Size", settings.textureSize);
    config.add("FPS", settings.fps);
    config.add("Window Width", settings.windowWidth);
    config.add("Window Height", settings.windowHeight);
    config.add("Smooth Preset Duration", settings.smoothPresetDuration);
    config.add("Preset Duration", settings.presetDuration);
    config.add("Preset Path", settings.presetURL);
    config.add("Title Font", settings.titleFontURL);
    config.add("Menu Font", settings.menuFontURL);
    config.add("Hard Cut Sensitivity", settings.beatSensitivity);
    config.add("Aspect Correction", settings.aspectCorrection);
    config.add("Easter Egg Parameter", settings.easterEgg);
    config.add("Shuffle Enabled", settings.shuffleEnabled);
    config.add("Soft Cut Ratings Enabled", settings.softCutRatingsEnabled);
    std::fstream file(configFile.c_str());
    if (file) {
        file << config;
        return true;
    } else
        return false;
}



void projectM::readConfig (const std::string & configFile )
{
    std::cout << "[projectM] config file: " << configFile << std::endl;

    ConfigFile config ( configFile );
    _settings.meshX = config.read<int> ( "Mesh X", 32 );
    _settings.meshY = config.read<int> ( "Mesh Y", 24 );
    _settings.textureSize = config.read<int> ( "Texture Size", 512 );
    _settings.fps = config.read<int> ( "FPS", 35 );
    _settings.windowWidth  = config.read<int> ( "Window Width", 512 );
    _settings.windowHeight = config.read<int> ( "Window Height", 512 );
    _settings.smoothPresetDuration =  config.read<int>
            ( "Smooth Preset Duration", config.read<int>("Smooth Transition Duration", 10));
    _settings.presetDuration = config.read<int> ( "Preset Duration", 15 );

#ifdef __unix__
    _settings.presetURL = config.read<string> ( "Preset Path", "/usr/local/share/projectM/presets" );
#endif

#ifdef __APPLE__
    /// @bug awful hardcoded hack- need to add intelligence to cmake wrt bundling - carm
    _settings.presetURL = config.read<string> ( "Preset Path", "../Resources/presets" );
#endif

#ifdef WIN32
    _settings.presetURL = config.read<string> ( "Preset Path", "/usr/local/share/projectM/presets" );
#endif

#ifdef __APPLE__
    _settings.titleFontURL = config.read<string>
    ( "Title Font",  "../Resources/fonts/Vera.tff");
    _settings.menuFontURL = config.read<string>
    ( "Menu Font", "../Resources/fonts/VeraMono.ttf");
#endif

#ifdef __unix__
    _settings.titleFontURL = config.read<string>
            ( "Title Font", "/usr/local/share/projectM/fonts/Vera.tff" );
    _settings.menuFontURL = config.read<string>
            ( "Menu Font", "/usr/local/share/projectM/fonts/VeraMono.tff" );
#endif

#ifdef WIN32
    _settings.titleFontURL = config.read<string>
    ( "Title Font", projectM_FONT_TITLE );
    _settings.menuFontURL = config.read<string>
    ( "Menu Font", projectM_FONT_MENU );
#endif


    _settings.shuffleEnabled = config.read<bool> ( "Shuffle Enabled", true);

    _settings.easterEgg = config.read<float> ( "Easter Egg Parameter", 0.0);
    _settings.softCutRatingsEnabled =
            config.read<bool> ( "Soft Cut Ratings Enabled", false);

    // Hard Cuts are preset transitions that occur when your music becomes louder. They only occur after a hard cut duration threshold has passed.
    _settings.hardcutEnabled = config.read<bool> ( "Hard Cuts Enabled", false );
    // Hard Cut duration is the number of seconds before you become eligible for a hard cut.
    _settings.hardcutDuration = config.read<int> ( "Hard Cut Duration", 60 );
    // Hard Cut sensitivity is the volume difference before a "hard cut" is triggered.
    _settings.hardcutSensitivity = config.read<float> ( "Hard Cut Sensitivity", 1.0 );
    
    // Beat Sensitivity impacts how reactive your visualizations are to volume, bass, mid-range, and treble. 
    // Preset authors have developed their visualizations with the default of 1.0.
    _settings.beatSensitivity = config.read<float> ( "Beat Sensitivity", 1.0 );


    projectM_init ( _settings.meshX, _settings.meshY, _settings.fps,
                _settings.textureSize, _settings.windowWidth,_settings.windowHeight);
    
    if ( config.read ( "Aspect Correction", true ) )
    {
        _settings.aspectCorrection = true;
        renderer->correction = true;
    }
    else
    {
        _settings.aspectCorrection = false;
        renderer->correction = false;
    }


}


void projectM::readSettings (const Settings & settings )
{
    _settings.meshX = settings.meshX;
    _settings.meshY = settings.meshY;
    _settings.textureSize = settings.textureSize;
    _settings.fps = settings.fps;
    _settings.windowWidth  = settings.windowWidth;
    _settings.windowHeight = settings.windowHeight;
    _settings.smoothPresetDuration = settings.smoothPresetDuration;
    _settings.presetDuration = settings.presetDuration;
    _settings.softCutRatingsEnabled = settings.softCutRatingsEnabled;

    _settings.presetURL = settings.presetURL;
    _settings.titleFontURL = settings.titleFontURL;
    _settings.menuFontURL =  settings.menuFontURL;
    _settings.shuffleEnabled = settings.shuffleEnabled;
    _settings.datadir = settings.datadir;

    _settings.easterEgg = settings.easterEgg;

    _settings.hardcutEnabled = settings.hardcutEnabled;
    _settings.hardcutDuration = settings.hardcutDuration;
    _settings.hardcutSensitivity = settings.hardcutSensitivity;
    
    _settings.beatSensitivity = settings.beatSensitivity;
    
    projectM_init ( _settings.meshX, _settings.meshY, _settings.fps,
                    _settings.textureSize, _settings.windowWidth,_settings.windowHeight);
    

    _settings.aspectCorrection = settings.aspectCorrection;
}

#ifdef USE_THREADS
static void *thread_callback(void *prjm)
{
    projectM *p = (projectM *)prjm;
    p->thread_func(prjm);
    return NULL;
}


void *projectM::thread_func(void *vptr_args)
{
    //  printf("in thread: %f\n", timeKeeper->PresetProgressB());
    while (true)
    {
        if (!worker_sync.wait_for_work())
            return NULL;
        evaluateSecondPreset();
        worker_sync.finished_work();
    }
}
#endif

void projectM::evaluateSecondPreset()
{
    pipelineContext2().time = timeKeeper->GetRunningTime();
    pipelineContext2().presetStartTime = timeKeeper->PresetTimeB();
    pipelineContext2().frame = timeKeeper->PresetFrameB();
    pipelineContext2().progress = timeKeeper->PresetProgressB();

    m_activePreset2->Render(*beatDetect, pipelineContext2());
}

void projectM::renderFrame()
{
    Pipeline pipeline;
    Pipeline *comboPipeline;
    
    comboPipeline = renderFrameOnlyPass1(&pipeline);
    
    renderFrameOnlyPass2(comboPipeline,0,0,0);
    
    projectM::renderFrameEndOnSeparatePasses(comboPipeline);
}








Pipeline * projectM::renderFrameOnlyPass1(Pipeline *pPipeline) /*pPipeline is a pointer to a Pipeline for use in pass 2. returns the pointer if it was used, else returns NULL */
{
#ifdef SYNC_PRESET_SWITCHES
    pthread_mutex_lock(&preset_mutex);
#endif

#ifdef DEBUG
    char fname[1024];
    FILE *f = NULL;
    int index = 0;
    int x, y;
#endif

    timeKeeper->UpdateTimers();
/*
    if (timeKeeper->IsSmoothing())
    {
        printf("Smoothing A:%f, B:%f, S:%f\n", timeKeeper->PresetProgressA(), timeKeeper->PresetProgressB(), timeKeeper->SmoothRatio());
    }
    else
    {
        printf("          A:%f\n", timeKeeper->PresetProgressA());
    }*/

    mspf= ( int ) ( 1000.0/ ( float ) settings().fps ); //milliseconds per frame

    /// @bug who is responsible for updating this now?"
    pipelineContext().time = timeKeeper->GetRunningTime();
    pipelineContext().presetStartTime = timeKeeper->PresetTimeA();
    pipelineContext().frame = timeKeeper->PresetFrameA();
    pipelineContext().progress = timeKeeper->PresetProgressA();

    beatDetect->detectFromSamples();

    //m_activePreset->evaluateFrame();

    //if the preset isn't locked and there are more presets
    if ( renderer->noSwitch==false && !m_presetChooser->empty() )
    {
        //if preset is done and we're not already switching
        if ( timeKeeper->PresetProgressA()>=1.0 && !timeKeeper->IsSmoothing())
        {
            if (settings().shuffleEnabled)
                selectRandom(false);
            else
                selectNext(false);
        } else if (settings().hardcutEnabled && (beatDetect->vol-beatDetect->vol_old>settings().hardcutSensitivity) && timeKeeper->CanHardCut())
        {
            // Hard Cuts must be enabled, must have passed the hardcut duration, and the volume must be a greater difference than the hardcut sensitivity.
            if (settings().shuffleEnabled)
                selectRandom(true);
            else
                selectNext(true);
        }
    }


    if ( timeKeeper->IsSmoothing() && timeKeeper->SmoothRatio() <= 1.0 && !m_presetChooser->empty() )
    {
        //	 printf("start thread\n");
        assert ( m_activePreset2.get() );

#ifdef USE_THREADS
        worker_sync.wake_up_bg();
#endif

        m_activePreset->Render(*beatDetect, pipelineContext());

#ifdef USE_THREADS
        worker_sync.wait_for_bg_to_finish();
#else
        evaluateSecondPreset();
#endif


        pPipeline->setStaticPerPixel(settings().meshX, settings().meshY);

        assert(_matcher);
        PipelineMerger::mergePipelines( m_activePreset->pipeline(),
                                        m_activePreset2->pipeline(), *pPipeline,
                                        _matcher->matchResults(),
                                        *_merger, timeKeeper->SmoothRatio());

        renderer->RenderFrameOnlyPass1(*pPipeline, pipelineContext());


        return pPipeline;

    }
    else
    {


        if ( timeKeeper->IsSmoothing() && timeKeeper->SmoothRatio() > 1.0 )
        {
            //printf("End Smooth\n");
            m_activePreset = std::move(m_activePreset2);
            timeKeeper->EndSmoothing();
        }
        //printf("Normal\n");

        m_activePreset->Render(*beatDetect, pipelineContext());
        renderer->RenderFrameOnlyPass1 (m_activePreset->pipeline(), pipelineContext());
	return NULL; // indicating no transition

    }

    //	std::cout<< m_activePreset->absoluteFilePath()<<std::endl;
    //	renderer->presetName = m_activePreset->absoluteFilePath();




}





/* eye is 0,or 1, or who knows?*/
void projectM::renderFrameOnlyPass2(Pipeline *pPipeline,int xoffset,int yoffset,int eye) /*pPipeline can be null if we re not in transition */
{
/* eye is currently ignored */


#ifdef DEBUG
    char fname[1024];
    FILE *f = NULL;
    int index = 0;
    int x, y;
#endif

    if (pPipeline) 
//    if ( timeKeeper->IsSmoothing() && timeKeeper->SmoothRatio() <= 1.0 && !m_presetChooser->empty() )
    {
        //	 printf("start thread\n");
        assert ( m_activePreset2.get() );


        /* was other stuff */
	
        renderer->RenderFrameOnlyPass2(*pPipeline, pipelineContext(),xoffset,yoffset,eye);

    }
    else
    {


        renderer->RenderFrameOnlyPass2 (m_activePreset->pipeline(), pipelineContext(),xoffset,yoffset,eye);


    }

}




void projectM::renderFrameEndOnSeparatePasses(Pipeline *pPipeline) {

    if (pPipeline) {
       // mergePipelines() sets masterAlpha for each RenderItem, reset it before we forget
       for (RenderItem *drawable : pPipeline->drawables) {
           drawable->masterAlpha = 1.0;
       }
    pPipeline->drawables.clear();
    }
  
    count++;
#ifndef WIN32
    /** Frame-rate limiter */
    /** Compute once per preset */
    if ( this->count%100==0 )
    {
        this->renderer->realfps=100.0/ ( ( getTicks ( &timeKeeper->startTime )-this->fpsstart ) /1000 );
        this->fpsstart=getTicks ( &timeKeeper->startTime );
    }

#ifndef UNLOCK_FPS
    int timediff = getTicks ( &timeKeeper->startTime )-this->timestart;

    if ( timediff < this->mspf )
    {
        // printf("%s:",this->mspf-timediff);
        int sleepTime = ( unsigned int ) ( this->mspf-timediff ) * 1000;
        //		DWRITE ( "usleep: %d\n", sleepTime );
        if ( sleepTime > 0 && sleepTime < 100000 )
        {
            if ( usleep ( sleepTime ) != 0 ) {}}
    }
    this->timestart=getTicks ( &timeKeeper->startTime );
#endif

#endif /** !WIN32 */
#ifdef SYNC_PRESET_SWITCHES
    pthread_mutex_unlock(&preset_mutex);
#endif
return;
}

void projectM::projectM_reset()
{
    this->mspf = 0;
    this->timed = 0;
    this->timestart = 0;
    this->count = 0;

    this->fpsstart = 0;

    projectM_resetengine();
}

void projectM::projectM_init ( int gx, int gy, int fps, int texsize, int width, int height )
{
    /** Initialise start time */
    timeKeeper = new TimeKeeper(_settings.presetDuration,_settings.smoothPresetDuration, _settings.hardcutDuration, _settings.easterEgg);

    /** Nullify frame stash */

    /** Initialise per-pixel matrix calculations */
    /** We need to initialise this before the builtin param db otherwise bass/mid etc won't bind correctly */
    assert ( !beatDetect );

    if (!_pcm)
        _pcm = new PCM();
    assert(pcm());
    beatDetect = new BeatDetect ( _pcm );

    if ( _settings.fps > 0 )
        mspf= ( int ) ( 1000.0/ ( float ) _settings.fps );
    else mspf = 0;

    this->renderer = new Renderer ( width, height, gx, gy, beatDetect, settings().presetURL, settings().titleFontURL, settings().menuFontURL, settings().datadir );

    initPresetTools(gx, gy);


#ifdef USE_THREADS

    #ifdef SYNC_PRESET_SWITCHES
    pthread_mutex_init(&preset_mutex, NULL);
#endif

    worker_sync.reset();
    if (pthread_create(&thread, NULL, thread_callback, this) != 0)
    {

        std::cerr << "[projectM] failed to allocate a thread! try building with option USE_THREADS turned off" << std::endl;;
        exit(EXIT_FAILURE);
    }
#endif

    /// @bug order of operatoins here is busted
    //renderer->setPresetName ( m_activePreset->name() );
    timeKeeper->StartPreset();
    assert(pcm());

    pipelineContext().fps = fps;
    pipelineContext2().fps = fps;

}

/* Reinitializes the engine variables to a default (conservative and sane) value */
void projectM::projectM_resetengine()
{

    if ( beatDetect != NULL )
    {
        beatDetect->reset();
        beatDetect->beatSensitivity = _settings.beatSensitivity;
    }

}

/** Resets OpenGL state */
void projectM::projectM_resetGL ( int w, int h )
{
    /** sanity check **/
    assert(w > 0);
    assert(h > 0);

    /** Stash the new dimensions */
    _settings.windowWidth = w;
    _settings.windowHeight = h;
    renderer->reset ( w,h );
}

/** Sets the title to display */
void projectM::projectM_setTitle ( std::string title ) {

    if ( title != renderer->title )
    {
        renderer->title=title;
        renderer->drawtitle=1;
    }
}


int projectM::initPresetTools(int gx, int gy)
{

    /* Set the seed to the current time in seconds */
    srand ( time ( NULL ) );

    std::string url = (m_flags & FLAG_DISABLE_PLAYLIST_LOAD) ? std::string() : settings().presetURL;

    if ( ( m_presetLoader = new PresetLoader ( gx, gy, url) ) == 0 )
    {
        m_presetLoader = 0;
        std::cerr << "[projectM] error allocating preset loader" << std::endl;
        return PROJECTM_FAILURE;
    }

    if ( ( m_presetChooser = new PresetChooser ( *m_presetLoader, settings().softCutRatingsEnabled ) ) == 0 )
    {
        delete ( m_presetLoader );

        m_presetChooser = 0;
        m_presetLoader = 0;

        std::cerr << "[projectM] error allocating preset chooser" << std::endl;
        return PROJECTM_FAILURE;
    }

    // Start the iterator
    if (!m_presetPos)
        m_presetPos = new PresetIterator();

    // Initialize a preset queue position as well
    //	m_presetQueuePos = new PresetIterator();

    // Start at end ptr- this allows next/previous to easily be done from this position.
    *m_presetPos = m_presetChooser->end();

    // Load idle preset
//        std::cerr << "[projectM] Allocating idle preset..." << std::endl;
    m_activePreset = m_presetLoader->loadPreset
            ("idle://Geiss & Sperl - Feedback (projectM idle HDR mix).milk");
	renderer->setPresetName("Geiss & Sperl - Feedback (projectM idle HDR mix)");

    renderer->SetPipeline(m_activePreset->pipeline());

    // Case where no valid presets exist in directory. Could also mean
    // playlist initialization was deferred
    if (m_presetChooser->empty())
    {
        //std::cerr << "[projectM] warning: no valid files found in preset directory \""
        ///< m_presetLoader->directoryName() << "\"" << std::endl;
    }

    _matcher = new RenderItemMatcher();
    _merger = new MasterRenderItemMerge();
    //_merger->add(new WaveFormMergeFunction());
    _merger->add(new ShapeMerge());
    _merger->add(new BorderMerge());
    //_merger->add(new BorderMergeFunction());

    /// @bug These should be requested by the preset factories.
    _matcher->distanceFunction().addMetric(new ShapeXYDistance());

    //std::cerr << "[projectM] Idle preset allocated." << std::endl;

    projectM_resetengine();

    //std::cerr << "[projectM] engine has been reset." << std::endl;
    return PROJECTM_SUCCESS;
}

void projectM::destroyPresetTools()
{

    if ( m_presetPos )
        delete ( m_presetPos );

    m_presetPos = 0;

    if ( m_presetChooser )
        delete ( m_presetChooser );

    m_presetChooser = 0;

    if ( m_presetLoader )
        delete ( m_presetLoader );

    m_presetLoader = 0;

    if (_matcher) {
        delete _matcher;
        _matcher = NULL;
    }

    if (_merger) {
        delete _merger;
        _merger = NULL;
    }
}

/// @bug queuePreset case isn't handled
void projectM::removePreset(unsigned int index) {

    size_t chooserIndex = **m_presetPos;

    m_presetLoader->removePreset(index);


    // Case: no more presets, set iterator to end
    if (m_presetChooser->empty())
        *m_presetPos = m_presetChooser->end();

        // Case: chooser index has become one less due to removal of an index below it
    else if (chooserIndex > index) {
        chooserIndex--;
        *m_presetPos = m_presetChooser->begin(chooserIndex);
    }

        // Case: we have deleted the active preset position
        // Set iterator to end of chooser
    else if (chooserIndex == index) {
        *m_presetPos = m_presetChooser->end();
    }



}

unsigned int projectM::addPresetURL ( const std::string & presetURL, const std::string & presetName, const RatingList & ratings)
{
    bool restorePosition = false;

    if (*m_presetPos == m_presetChooser->end())
        restorePosition = true;

    int index = m_presetLoader->addPresetURL ( presetURL, presetName, ratings);

    if (restorePosition)
        *m_presetPos = m_presetChooser->end();

    return index;
}

void projectM::selectPreset(unsigned int index, bool hardCut)
{

    if (m_presetChooser->empty())
        return;


    *m_presetPos = m_presetChooser->begin(index);
    switchPreset(hardCut);
}

void projectM::switchPreset(const bool hardCut) {
    std::string result;

    if (!hardCut) {
        result = switchPreset(m_activePreset2);
    } else {
        result = switchPreset(m_activePreset);
        if (result.empty())
            timeKeeper->StartPreset();
    }

    if (result.empty() && !hardCut) {
        timeKeeper->StartSmoothing();
    }

    if (result.empty()) {
        presetSwitchedEvent(hardCut, **m_presetPos);
        errorLoadingCurrentPreset = false;
    } else {
        presetSwitchFailedEvent(hardCut, **m_presetPos, result);
        errorLoadingCurrentPreset = true;

    }
}


void projectM::selectRandom(const bool hardCut) {
    if (m_presetChooser->empty())
        return;
    presetHistory.push_back(m_presetPos->lastIndex());
    presetFuture.clear();
    *m_presetPos = m_presetChooser->weightedRandom(hardCut);

    switchPreset(hardCut);

}

void projectM::selectPrevious(const bool hardCut) {
    if (m_presetChooser->empty())
        return;

    if (settings().shuffleEnabled && presetHistory.size() >= 1 && presetHistory.back() != m_presetLoader->size()) { // if randomly browsing presets, "previous" should return to last random preset not the index--. Avoid returning to size() because that's the idle:// preset.
        presetFuture.push_back(m_presetPos->lastIndex());
        selectPreset(presetHistory.back());
        presetHistory.pop_back();
    }
    else {
        // if we are not shuffling or there is no random future history, then let's not track a random vector and move fowards in the preset index.
        presetHistory.clear();
        presetFuture.clear();<commit_msg>Cap preset random history to 10.<commit_after>/**
* projectM -- Milkdrop-esque visualisation SDK
* Copyright (C)2003-2004 projectM Team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
* See 'LICENSE.txt' included within this release
*
*/

#include "RenderItemMatcher.hpp"
#include "RenderItemMergeFunction.hpp"
#include "fatal.h"
#include "Common.hpp"

#ifdef WIN32
#include "dirent.h"
#endif

#include "timer.h"
#include <iostream>
#ifdef __unix__
#include "time.h"
#endif

#ifdef WIN32
#include <time.h>
#endif
#include "PipelineContext.hpp"
#include <iostream>
#include "projectM.hpp"
#include "BeatDetect.hpp"
#include "Preset.hpp"
#include "PipelineMerger.hpp"
#include "PCM.hpp"                    //Sound data handler (buffering, FFT, etc.)

#include <map>

#include "Renderer.hpp"
#include "PresetChooser.hpp"
#include "ConfigFile.h"
#include "TextureManager.hpp"
#include "TimeKeeper.hpp"
#include "RenderItemMergeFunction.hpp"

#ifdef USE_THREADS
#include "pthread.h"

#include "BackgroundWorker.h"

pthread_t thread;
BackgroundWorkerSync worker_sync;

#ifdef SYNC_PRESET_SWITCHES
pthread_mutex_t preset_mutex;
#endif
#endif

projectM::~projectM()
{
#ifdef USE_THREADS
    void *status;
    worker_sync.finish_up();
    pthread_join(thread, &status);
    #ifdef SYNC_PRESET_SWITCHES
    pthread_mutex_destroy( &preset_mutex );
    #endif
    std::cout << std::endl;
#endif
    destroyPresetTools();

    if ( renderer )
        delete ( renderer );
    if ( beatDetect )
        delete ( beatDetect );
    if ( _pcm ) {
        delete ( _pcm );
        _pcm = 0;
    }

    if(timeKeeper) {
        delete timeKeeper;
        timeKeeper = NULL;
    }

    delete(_pipelineContext);
    delete(_pipelineContext2);
}

unsigned projectM::initRenderToTexture()
{
    return renderer->initRenderToTexture();
}

void projectM::projectM_resetTextures()
{
    renderer->ResetTextures();
}


projectM::projectM ( std::string config_file, int flags) :
        _pcm(0), beatDetect ( 0 ), renderer ( 0 ), _pipelineContext(new PipelineContext()), _pipelineContext2(new PipelineContext()), m_presetPos(0),
        timeKeeper(NULL), m_flags(flags), _matcher(NULL), _merger(NULL)
{
    readConfig(config_file);
    projectM_reset();
    projectM_resetGL(_settings.windowWidth, _settings.windowHeight);

}

projectM::projectM(Settings settings, int flags):
        _pcm(0), beatDetect ( 0 ), renderer ( 0 ), _pipelineContext(new PipelineContext()), _pipelineContext2(new PipelineContext()), m_presetPos(0),
        timeKeeper(NULL), m_flags(flags), _matcher(NULL), _merger(NULL)
{
    readSettings(settings);
    projectM_reset();
    projectM_resetGL(_settings.windowWidth, _settings.windowHeight);
}


bool projectM::writeConfig(const std::string & configFile, const Settings & settings) {

    ConfigFile config ( configFile );

    config.add("Mesh X", settings.meshX);
    config.add("Mesh Y", settings.meshY);
    config.add("Texture Size", settings.textureSize);
    config.add("FPS", settings.fps);
    config.add("Window Width", settings.windowWidth);
    config.add("Window Height", settings.windowHeight);
    config.add("Smooth Preset Duration", settings.smoothPresetDuration);
    config.add("Preset Duration", settings.presetDuration);
    config.add("Preset Path", settings.presetURL);
    config.add("Title Font", settings.titleFontURL);
    config.add("Menu Font", settings.menuFontURL);
    config.add("Hard Cut Sensitivity", settings.beatSensitivity);
    config.add("Aspect Correction", settings.aspectCorrection);
    config.add("Easter Egg Parameter", settings.easterEgg);
    config.add("Shuffle Enabled", settings.shuffleEnabled);
    config.add("Soft Cut Ratings Enabled", settings.softCutRatingsEnabled);
    std::fstream file(configFile.c_str());
    if (file) {
        file << config;
        return true;
    } else
        return false;
}



void projectM::readConfig (const std::string & configFile )
{
    std::cout << "[projectM] config file: " << configFile << std::endl;

    ConfigFile config ( configFile );
    _settings.meshX = config.read<int> ( "Mesh X", 32 );
    _settings.meshY = config.read<int> ( "Mesh Y", 24 );
    _settings.textureSize = config.read<int> ( "Texture Size", 512 );
    _settings.fps = config.read<int> ( "FPS", 35 );
    _settings.windowWidth  = config.read<int> ( "Window Width", 512 );
    _settings.windowHeight = config.read<int> ( "Window Height", 512 );
    _settings.smoothPresetDuration =  config.read<int>
            ( "Smooth Preset Duration", config.read<int>("Smooth Transition Duration", 10));
    _settings.presetDuration = config.read<int> ( "Preset Duration", 15 );

#ifdef __unix__
    _settings.presetURL = config.read<string> ( "Preset Path", "/usr/local/share/projectM/presets" );
#endif

#ifdef __APPLE__
    /// @bug awful hardcoded hack- need to add intelligence to cmake wrt bundling - carm
    _settings.presetURL = config.read<string> ( "Preset Path", "../Resources/presets" );
#endif

#ifdef WIN32
    _settings.presetURL = config.read<string> ( "Preset Path", "/usr/local/share/projectM/presets" );
#endif

#ifdef __APPLE__
    _settings.titleFontURL = config.read<string>
    ( "Title Font",  "../Resources/fonts/Vera.tff");
    _settings.menuFontURL = config.read<string>
    ( "Menu Font", "../Resources/fonts/VeraMono.ttf");
#endif

#ifdef __unix__
    _settings.titleFontURL = config.read<string>
            ( "Title Font", "/usr/local/share/projectM/fonts/Vera.tff" );
    _settings.menuFontURL = config.read<string>
            ( "Menu Font", "/usr/local/share/projectM/fonts/VeraMono.tff" );
#endif

#ifdef WIN32
    _settings.titleFontURL = config.read<string>
    ( "Title Font", projectM_FONT_TITLE );
    _settings.menuFontURL = config.read<string>
    ( "Menu Font", projectM_FONT_MENU );
#endif


    _settings.shuffleEnabled = config.read<bool> ( "Shuffle Enabled", true);

    _settings.easterEgg = config.read<float> ( "Easter Egg Parameter", 0.0);
    _settings.softCutRatingsEnabled =
            config.read<bool> ( "Soft Cut Ratings Enabled", false);

    // Hard Cuts are preset transitions that occur when your music becomes louder. They only occur after a hard cut duration threshold has passed.
    _settings.hardcutEnabled = config.read<bool> ( "Hard Cuts Enabled", false );
    // Hard Cut duration is the number of seconds before you become eligible for a hard cut.
    _settings.hardcutDuration = config.read<int> ( "Hard Cut Duration", 60 );
    // Hard Cut sensitivity is the volume difference before a "hard cut" is triggered.
    _settings.hardcutSensitivity = config.read<float> ( "Hard Cut Sensitivity", 1.0 );
    
    // Beat Sensitivity impacts how reactive your visualizations are to volume, bass, mid-range, and treble. 
    // Preset authors have developed their visualizations with the default of 1.0.
    _settings.beatSensitivity = config.read<float> ( "Beat Sensitivity", 1.0 );


    projectM_init ( _settings.meshX, _settings.meshY, _settings.fps,
                _settings.textureSize, _settings.windowWidth,_settings.windowHeight);
    
    if ( config.read ( "Aspect Correction", true ) )
    {
        _settings.aspectCorrection = true;
        renderer->correction = true;
    }
    else
    {
        _settings.aspectCorrection = false;
        renderer->correction = false;
    }


}


void projectM::readSettings (const Settings & settings )
{
    _settings.meshX = settings.meshX;
    _settings.meshY = settings.meshY;
    _settings.textureSize = settings.textureSize;
    _settings.fps = settings.fps;
    _settings.windowWidth  = settings.windowWidth;
    _settings.windowHeight = settings.windowHeight;
    _settings.smoothPresetDuration = settings.smoothPresetDuration;
    _settings.presetDuration = settings.presetDuration;
    _settings.softCutRatingsEnabled = settings.softCutRatingsEnabled;

    _settings.presetURL = settings.presetURL;
    _settings.titleFontURL = settings.titleFontURL;
    _settings.menuFontURL =  settings.menuFontURL;
    _settings.shuffleEnabled = settings.shuffleEnabled;
    _settings.datadir = settings.datadir;

    _settings.easterEgg = settings.easterEgg;

    _settings.hardcutEnabled = settings.hardcutEnabled;
    _settings.hardcutDuration = settings.hardcutDuration;
    _settings.hardcutSensitivity = settings.hardcutSensitivity;
    
    _settings.beatSensitivity = settings.beatSensitivity;
    
    projectM_init ( _settings.meshX, _settings.meshY, _settings.fps,
                    _settings.textureSize, _settings.windowWidth,_settings.windowHeight);
    

    _settings.aspectCorrection = settings.aspectCorrection;
}

#ifdef USE_THREADS
static void *thread_callback(void *prjm)
{
    projectM *p = (projectM *)prjm;
    p->thread_func(prjm);
    return NULL;
}


void *projectM::thread_func(void *vptr_args)
{
    //  printf("in thread: %f\n", timeKeeper->PresetProgressB());
    while (true)
    {
        if (!worker_sync.wait_for_work())
            return NULL;
        evaluateSecondPreset();
        worker_sync.finished_work();
    }
}
#endif

void projectM::evaluateSecondPreset()
{
    pipelineContext2().time = timeKeeper->GetRunningTime();
    pipelineContext2().presetStartTime = timeKeeper->PresetTimeB();
    pipelineContext2().frame = timeKeeper->PresetFrameB();
    pipelineContext2().progress = timeKeeper->PresetProgressB();

    m_activePreset2->Render(*beatDetect, pipelineContext2());
}

void projectM::renderFrame()
{
    Pipeline pipeline;
    Pipeline *comboPipeline;
    
    comboPipeline = renderFrameOnlyPass1(&pipeline);
    
    renderFrameOnlyPass2(comboPipeline,0,0,0);
    
    projectM::renderFrameEndOnSeparatePasses(comboPipeline);
}








Pipeline * projectM::renderFrameOnlyPass1(Pipeline *pPipeline) /*pPipeline is a pointer to a Pipeline for use in pass 2. returns the pointer if it was used, else returns NULL */
{
#ifdef SYNC_PRESET_SWITCHES
    pthread_mutex_lock(&preset_mutex);
#endif

#ifdef DEBUG
    char fname[1024];
    FILE *f = NULL;
    int index = 0;
    int x, y;
#endif

    timeKeeper->UpdateTimers();
/*
    if (timeKeeper->IsSmoothing())
    {
        printf("Smoothing A:%f, B:%f, S:%f\n", timeKeeper->PresetProgressA(), timeKeeper->PresetProgressB(), timeKeeper->SmoothRatio());
    }
    else
    {
        printf("          A:%f\n", timeKeeper->PresetProgressA());
    }*/

    mspf= ( int ) ( 1000.0/ ( float ) settings().fps ); //milliseconds per frame

    /// @bug who is responsible for updating this now?"
    pipelineContext().time = timeKeeper->GetRunningTime();
    pipelineContext().presetStartTime = timeKeeper->PresetTimeA();
    pipelineContext().frame = timeKeeper->PresetFrameA();
    pipelineContext().progress = timeKeeper->PresetProgressA();

    beatDetect->detectFromSamples();

    //m_activePreset->evaluateFrame();

    //if the preset isn't locked and there are more presets
    if ( renderer->noSwitch==false && !m_presetChooser->empty() )
    {
        //if preset is done and we're not already switching
        if ( timeKeeper->PresetProgressA()>=1.0 && !timeKeeper->IsSmoothing())
        {
            if (settings().shuffleEnabled)
                selectRandom(false);
            else
                selectNext(false);
        } else if (settings().hardcutEnabled && (beatDetect->vol-beatDetect->vol_old>settings().hardcutSensitivity) && timeKeeper->CanHardCut())
        {
            // Hard Cuts must be enabled, must have passed the hardcut duration, and the volume must be a greater difference than the hardcut sensitivity.
            if (settings().shuffleEnabled)
                selectRandom(true);
            else
                selectNext(true);
        }
    }


    if ( timeKeeper->IsSmoothing() && timeKeeper->SmoothRatio() <= 1.0 && !m_presetChooser->empty() )
    {
        //	 printf("start thread\n");
        assert ( m_activePreset2.get() );

#ifdef USE_THREADS
        worker_sync.wake_up_bg();
#endif

        m_activePreset->Render(*beatDetect, pipelineContext());

#ifdef USE_THREADS
        worker_sync.wait_for_bg_to_finish();
#else
        evaluateSecondPreset();
#endif


        pPipeline->setStaticPerPixel(settings().meshX, settings().meshY);

        assert(_matcher);
        PipelineMerger::mergePipelines( m_activePreset->pipeline(),
                                        m_activePreset2->pipeline(), *pPipeline,
                                        _matcher->matchResults(),
                                        *_merger, timeKeeper->SmoothRatio());

        renderer->RenderFrameOnlyPass1(*pPipeline, pipelineContext());


        return pPipeline;

    }
    else
    {


        if ( timeKeeper->IsSmoothing() && timeKeeper->SmoothRatio() > 1.0 )
        {
            //printf("End Smooth\n");
            m_activePreset = std::move(m_activePreset2);
            timeKeeper->EndSmoothing();
        }
        //printf("Normal\n");

        m_activePreset->Render(*beatDetect, pipelineContext());
        renderer->RenderFrameOnlyPass1 (m_activePreset->pipeline(), pipelineContext());
	return NULL; // indicating no transition

    }

    //	std::cout<< m_activePreset->absoluteFilePath()<<std::endl;
    //	renderer->presetName = m_activePreset->absoluteFilePath();




}





/* eye is 0,or 1, or who knows?*/
void projectM::renderFrameOnlyPass2(Pipeline *pPipeline,int xoffset,int yoffset,int eye) /*pPipeline can be null if we re not in transition */
{
/* eye is currently ignored */


#ifdef DEBUG
    char fname[1024];
    FILE *f = NULL;
    int index = 0;
    int x, y;
#endif

    if (pPipeline) 
//    if ( timeKeeper->IsSmoothing() && timeKeeper->SmoothRatio() <= 1.0 && !m_presetChooser->empty() )
    {
        //	 printf("start thread\n");
        assert ( m_activePreset2.get() );


        /* was other stuff */
	
        renderer->RenderFrameOnlyPass2(*pPipeline, pipelineContext(),xoffset,yoffset,eye);

    }
    else
    {


        renderer->RenderFrameOnlyPass2 (m_activePreset->pipeline(), pipelineContext(),xoffset,yoffset,eye);


    }

}




void projectM::renderFrameEndOnSeparatePasses(Pipeline *pPipeline) {

    if (pPipeline) {
       // mergePipelines() sets masterAlpha for each RenderItem, reset it before we forget
       for (RenderItem *drawable : pPipeline->drawables) {
           drawable->masterAlpha = 1.0;
       }
    pPipeline->drawables.clear();
    }
  
    count++;
#ifndef WIN32
    /** Frame-rate limiter */
    /** Compute once per preset */
    if ( this->count%100==0 )
    {
        this->renderer->realfps=100.0/ ( ( getTicks ( &timeKeeper->startTime )-this->fpsstart ) /1000 );
        this->fpsstart=getTicks ( &timeKeeper->startTime );
    }

#ifndef UNLOCK_FPS
    int timediff = getTicks ( &timeKeeper->startTime )-this->timestart;

    if ( timediff < this->mspf )
    {
        // printf("%s:",this->mspf-timediff);
        int sleepTime = ( unsigned int ) ( this->mspf-timediff ) * 1000;
        //		DWRITE ( "usleep: %d\n", sleepTime );
        if ( sleepTime > 0 && sleepTime < 100000 )
        {
            if ( usleep ( sleepTime ) != 0 ) {}}
    }
    this->timestart=getTicks ( &timeKeeper->startTime );
#endif

#endif /** !WIN32 */
#ifdef SYNC_PRESET_SWITCHES
    pthread_mutex_unlock(&preset_mutex);
#endif
return;
}

void projectM::projectM_reset()
{
    this->mspf = 0;
    this->timed = 0;
    this->timestart = 0;
    this->count = 0;

    this->fpsstart = 0;

    projectM_resetengine();
}

void projectM::projectM_init ( int gx, int gy, int fps, int texsize, int width, int height )
{
    /** Initialise start time */
    timeKeeper = new TimeKeeper(_settings.presetDuration,_settings.smoothPresetDuration, _settings.hardcutDuration, _settings.easterEgg);

    /** Nullify frame stash */

    /** Initialise per-pixel matrix calculations */
    /** We need to initialise this before the builtin param db otherwise bass/mid etc won't bind correctly */
    assert ( !beatDetect );

    if (!_pcm)
        _pcm = new PCM();
    assert(pcm());
    beatDetect = new BeatDetect ( _pcm );

    if ( _settings.fps > 0 )
        mspf= ( int ) ( 1000.0/ ( float ) _settings.fps );
    else mspf = 0;

    this->renderer = new Renderer ( width, height, gx, gy, beatDetect, settings().presetURL, settings().titleFontURL, settings().menuFontURL, settings().datadir );

    initPresetTools(gx, gy);


#ifdef USE_THREADS

    #ifdef SYNC_PRESET_SWITCHES
    pthread_mutex_init(&preset_mutex, NULL);
#endif

    worker_sync.reset();
    if (pthread_create(&thread, NULL, thread_callback, this) != 0)
    {

        std::cerr << "[projectM] failed to allocate a thread! try building with option USE_THREADS turned off" << std::endl;;
        exit(EXIT_FAILURE);
    }
#endif

    /// @bug order of operatoins here is busted
    //renderer->setPresetName ( m_activePreset->name() );
    timeKeeper->StartPreset();
    assert(pcm());

    pipelineContext().fps = fps;
    pipelineContext2().fps = fps;

}

/* Reinitializes the engine variables to a default (conservative and sane) value */
void projectM::projectM_resetengine()
{

    if ( beatDetect != NULL )
    {
        beatDetect->reset();
        beatDetect->beatSensitivity = _settings.beatSensitivity;
    }

}

/** Resets OpenGL state */
void projectM::projectM_resetGL ( int w, int h )
{
    /** sanity check **/
    assert(w > 0);
    assert(h > 0);

    /** Stash the new dimensions */
    _settings.windowWidth = w;
    _settings.windowHeight = h;
    renderer->reset ( w,h );
}

/** Sets the title to display */
void projectM::projectM_setTitle ( std::string title ) {

    if ( title != renderer->title )
    {
        renderer->title=title;
        renderer->drawtitle=1;
    }
}


int projectM::initPresetTools(int gx, int gy)
{

    /* Set the seed to the current time in seconds */
    srand ( time ( NULL ) );

    std::string url = (m_flags & FLAG_DISABLE_PLAYLIST_LOAD) ? std::string() : settings().presetURL;

    if ( ( m_presetLoader = new PresetLoader ( gx, gy, url) ) == 0 )
    {
        m_presetLoader = 0;
        std::cerr << "[projectM] error allocating preset loader" << std::endl;
        return PROJECTM_FAILURE;
    }

    if ( ( m_presetChooser = new PresetChooser ( *m_presetLoader, settings().softCutRatingsEnabled ) ) == 0 )
    {
        delete ( m_presetLoader );

        m_presetChooser = 0;
        m_presetLoader = 0;

        std::cerr << "[projectM] error allocating preset chooser" << std::endl;
        return PROJECTM_FAILURE;
    }

    // Start the iterator
    if (!m_presetPos)
        m_presetPos = new PresetIterator();

    // Initialize a preset queue position as well
    //	m_presetQueuePos = new PresetIterator();

    // Start at end ptr- this allows next/previous to easily be done from this position.
    *m_presetPos = m_presetChooser->end();

    // Load idle preset
//        std::cerr << "[projectM] Allocating idle preset..." << std::endl;
    m_activePreset = m_presetLoader->loadPreset
            ("idle://Geiss & Sperl - Feedback (projectM idle HDR mix).milk");
	renderer->setPresetName("Geiss & Sperl - Feedback (projectM idle HDR mix)");

    renderer->SetPipeline(m_activePreset->pipeline());

    // Case where no valid presets exist in directory. Could also mean
    // playlist initialization was deferred
    if (m_presetChooser->empty())
    {
        //std::cerr << "[projectM] warning: no valid files found in preset directory \""
        ///< m_presetLoader->directoryName() << "\"" << std::endl;
    }

    _matcher = new RenderItemMatcher();
    _merger = new MasterRenderItemMerge();
    //_merger->add(new WaveFormMergeFunction());
    _merger->add(new ShapeMerge());
    _merger->add(new BorderMerge());
    //_merger->add(new BorderMergeFunction());

    /// @bug These should be requested by the preset factories.
    _matcher->distanceFunction().addMetric(new ShapeXYDistance());

    //std::cerr << "[projectM] Idle preset allocated." << std::endl;

    projectM_resetengine();

    //std::cerr << "[projectM] engine has been reset." << std::endl;
    return PROJECTM_SUCCESS;
}

void projectM::destroyPresetTools()
{

    if ( m_presetPos )
        delete ( m_presetPos );

    m_presetPos = 0;

    if ( m_presetChooser )
        delete ( m_presetChooser );

    m_presetChooser = 0;

    if ( m_presetLoader )
        delete ( m_presetLoader );

    m_presetLoader = 0;

    if (_matcher) {
        delete _matcher;
        _matcher = NULL;
    }

    if (_merger) {
        delete _merger;
        _merger = NULL;
    }
}

/// @bug queuePreset case isn't handled
void projectM::removePreset(unsigned int index) {

    size_t chooserIndex = **m_presetPos;

    m_presetLoader->removePreset(index);


    // Case: no more presets, set iterator to end
    if (m_presetChooser->empty())
        *m_presetPos = m_presetChooser->end();

        // Case: chooser index has become one less due to removal of an index below it
    else if (chooserIndex > index) {
        chooserIndex--;
        *m_presetPos = m_presetChooser->begin(chooserIndex);
    }

        // Case: we have deleted the active preset position
        // Set iterator to end of chooser
    else if (chooserIndex == index) {
        *m_presetPos = m_presetChooser->end();
    }



}

unsigned int projectM::addPresetURL ( const std::string & presetURL, const std::string & presetName, const RatingList & ratings)
{
    bool restorePosition = false;

    if (*m_presetPos == m_presetChooser->end())
        restorePosition = true;

    int index = m_presetLoader->addPresetURL ( presetURL, presetName, ratings);

    if (restorePosition)
        *m_presetPos = m_presetChooser->end();

    return index;
}

void projectM::selectPreset(unsigned int index, bool hardCut)
{

    if (m_presetChooser->empty())
        return;


    *m_presetPos = m_presetChooser->begin(index);
    switchPreset(hardCut);
}

void projectM::switchPreset(const bool hardCut) {
    std::string result;

    if (!hardCut) {
        result = switchPreset(m_activePreset2);
    } else {
        result = switchPreset(m_activePreset);
        if (result.empty())
            timeKeeper->StartPreset();
    }

    if (result.empty() && !hardCut) {
        timeKeeper->StartSmoothing();
    }

    if (result.empty()) {
        presetSwitchedEvent(hardCut, **m_presetPos);
        errorLoadingCurrentPreset = false;
    } else {
        presetSwitchFailedEvent(hardCut, **m_presetPos, result);
        errorLoadingCurrentPreset = true;

    }
}


void projectM::selectRandom(const bool hardCut) {
    if (m_presetChooser->empty())
        return;
    presetHistory.push_back(m_presetPos->lastIndex());
    // If presetHistory is tracking more than 10, then delete the oldest entry so we cap to a history of 10.
    if (presetHistory.size() >= 10)
        presetHistory.erase(presetHistory.begin());
    presetFuture.clear();
    *m_presetPos = m_presetChooser->weightedRandom(hardCut);

    switchPreset(hardCut);

}

void projectM::selectPrevious(const bool hardCut) {
    if (m_presetChooser->empty())
        return;

    if (settings().shuffleEnabled && presetHistory.size() >= 1 && presetHistory.back() != m_presetLoader->size()) { // if randomly browsing presets, "previous" should return to last random preset not the index--. Avoid returning to size() because that's the idle:// preset.
        presetFuture.push_back(m_presetPos->lastIndex());
        selectPreset(presetHistory.back());
        presetHistory.pop_back();
    }
    else {
        // if we are not shuffling or there is no random future history, then let's not track a random vector and move fowards in the preset index.
        presetHistory.clear();
        presetFuture.clear();
{'fraction_non_alphanumeric': 0.1234509253139458, 'fraction_numerical': 0.007476867151354924, 'mean_word_length': 3.181449175231022, 'pattern_counts': {'":': 0, '<': 159, '<?xml version=': 0, '>': 314, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4796320', 'n_tokens_mistral': 15717, 'n_tokens_neox': 14845, 'n_words': 3508}