/*
  Copyright (C) 2009 Rory Walsh

  Cabbage 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.

  Cabbage 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 Csound; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  02111-1307 USA
*/

#include "CabbagePluginProcessor.h"
#include "CabbagePluginEditor.h"
#include <iostream>


#define MAX_BUFFER_SIZE 1024
//Csound functions like to return 0 when everything is ok..
#define LOGGER 0

//these two lines may need to be copied to top part of csound.h
//#define int32 int
//#define uint32 unsigned int

CabbageLookAndFeel* lookAndFeel;
CabbageLookAndFeelBasic* lookAndFeelBasic;

juce_ImplementSingleton (IdentArray);

#if defined(Cabbage_No_Csound)
#define OK 0
#endif

//==============================================================================
// There are two different CabbagePluginAudioProcessor constructors. One for the
// standalone application and the other for the plugin library
//==============================================================================
//#ifdef Cabbage_Build_Standalone
#if defined(Cabbage_Build_Standalone) || defined(Cabbage_Plugin_Host)
//===========================================================
// STANDALONE - CONSTRUCTOR
//===========================================================
CabbagePluginAudioProcessor::CabbagePluginAudioProcessor(String inputfile, bool guiOnOff, int _pluginType)
    :backgroundThread ("Audio Recorder Thread"),
     activeWriter (nullptr),
     csoundStatus(false),
     csdFile(File(inputfile)),
     showMIDI(false),
     csCompileResult(1),
     changeMessageType(""),
     guiON(false),
     currentLine(-99),
     noSteps(0),
     noPatterns(0),
     timeCounter(0),
     beat(0),
     bpm(120),
     patMatrixActive(0),
     masterCounter(0),
     xyAutosCreated(false),
     updateTable(false),
     yieldCallbackBool(false),
     yieldCounter(10),
     isNativeThreadRunning(false),
     scoreEvents(),
     nativePluginEditor(false),
     averageSampleIndex(0),
     pluginType(_pluginType),
     automationAmp(0),
     isAutomator(false),
     createLog(false),
     automationParamID(-1),
     debugMessage(""),
     guiRefreshRate(20),
     mouseY(0),
     mouseX(0),
     isWinXP(false),
     ksmpsOffset(0),
     breakCount(0),
	 stopProcessing(false)
{
//suspendProcessing(true);
    codeEditor = nullptr;
    backgroundThread.startThread();

    setPlayConfigDetails(2, 2, 44100, 512);

    
//set up file logger if needed..
    StringArray tmpArray;
    CabbageGUIClass cAttr;

    tmpArray.addLines(File(inputfile).loadFileAsString());
    for(int i=0; i<tmpArray.size() || tmpArray[i].contains("</Cabbage>"); i++)
        if(tmpArray[i].contains("logger("))
        {
            CabbageGUIClass cAttr(tmpArray[i], -99);
            createLog = cAttr.getNumProp(CabbageIDs::logger);
            if(createLog)
            {
                String logFileName = File(inputfile).getParentDirectory().getFullPathName()+String("/")+File(inputfile).getFileNameWithoutExtension()+String("_Log.txt");
                logFile = File(logFileName);
                fileLogger = new FileLogger(logFile, String("Cabbage Log.."));
                Logger::setCurrentLogger(fileLogger);
            }
        }


#ifndef Cabbage_No_Csound
//don't start of run Csound in edit mode
    setOpcodeDirEnv();
    csound = nullptr;
#if !defined(AndroidBuild)
    csound = new Csound();
#else
				AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
                                              "Uh-oh",
                                              "Pugin Constructor",
                                              "ok");	
	csound = new AndroidCsound();
	csound->setOpenSlCallbacks(); // for android audio to work
#endif

#ifdef CSOUND6
    csound->SetHostImplementedMIDIIO(true);


#endif
    csound->Reset();
//Logger::writeToLog(csound->GetEnv("OPCODEDIR64"));
#ifdef CSOUND5
    csound->PreCompile();
#endif
    csound->SetHostData(this);
    csound->SetMessageCallback(CabbagePluginAudioProcessor::messageCallback);
    csound->SetExternalMidiInOpenCallback(OpenMidiInputDevice);
    csound->SetExternalMidiReadCallback(ReadMidiData);
    csound->SetExternalMidiOutOpenCallback(OpenMidiOutputDevice);
    csound->SetExternalMidiWriteCallback(WriteMidiData);

    csound->SetIsGraphable(0);
#ifndef Cabbage_Plugin_Host
    if(!getPreference(appProperties, "UseCabbageIO"))
    {
        csoundPerfThread = new CsoundPerformanceThread(csound);
        csoundPerfThread->SetProcessCallback(CabbagePluginAudioProcessor::YieldCallback, (void*)this);
    }
#endif

    if(pluginType==AUTOMATION_PLUGIN)
        isAutomator = true;

    csoundChanList = NULL;
    numCsoundChannels = 0;
    csndIndex = 32;

//set up PVS struct
    dataout = new PVSDATEXT;

    if(inputfile.isNotEmpty())
    {
        File(inputfile).setAsCurrentWorkingDirectory();


#ifdef CSOUND6 
        csoundParams = new CSOUND_PARAMS();
        csoundParams->nchnls_override = this->getNumOutputChannels();
        csoundParams->displays = 0;
        csound->SetParams(csoundParams);
#endif

//ftpp->ftable;


        StringArray lines, includeFiles;
        lines.addLines(File(inputfile).loadFileAsString());

        for(int i=0; i<lines.size(); i++)
            if(lines[i].contains("include("))
            {
                CabbageGUIClass cAttr(lines[i], -99);
                for(int y=0; y<cAttr.getStringArrayProp(CabbageIDs::include).size(); y++)
                {
                    String infile = cAttr.getStringArrayPropValue(CabbageIDs::include, y);
                    if(!File::isAbsolutePath(infile))
                    {
                        String file = returnFullPathForFile(infile, File(inputfile).getParentDirectory().getFullPathName());
                        includeFiles.add(file);
                    }
                    else
                        includeFiles.add(infile);
                }
                break;
            }

        includeFiles.removeDuplicates(0);


        csCompileResult = csound->Compile(const_cast<char*>(inputfile.toUTF8().getAddress()));

        Logger::writeToLog(inputfile);
        if(csCompileResult==OK)
        {

            //send root directory path to Csound.
            setPlayConfigDetails(getNumberCsoundOutChannels(),
                                 getNumberCsoundOutChannels(),
                                 getCsoundSamplingRate(),
                                 getCsoundKsmpsSize());

            //simple hack to allow tables to be set up correctly.
            csound->PerformKsmps();
            for(int i=0; i<includeFiles.size(); i++)
            {
                //	csound->CompileOrc(File(includeFiles[i]).loadFileAsString().toUTF8().getAddress());
                //	csound->InputMessage("i\"PROCESSOR\" 0 3600 1");
            }

            csound->SetScoreOffsetSeconds(0);
            csound->RewindScore();
#ifdef WIN32
            csound->SetChannel("CSD_PATH", File(inputfile).getParentDirectory().getFullPathName().replace("\\", "\\\\").toUTF8().getAddress());
#else
            csound->SetChannel("CSD_PATH", File(inputfile).getParentDirectory().getFullPathName().toUTF8().getAddress());
#endif
            Logger::writeToLog("Csound compiled your file");

            //csound->SetYieldCallback(CabbagePluginAudioProcessor::yieldCallback);
            if(csound->GetSpout()==nullptr);
            CSspout = csound->GetSpout();
            CSspin  = csound->GetSpin();
            // numCsoundChannels = csoundListChannels(csound->GetCsound(), &csoundChanList);
            csndIndex = csound->GetKsmps();
            csdKsmps = csound->GetKsmps();
            cs_scale = csound->Get0dBFS();
            csoundStatus = true;
            debugMessageArray.add(CABBAGE_VERSION);
            debugMessageArray.add(String("\n"));
            this->setLatencySamples(csound->GetKsmps());
            updateHostDisplay();

        }
        else
        {
            Logger::writeToLog("Csound couldn't compile your file");
            csoundStatus=false;
            //debugMessage = "Csound did not compile correctly. Check for snytax errors by compiling with WinXound";
        }
    }
    else
        Logger::writeToLog("Welcome to Cabbage, problems with input file...");

    if(SystemStats::getOperatingSystemType()!=SystemStats::WinXP)
    {
        isWinXP = true;
        String path = File(inputfile).getParentDirectory().getFullPathName();
        String fullFileName;
#ifdef LINUX
        fullFileName = path+"/CabbageTemp.wav";
#else
        fullFileName = path+"\\CabbageTemp.wav";
#endif
        tempAudioFile = fullFileName;
        tempAudioFile.replaceWithData(0 ,0);
	}
#endif

}
#else

//===========================================================
// PLUGIN - CONSTRUCTOR
//===========================================================
CabbagePluginAudioProcessor::CabbagePluginAudioProcessor():
    backgroundThread ("Audio Recorder Thread"),
    activeWriter (nullptr),
    csoundStatus(false),
    showMIDI(false),
    csCompileResult(1),
    changeMessageType(""),
    guiON(false),
    currentLine(-99),
    noSteps(0),
    noPatterns(0),
    timeCounter(0),
    beat(0),
    bpm(120),
    patMatrixActive(0),
    masterCounter(0),
    xyAutosCreated(false),
    createLog(false),
    updateTable(false),
    yieldCallbackBool(false),
    yieldCounter(10),
    nativePluginEditor(false),
    averageSampleIndex(0),
	stopProcessing(false)
{
//Cabbage plugins always try to load a csd file with the same name as the plugin library.
//Therefore we need to find the name of the library and append a '.csd' to it.
#ifdef MACOSX
    String osxCSD = File::getSpecialLocation(File::currentApplicationFile).getFullPathName()+String("/Contents/")+File::getSpecialLocation(File::currentApplicationFile).getFileName();
    File thisFile(osxCSD);
    Logger::writeToLog("MACOSX defined OK");
	csdFile = thisFile.withFileExtension(String(".csd")).getFullPathName();
#elif defined(AndroidBuild)
    File inFile(File::getSpecialLocation(File::currentApplicationFile));
    ScopedPointer<InputStream> fileStream;
    fileStream = File(inFile.getFullPathName()).createInputStream();
    ZipFile zipFile (fileStream, false); 
    ScopedPointer<InputStream> fileContents;
    fileContents = zipFile.createStreamForEntry(*zipFile.getEntry("assets/AndroidSimpleSynth.csd"));
    File thisFile("/sdcard/Cabbage.csd");
    thisFile.replaceWithText(fileContents->readEntireStreamAsString());
    csdFile = thisFile;
#else
    File thisFile(File::getSpecialLocation(File::currentExecutableFile));
	csdFile = thisFile.withFileExtension(String(".csd")).getFullPathName();
#endif
   
    //Logger::writeToLog(File::getSpecialLocation(File::currentExecutableFile).getFullPathName());

    if(csdFile.exists())
	{
		Logger::writeToLog("File exists:"+String(csdFile.getFullPathName()));
		
	}
    else
	{
        Logger::writeToLog("File doesn't exist"+String(csdFile.getFullPathName()));
	}

    File(csdFile.getFullPathName()).setAsCurrentWorkingDirectory();
    csdFile.setAsCurrentWorkingDirectory();



    StringArray tmpArray;
    CabbageGUIClass cAttr;

    tmpArray.addLines(csdFile.loadFileAsString());
    for(int i=0; i<tmpArray.size() || tmpArray[i].contains("</Cabbage>"); i++)
        if(tmpArray[i].contains("logger("))
        {
            CabbageGUIClass cAttr(tmpArray[i], -99);
            createLog = cAttr.getNumProp(CabbageIDs::logger);
            if(createLog)
            {
                String logFileName = csdFile.getParentDirectory().getFullPathName()+String("/")+csdFile.getFileNameWithoutExtension()+String("_Log.txt");
                logFile = File(logFileName);
                fileLogger = new FileLogger(logFile, String("Cabbage Log.."));
                Logger::setCurrentLogger(fileLogger);
            }
        }

    setOpcodeDirEnv();

#ifndef Cabbage_No_Csound
#if !defined(AndroidBuild)
    csound = new Csound();
#else
	csound = new AndroidCsound();
	//csound->setOpenSlCallbacks(); // for android audio to work
#endif

    csound->SetHostImplementedMIDIIO(true);
//csound->Reset();
//csound->PreCompile();
    csound->SetHostData(this);
    midiOutputBuffer.clear();
//for host midi to get sent to Csound, don't need this for standalone
//but might use it in the future for midi mapping to controls
    csound->SetMessageCallback(CabbagePluginAudioProcessor::messageCallback);
    csound->SetExternalMidiInOpenCallback(OpenMidiInputDevice);
    csound->SetExternalMidiReadCallback(ReadMidiData);
    csound->SetExternalMidiOutOpenCallback(OpenMidiOutputDevice);
    csound->SetExternalMidiWriteCallback(WriteMidiData);

    csoundChanList = NULL;
    numCsoundChannels = 0;
    csndIndex = 32;
    startTimer(20);
#ifdef CSOUND6
    csoundParams = new CSOUND_PARAMS();
    csoundParams->nchnls_override = this->getNumOutputChannels();
    csoundParams->displays = 0;
    csound->SetParams(csoundParams);
#endif

    csCompileResult = csound->Compile(const_cast<char*>(csdFile.getFullPathName().toUTF8().getAddress()));
//csoundSetBreakpointCallback(csound->GetCsound(), breakpointCallback, (void*)this);
    csdFile.setAsCurrentWorkingDirectory();
    if(csCompileResult==OK)
    {
        Logger::writeToLog("compiled Ok");
		keyboardState.allNotesOff(0);
        keyboardState.reset();
        //simple hack to allow tables to be set up correctly.
        csound->PerformKsmps();
        csound->SetScoreOffsetSeconds(0);
        csound->RewindScore();
        //set up PVS struct
        dataout = new PVSDATEXT;
        csdKsmps = csound->GetKsmps();

        if(csound->GetSpout()==nullptr);
        CSspout = csound->GetSpout();
        CSspin  = csound->GetSpin();
        cs_scale = csound->Get0dBFS();
        //numCsoundChannels = csoundListChannels(csound->GetCsound(), &csoundChanList);
        csndIndex = csound->GetKsmps();
        this->setLatencySamples(csound->GetKsmps());
        updateHostDisplay();
        //soundFilerVector = new MYFLT[csdKsmps];
        csoundStatus = true;
        debugMessageArray.add(VERSION);
        debugMessageArray.add(String("\n"));
#ifdef WIN32
        csound->SetChannel("CSD_PATH", File(csdFile).getParentDirectory().getFullPathName().replace("\\", "\\\\").toUTF8().getAddress());
#else
        csound->SetChannel("CSD_PATH", File(csdFile).getParentDirectory().getFullPathName().toUTF8().getAddress());
#endif

        //send host info before performance..
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::hostbpm.toUTF8(), hostInfo.bpm);
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::timeinseconds.toUTF8(), hostInfo.timeInSeconds);
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::isplaying.toUTF8(), hostInfo.isPlaying);
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::isrecording.toUTF8(), hostInfo.isRecording);
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::hostppqpos.toUTF8(), hostInfo.ppqPosition);
		Logger::writeToLog("everything still good...");
    }
    else
    {
        Logger::writeToLog("Csound couldn't compile your file");
        csoundStatus=false;
    }
#endif


    if(SystemStats::getOperatingSystemType()!=SystemStats::WinXP)
    {
        isWinXP = true;
        String path = csdFile.getParentDirectory().getFullPathName();
        String fullFileName;
#ifdef LINUX
        fullFileName = path+"/CabbageTemp.wav";
#else
        fullFileName = path+"\\CabbageTemp.wav";
#endif
        tempAudioFile = fullFileName;
        tempAudioFile.replaceWithData(0 ,0);
    }


    createGUI(csdFile.loadFileAsString(), true);
	Logger::writeToLog("GUI has been created");

}
#endif

//===========================================================
// PLUGIN - DESTRUCTOR
//===========================================================
CabbagePluginAudioProcessor::~CabbagePluginAudioProcessor()
{
    deleteAndZero(lookAndFeel);
    deleteAndZero(lookAndFeelBasic);
    Logger::setCurrentLogger (nullptr);
    stopProcessing = true;
    removeAllChangeListeners();

#ifndef Cabbage_No_Csound

    xyAutomation.clear();

    IdentArray::deleteInstance();
    //const MessageManagerLock mmLock;
    if(csound)
    {
        if(csoundPerfThread)
        {
            csoundPerfThread->Stop();
            csoundPerfThread = nullptr;
        }
        //csound->SetHostImplementedMIDIIO(false);
        csound->DeleteChannelList(csoundChanList);
        Logger::writeToLog("about to cleanup Csound");
        //csoundDebugContinue(csound);
        //csound->Cleanup();
        //csound->SetHostImplementedMIDIIO(false);

        //csound->Reset();
        //csoundDebuggerClean(csound->GetCsound());
        csound = nullptr;
        Logger::writeToLog("Csound cleaned up");
    }


#endif
}

int CabbagePluginAudioProcessor::performEntireScore()
{
#ifndef Cabbage_No_Csound
    if(!isNativeThreadRunning)
    {
        csoundPerfThread->Play();
        isNativeThreadRunning = true;
    }
#endif
    return 1;
}

//this callback will be employed when users run with Csound audio IO rather than Cabbage
void CabbagePluginAudioProcessor::YieldCallback(void* data)
{
    CabbagePluginAudioProcessor *cabbage = (CabbagePluginAudioProcessor *)data;
    cabbage->sendOutgoingMessagesToCsound();
    cabbage->updateCabbageControls();
}

//============================================================================
//RECOMPILE CSOUND. THIS IS CALLED FROM THE PLUGIN HOST WHEN UDPATES ARE MADE ON THE FLY
//============================================================================
void CabbagePluginAudioProcessor::reCompileCsound(File file)
{
#ifndef Cabbage_No_Csound
    
	stopProcessing = true;
    //getCallbackLock().enter();
    midiOutputBuffer.clear();
//csound->DeleteChannelList(csoundChanList);

    csound->Reset();
    ksmpsOffset = 0;
    breakCount = 0;
#ifdef CSOUND6
    csoundParams = new CSOUND_PARAMS();
    csoundParams->nchnls_override =2;
    csoundParams->displays = 0;
    csound->SetParams(csoundParams);
#endif

    csound->SetHostImplementedMIDIIO(true);
    xyAutosCreated = false;
//csound->SetMessageCallback(CabbagePluginAudioProcessor::messageCallback);
    numCsoundChannels = 0;
//csound->SetParams(csoundParams);
//csound->SetMessageCallback(CabbagePluginAudioProcessor::messageCallback);
    csound->SetExternalMidiInOpenCallback(OpenMidiInputDevice);
    csound->SetExternalMidiReadCallback(ReadMidiData);
    csound->SetExternalMidiOutOpenCallback(OpenMidiOutputDevice);
    csound->SetExternalMidiWriteCallback(WriteMidiData);

    CSspout = nullptr;
    CSspin = nullptr;

    csCompileResult = csound->Compile(const_cast<char*>(file.getFullPathName().toUTF8().getAddress()));

#ifdef BUILD_DEBUGGER
    for(int i=0; i<breakpointInstruments.size(); i++)
    {
        getCallbackLock().enter();
        if(i==0)
            csoundDebuggerInit(csound->GetCsound());
        csoundSetBreakpointCallback(csound->GetCsound(), breakpointCallback, (void*)this);
        csoundSetInstrumentBreakpoint(csound->GetCsound(), breakpointInstruments[i], 0);
        getCallbackLock().exit();
    }
#endif

    StringArray lines, includeFiles;
    lines.addLines(file.loadFileAsString());

    for(int i=0; i<lines.size(); i++)
        if(lines[i].contains("include("))
        {
            CabbageGUIClass cAttr(lines[i], -99);
            for(int y=0; y<cAttr.getStringArrayProp(CabbageIDs::include).size(); y++)
            {
                String infile = cAttr.getStringArrayPropValue(CabbageIDs::include, y);
                if(!File::isAbsolutePath(infile))
                {
                    String ifile = returnFullPathForFile(infile, file.getParentDirectory().getFullPathName());
                    includeFiles.add(ifile);
                }
                else
                    includeFiles.add(infile);
            }
            break;
        }

    includeFiles.removeDuplicates(0);


    if(csCompileResult==OK)
    {
        keyboardState.allNotesOff(0);
        keyboardState.reset();
        //simple hack to allow tables to be set up correctly.
        csound->PerformKsmps();
        csound->SetScoreOffsetSeconds(0);
        csound->RewindScore();
        for(int i=0; i<includeFiles.size(); i++)
        {
            //	csound->CompileOrc(File(includeFiles[i]).loadFileAsString().toUTF8().getAddress());
            //	csound->InputMessage("i\"PROCESSOR\" 0 3600 1");
        }
        csndIndex = 0;
        CSspout = csound->GetSpout();
        CSspin  = csound->GetSpin();
        csound->PerformKsmps();
        csound->SetScoreOffsetSeconds(0);
        csound->RewindScore();
        Logger::writeToLog("Csound compiled your file");
        //numCsoundChannels = csoundListChannels(csound->GetCsound(), &csoundChanList);
        cs_scale = csound->Get0dBFS();
        csoundStatus = true;
        debugMessageArray.add(CABBAGE_VERSION);
        debugMessageArray.add(String("\n"));
        //removeAllChangeListeners();
        getCallbackLock().exit();

        //init all channels with their init val
        for(int i=0; i<guiCtrls.size(); i++)
        {
            csound->SetChannel( guiCtrls.getReference(i).getStringProp(CabbageIDs::channel).toUTF8(),
                                guiCtrls.getReference(i).getNumProp(CabbageIDs::value));
        }

#ifdef WIN32
        csound->SetChannel("CSD_PATH", file.getParentDirectory().getFullPathName().replace("\\", "\\\\").toUTF8().getAddress());
#else
        csound->SetChannel("CSD_PATH", file.getParentDirectory().getFullPathName().toUTF8().getAddress());
#endif

        stopProcessing = false;

        return;
    }
    else
    {
        Logger::writeToLog("Csound couldn't compile your file");
        csoundStatus=false;
    }
    getCallbackLock().exit();
#endif
}
//===========================================================
// PARSE CSD FILE AND FILL GUI/GUI-LAYOUT VECTORs.
// NO JUCE WIDGETS GET CREATED IN THIS CLASS. ALL
// GUI OBJECTS ARE CREATED ON THE FLY IN THE CABBAGE PLUGIN
// EDITOR FROM INFORMATION HELD IN THE GUICONTROLS VECTOR
//===========================================================
//maybe this should only be done at the end of a k-rate cycle..
void CabbagePluginAudioProcessor::createGUI(String source, bool refresh)
{
//clear arrays if refresh is set
    if(refresh==true)
    {
        guiLayoutCtrls.clear();
        guiCtrls.clear();
        CabbagePluginAudioProcessorEditor* editor = dynamic_cast<CabbagePluginAudioProcessorEditor*>(this->getActiveEditor());
        if(editor)
        {
            editor->comps.clear();
            editor->layoutComps.clear();
            editor->subPatches.clear();
            editor->popupMenus.clear();
        }
    }

    int indexOfLastGUICtrl = guiCtrls.size();
    int indexOfLastLayoutCtrl = guiLayoutCtrls.size();

    int test=100;
    int checkGUI = isGuiEnabled();
//setGuiEnabled((false));
    int guiID=0;
    StringArray csdText;

    int lines=1;
    String csdLine("");
    csdText.addLines(source);
    bool multiComment = false;
    bool multiLine = false;
//check for minimal Cabbage GUI

    for(int i=0; i<csdText.size(); i++)
    {
        int csdLineNumber=0;
#if defined(Cabbage_Build_Standalone) && !defined(AndroidBuild)
        if(!refresh)
        {
            StringArray fullText;
            if(codeEditor)
                fullText.addLines(codeEditor->getAllText());
            else
                fullText.addLines(csdFile.loadFileAsString());
            for(int u=0; u<fullText.size(); u++)
                if(csdText[i].equalsIgnoreCase(fullText[u]))
                {
                    csdLineNumber = u;
                    //Logger::writeToLog("LineNumber:"+String(csdLineNumber)+"\n"+csdText[i]);
                    u=fullText.size();
                }
        }
#endif

        if(csdText[i].indexOfWholeWordIgnoreCase(String("</Cabbage>"))==-1)
        {
            if(csdText[i].trim().isNotEmpty())
            {
                csdLine = csdText[i];
                if(refresh)
                    csdLineNumber = i;
                //tidy up string
                csdLine = csdLine.trimStart();
                //csdLine = csdLine.removeCharacters(" \\");
                //csdLine = csdLine.removeCharacters(",\\");
                //Logger::writeToLog(csdLine);
                StringArray tokes;
                tokes.addTokens(csdLine.trimEnd(), ", ", "\"");

                if(tokes[0].containsIgnoreCase(String("/*")))
                {
                    multiComment = true;
                }
                if(tokes[0].containsIgnoreCase(String("*\\")))
                {
                    multiComment = false;
                }

                if(tokes[0].containsIgnoreCase(String(";")))
                {
                    //allows for single line comments
                }
                else if(tokes[0].containsIgnoreCase(String("}")))
                {
                    plantFlag = ""; //reset plantFlag when a closing bracket is found
                    presetFlag = "";
                }

                if(!multiComment)
                    //populate the guiLayoutCtrls vector with non-interactive widgets
                    //the host widgets aren't GUI based but they can be added to this
                    //vector too, as can the editor button.
                    if(tokes[0].equalsIgnoreCase(String("form"))
                            ||tokes[0].equalsIgnoreCase(String("image"))
                            ||tokes[0].equalsIgnoreCase(String("keyboard"))
                            ||tokes[0].equalsIgnoreCase(String("gentable"))
                            ||tokes[0].equalsIgnoreCase(String("csoundoutput"))
                            ||tokes[0].equalsIgnoreCase(String("textbox"))
                            ||tokes[0].equalsIgnoreCase(String("line"))
                            ||tokes[0].equalsIgnoreCase(String("recordbutton"))
                            ||tokes[0].equalsIgnoreCase(String("label"))
                            ||tokes[0].equalsIgnoreCase(String("hostbpm"))
                            ||tokes[0].equalsIgnoreCase(String("hosttime"))
                            ||tokes[0].equalsIgnoreCase(String("hostplaying"))
                            ||tokes[0].equalsIgnoreCase(String("hostppqpos"))
                            ||tokes[0].equalsIgnoreCase(String("vumeter"))
                            ||tokes[0].equalsIgnoreCase(String("patmatrix"))
                            ||tokes[0].equalsIgnoreCase(String("source"))
                            ||tokes[0].equalsIgnoreCase(String("multitab"))
                            ||tokes[0].equalsIgnoreCase(String("infobutton"))
                            ||tokes[0].equalsIgnoreCase(String("filebutton"))
                            ||tokes[0].equalsIgnoreCase(String("soundfiler"))
							||tokes[0].equalsIgnoreCase(String("texteditor"))
                            ||tokes[0].equalsIgnoreCase(String("popupmenu"))
                            ||tokes[0].equalsIgnoreCase(String("snapshot"))
                            ||tokes[0].equalsIgnoreCase(String("table"))
                            ||tokes[0].equalsIgnoreCase(String("pvsview"))
                            ||tokes[0].equalsIgnoreCase(String("hostrecording"))
                            ||tokes[0].equalsIgnoreCase(String("directorylist"))
                            ||tokes[0].equalsIgnoreCase(String("transport"))
                            ||tokes[0].equalsIgnoreCase(String("groupbox")))
                    {
                        CabbageGUIClass cAttr(csdLine.trimEnd(), guiID);
                        //showMessage(csdLine);
                        cAttr.setNumProp(CabbageIDs::lineNumber, csdLineNumber);
                        if(cAttr.getStringProp("native").length()>0)
                        {
                            //create generic plugin editor and break..
                            setupNativePluginEditor();
                            nativePluginEditor = true;
                            return;
                        }


                        if(cAttr.getNumProp(CabbageIDs::guirefresh)>1)
                            guiRefreshRate = cAttr.getNumProp(CabbageIDs::guirefresh);
                        //showMessage(cAttr.getStringProp("type"));

                        csdLine = "";

                        //set up stuff for tables
                        if(tokes[0].equalsIgnoreCase(String("table")))
                        {
/*                            if(cAttr.getStringArrayProp(CabbageIDs::channel).size()==0)
                                for(int i=0; i<cAttr.getIntArrayProp("tablenumber").size(); i++)
                                    cAttr.addDummyChannel("dummy"+String(i));*/

                            for(int i=0; i<cAttr.getStringArrayProp(CabbageIDs::channel).size(); i++)
                                cAttr.addTableChannelValues();

                        }

                        //set up plant flag if needed for other widgets
                        if(cAttr.getStringProp(String("plant")).isNotEmpty())
                        {
                            plantFlag = cAttr.getStringProp(String("plant"));
                        }
                        else if(cAttr.getStringProp(String("reltoplant")).equalsIgnoreCase(String("")))
                            cAttr.setStringProp(String("reltoplant"), plantFlag);

							if(cAttr.getStringArrayProp(CabbageIDs::channelarray).size()>0){
								//showMessage(String(cAttr.getStringArrayProp(CabbageIDs::channelarray).joinIntoString(" ")));
								//showMessage(String(cAttr.getStringArrayProp(CabbageIDs::channelarray).size()));
								for(int i=0;i<cAttr.getStringArrayProp(CabbageIDs::channelarray).size();i++)
								{
									CabbageGUIClass copy = cAttr;
									copy.setStringProp(CabbageIDs::channel, cAttr.getStringArrayProp(CabbageIDs::channelarray).getReference(i));
									copy.setStringProp(CabbageIDs::identchannel, cAttr.getStringArrayProp(CabbageIDs::identchannelarray).getReference(i));
									//Logger::writeToLog(cAttr.getStringArrayProp(CabbageIDs::channelarray).getReference(i));
									guiLayoutCtrls.add(copy);
									guiID++;
								}
							}
							else{
								guiLayoutCtrls.add(cAttr);
								guiID++;
							}

                        if(cAttr.getStringProp("type").containsIgnoreCase("form"))
                            if(cAttr.getStringProp("text").length()>2)
                                setPluginName(cAttr.getStringProp("text"));
                            else if(cAttr.getStringProp("caption").length()>2)
                                setPluginName(cAttr.getStringProp("caption"));
                            else setPluginName("Untitled Cabbage Patch!");

                        //StringArray log = logGUIAttributes(cAttr, String("Non-Interactive"));
                        //debugMessageArray.addArray(logGUIAttributes(cAttr, String("Non-Interactive")));
                        sendChangeMessage();
                        //if instrument uses any of the host widgets, or an xypad, turn
                        //on the timer
                        if(tokes[0].equalsIgnoreCase(String("hostbpm"))
                                ||tokes[0].equalsIgnoreCase(String("hosttime"))
                                ||tokes[0].equalsIgnoreCase(String("hostplaying"))
                                ||tokes[0].equalsIgnoreCase(String("hostppqpos"))
                                ||tokes[0].equalsIgnoreCase(String("hostrecording")))
                            startTimer(20);
                    }
                //populate the guiCtrls vector with interactive widgets
                    else if(tokes[0].equalsIgnoreCase(String("hslider"))
                            ||tokes[0].equalsIgnoreCase(String("vslider"))
                            ||tokes[0].equalsIgnoreCase(String("rslider"))
                            ||tokes[0].equalsIgnoreCase(String("combobox"))
                            ||tokes[0].equalsIgnoreCase(String("checkbox"))
                            ||tokes[0].equalsIgnoreCase(String("numberbox"))
                            ||tokes[0].equalsIgnoreCase(String("xypad"))
                            ||tokes[0].equalsIgnoreCase(String("button")))
                    {
						
                        CabbageGUIClass cAttr(csdLine.trimEnd(), guiID);
                        cAttr.setNumProp(CabbageIDs::lineNumber, csdLineNumber);
                        //Logger::writeToLog(csdLine.trimEnd());
                        csdLine = "";
                        //Logger::writeToLog(tokes[0]);
                        //attach widget to plant if need be
                        if(cAttr.getStringProp(String("reltoplant")).equalsIgnoreCase(String("")))
                        {
                            //showMessage(cAttr.getStringProp(String("relToPlant")));
                            cAttr.setStringProp(String("reltoplant"), plantFlag);
                            //showMessage(String("presetFlag:")+presetFlag);
                            //showMessage(cAttr.getStringProp("name"));
                            if(cAttr.getStringProp("preset").length()<1)
                                cAttr.setStringProp(String("preset"), presetFlag.trim());
                            //showMessage(cAttr.getStringProp("preset"));
                        }
												
                        //xypad contain two control paramters, one for x axis and another for y. As such we add two
                        //to our contorl vector so that plugin hosts display two sliders. We name one of the xypad pads
                        // 'dummy' so that our editor doesn't display it. Our editor only needs to show one xypad.
                        if(tokes[0].equalsIgnoreCase(String("xypad")))
                        {
                            cAttr.setStringProp(CabbageIDs::xychannel, String("X"));
                            cAttr.setNumProp(CabbageIDs::range,  cAttr.getNumProp(CabbageIDs::rangex));
                            cAttr.setNumProp(CabbageIDs::min,  cAttr.getNumProp(CabbageIDs::minx));
                            cAttr.setNumProp(CabbageIDs::max,  cAttr.getNumProp(CabbageIDs::maxx));
                            cAttr.setNumProp(CabbageIDs::value, cAttr.getNumProp(CabbageIDs::valuex));
                            cAttr.setStringProp(String(CabbageIDs::channel), cAttr.getStringProp(CabbageIDs::xchannel));
                            guiCtrls.add(cAttr);

                            cAttr.setStringProp(CabbageIDs::xychannel, String("Y"));
                            cAttr.setNumProp(CabbageIDs::range,  cAttr.getNumProp(CabbageIDs::rangey));
                            cAttr.setNumProp(CabbageIDs::min,  cAttr.getNumProp(CabbageIDs::miny));
                            cAttr.setNumProp(CabbageIDs::max,  cAttr.getNumProp(CabbageIDs::maxy));
                            cAttr.setNumProp(CabbageIDs::value, cAttr.getNumProp(CabbageIDs::valuey));
                            cAttr.setStringProp(String(CabbageIDs::channel), cAttr.getStringProp(CabbageIDs::ychannel));
                            //append 'dummy' to name so the editor know not to display the
                            //second xypad
                            cAttr.setStringProp("name", cAttr.getStringProp(CabbageIDs::name)+String("dummy"));
                            guiCtrls.add(cAttr);
                            guiID++;
                            startTimer(10);
                        }
                        else
                        {
                            //if an array of objects is to be set up enter.....
							if(cAttr.getStringArrayProp(CabbageIDs::channelarray).size()>0){
								//showMessage(String(cAttr.getStringArrayProp(CabbageIDs::channelarray).joinIntoString(" ")));
								//showMessage(String(cAttr.getStringArrayProp(CabbageIDs::channelarray).size()));
								for(int i=0;i<cAttr.getStringArrayProp(CabbageIDs::channelarray).size();i++)
								{
									CabbageGUIClass copy = cAttr;
									copy.setStringProp(CabbageIDs::channel, cAttr.getStringArrayProp(CabbageIDs::channelarray).getReference(i));
									copy.setStringProp(CabbageIDs::identchannel, cAttr.getStringArrayProp(CabbageIDs::identchannelarray).getReference(i));
									//Logger::writeToLog(cAttr.getStringArrayProp(CabbageIDs::channelarray).getReference(i));
									guiCtrls.add(copy);
									guiID++;
								}
							}
                            else{
								guiCtrls.add(cAttr);
								guiID++;
							}
                            
                        }

                        //debugMessageArray.addArray(logGUIAttributes(cAttr, String("Interactive")));
                        sendChangeMessage();
                    }

            }
        }
        else break;
    } //end of scan through entire csd text, control vectors are now populated


    //init all channels with their init val, and set parameters
    for(int i=0; i<guiCtrls.size(); i++)
    {
//		Logger::writeToLog(guiCtrls.getReference(i).getStringProp(CabbageIDs::channel)+": "+String(guiCtrls[i].getNumProp(CabbageIDs::value)));
#ifndef Cabbage_No_Csound
        if(guiCtrls.getReference(i).getStringProp("channeltype")=="string")
            //deal with combobox strings..
            csound->SetChannel(guiCtrls.getReference(i).getStringProp(CabbageIDs::channel).toUTF8(), "");
//									guiCtrls.getReference(i).getStringArrayPropValue("text", guiCtrls[i].getNumProp(CabbageIDs::value)-1).toUTF8().getAddress());
        else
            csound->SetChannel( guiCtrls.getReference(i).getStringProp(CabbageIDs::channel).toUTF8(), guiCtrls[i].getNumProp(CabbageIDs::value));
#endif
    }

    //init all channels with their init val, and set parameters
    for(int i=0; i<guiLayoutCtrls.size(); i++)
    {
//		Logger::writeToLog(guiCtrls.getReference(i).getStringProp(CabbageIDs::channel)+": "+String(guiCtrls[i].getNumProp(CabbageIDs::value)));
#ifndef Cabbage_No_Csound
        if(guiLayoutCtrls.getReference(i).getStringProp(CabbageIDs::identchannel).isNotEmpty())
            //deal with combobox strings..
            csound->SetChannel(guiLayoutCtrls.getReference(i).getStringProp(CabbageIDs::identchannel).toUTF8(), "");
//									guiCtrls.getReference(i).getStringArrayPropValue("text", guiCtrls[i].getNumProp(CabbageIDs::value)-1).toUTF8().getAddress());
#endif
    }


#ifdef Cabbage_Build_Standalone

    if(this->getActiveEditor())
    {
        CabbagePluginAudioProcessorEditor* editor = dynamic_cast<CabbagePluginAudioProcessorEditor*>(this->getActiveEditor());

        if(refresh)
        {
            editor->comps.clear();
            editor->layoutComps.clear();
            editor->repaint();
            //((CabbagePluginAudioProcessorEditor*)getActiveEditor())->setEditMode(false);
            //editor->setEditMode(false);
        }
        //!this will not work as we are moving through our entire control vector
        for(int i=indexOfLastLayoutCtrl; i<guiLayoutCtrls.size(); i++)
            editor->InsertGUIControls(guiLayoutCtrls[i]);
        for(int i=indexOfLastGUICtrl; i<guiCtrls.size(); i++)
            editor->InsertGUIControls(guiCtrls[i]);

        if(!getPreference(appProperties, "ExternalEditor") && refresh)
            editor->setEditMode(checkGUI);

    }

#endif
}

//============================================================================
//dynamically remove components from editor window, used in EDIT mode
//============================================================================
void CabbagePluginAudioProcessor::removeGUIComponent(int index, String type)
{
//remove component struct from our abstract vector
    CabbagePluginAudioProcessorEditor* editor = dynamic_cast<CabbagePluginAudioProcessorEditor*>(this->getActiveEditor());

    if(type=="interactive")
    {
        //remove GUI abstract structure from vector
        guiCtrls.remove(index);
    }
    else
    {
        //remove GUI abstract structure from vector
        guiLayoutCtrls.remove(index);
    }

    editor->updateLayoutEditorFrames();
    editor->repaint();
}

//============================================================================
// start/stop recording of file
//============================================================================
void CabbagePluginAudioProcessor::startRecording ()
{
    if(isWinXP)
        if (sampleRate > 0)
        {
            // Create an OutputStream to write to our destination file...
            tempAudioFile.deleteFile();
            ScopedPointer<FileOutputStream> fileStream (tempAudioFile.createOutputStream());

            if (fileStream != nullptr)
            {
                // Now create a WAV writer object that writes to our output stream...
                WavAudioFormat wavFormat;
                AudioFormatWriter* writer = wavFormat.createWriterFor(fileStream, sampleRate, 2, 16, StringPairArray(), 0);

                if (writer != nullptr)
                {
                    fileStream.release(); // (passes responsibility for deleting the stream to the writer object that is now using it)

                    // Now we'll create one of these helper objects which will act as a FIFO buffer, and will
                    // write the data to disk on our background thread.
                    threadedWriter = new AudioFormatWriter::ThreadedWriter (writer, backgroundThread, 32768);

                    // Reset our recording thumbnail
                    //thumbnail.reset (writer->getNumChannels(), writer->getSampleRate());
                    nextSampleNum = 0;

                    // And now, swap over our active writer pointer so that the audio callback will start using it..
                    const ScopedLock sl (writerLock);
                    activeWriter = threadedWriter;
                }
            }
        }
}

void CabbagePluginAudioProcessor::stopRecording()
{
    if(isWinXP)
    {
        // First, clear this pointer to stop the audio callback from using our writer object..
        {
            const ScopedLock sl (writerLock);
            activeWriter = nullptr;
        }

        threadedWriter = nullptr;

        // Now we can delete the writer object. It's done in this order because the deletion could
        // take a little time while remaining data gets flushed to disk, so it's best to avoid blocking
        // the audio callback while this happens.
#if !defined(AndroidBuild)
        FileChooser fc("Save", tempAudioFile.getFullPathName(), "*.wav", UseNativeDialogue);
        //determine whether to poen file or directory
        if(fc.browseForFileToSave(true))
        {
            File selectedFile = fc.getResult();
            tempAudioFile.moveFileTo(selectedFile);
        }
#endif
    }
}

//============================================================================
//SETS UP A GENERIC PLUGIN EDITOR
//============================================================================
void CabbagePluginAudioProcessor::setupNativePluginEditor()
{
    /*
    //create a basic 'native' gui if specificed by the user.
    	int guiID = 0;
    	guiCtrls.clear();
    	for(int i=0;i<numCsoundChannels;i++){
    		const CsoundChannelListEntry& entry = csoundChanList[i];
    		if (entry.type & CSOUND_CONTROL_CHANNEL && entry.type & CSOUND_INPUT_CHANNEL) {
    			MYFLT ddefault, dmin, dmax;
    			int value_type = getCsound()->GetControlChannelParams(entry.name, ddefault, dmin, dmax);
    			String parameterInfo;
    			float initVal = (ddefault<dmin ? dmin : ddefault);
    			parameterInfo << "channel(\"" << entry.name << "\"), " << "range(" << String(dmin) << ", " << String(dmax) << ", " << String(initVal) << ")";
    			Logger::writeToLog(parameterInfo);
    			CabbageGUIClass cAttr(parameterInfo, guiID);
    			cAttr.setNumProp("range", dmax-dmin);
    			//cAttr.setStringProp(CabbageIDs::channel, entry.name);
    			//cAttr.setNumProp("max", (dmax>0 ? dmax : 1));
    			//cAttr.setNumProp("init", (ddefault<dmin ? dmin : ddefault));

    			    switch(value_type) {
    					case CSOUND_CONTROL_CHANNEL_INT:
    					cAttr.setNumProp("incr", 1);
    					break;

    					case CSOUND_CONTROL_CHANNEL_LIN:
    					cAttr.setNumProp("incr", .01);
    					break;

    					case CSOUND_CONTROL_CHANNEL_EXP:
    					cAttr.setNumProp("skew", .5);
    					break;

    					default:

    					break;
    				}

    			guiCtrls.add(cAttr);
    			setPluginName("Test Plugin");
    			guiID++;
    			}
    		}
    		 */
}

//===========================================================
// SHOW SOURCE EDITOR
//===========================================================
//void CabbagePluginAudioProcessor::createAndShowSourceEditor(LookAndFeel* looky)
//{
//if(!cabbageCsoundEditor){
//cabbageCsoundEditor = new CabbageEditorWindow(looky);
//cabbageCsoundEditor->setCsoundFile(csdFile);
//cabbageCsoundEditor->setCsoundOutputText(csoundOutput);
//}
//cabbageCsoundEditor->setVisible(true);
//}

//===========================================================
// CALLBACKS FOR STANDALONE
//===========================================================
#ifndef Cabbage_No_Csound
void CabbagePluginAudioProcessor::messageCallback(CSOUND* csound, int /*attr*/,  const char* fmt, va_list args)
{
    CabbagePluginAudioProcessor* ud = (CabbagePluginAudioProcessor *) csoundGetHostData(csound);
    char msg[MAX_BUFFER_SIZE];
    vsnprintf(msg, MAX_BUFFER_SIZE, fmt, args);

    ud->debugMessage += String(msg); //We have to append the incoming msg
    ud->csoundOutput += ud->debugMessage;
    ud->debugMessageArray.add(ud->debugMessage);
    if(ud->createLog)
        Logger::writeToLog(String(msg).trim());
    ud->sendChangeMessage();
    ud->debugMessage = "";
    ud = nullptr;
}


#endif

#if defined(BUILD_DEBUGGER) && !defined(Cabbage_No_Csound)

void CabbagePluginAudioProcessor::breakpointCallback(CSOUND *csound, debug_bkpt_info_t *bkpt_info, void *userdata)
{
#ifdef BUILD_DEBUGGER
    CabbagePluginAudioProcessor* ud = (CabbagePluginAudioProcessor *) userdata;
    ud->getCallbackLock().enter();
    int ksmpOffset = ud->ksmpsOffset;;

    //code based on Andres implementation in CsoundQT...

    debug_instr_t *debug_instr = bkpt_info->breakpointInstr;


    // Copy variable list
    debug_variable_t* vp = bkpt_info->instrVarList;


    if(ud->breakCount==0)
        ud->breakCount= debug_instr->kcounter;
    else ud->breakCount++;
    String output;

    output << "\nBreakpoint at instr " << debug_instr->p1 << "\tNumber of k-cycles into performance: " << ud->breakCount << "\n------------------------------------------------------";;

    while (vp)
    {
        output << " \n";
        if (vp->name[0] != '#')
        {
            output << " VarName:"<< vp->name << "\t";;
            if (strcmp(vp->typeName, "i") == 0 || strcmp(vp->typeName, "k") == 0)
            {
                output << " value=" << *((MYFLT *) vp->data);
            }
            else if(strcmp(vp->typeName, "S") == 0)
            {
                output << " value=" << (char*)(vp->data);
            }
            else if (strcmp(vp->typeName, "a") == 0)
            {
                MYFLT *data = (MYFLT *) vp->data;
                output << " value="<< *data;// << *(data + 1) << *(data + 2)<< *(data + 3);

            }
            output << " varType[" << vp->typeName << "]";

        }
        vp = vp->next;
    }

    ud->csoundDebuggerOutput = output;
    csoundDebugFreeVariables(csound, vp);
    //csoundDebugFreeInstrInstances(csound, debug_instr);
    ud->getCallbackLock().exit();

#endif
}
#endif
//==============================================================================
#if defined(Cabbage_Build_Standalone) || defined(Cabbage_Plugin_Host)
CabbagePluginAudioProcessor* JUCE_CALLTYPE createCabbagePluginFilter(String inputfile, bool guiOnOff, int pluginType)
{
    return new CabbagePluginAudioProcessor(inputfile, false, pluginType);
}
#else
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    static bool needsInitialize = true;

    if (needsInitialize)
    {
        csoundInitialize(CSOUNDINIT_NO_SIGNAL_HANDLER|CSOUNDINIT_NO_ATEXIT);
        needsInitialize = false;
    }

    return new CabbagePluginAudioProcessor();
}
#endif

//==========================================================================
//action listener. Listen to messages being sent form xypad automations
//==========================================================================
void CabbagePluginAudioProcessor::changeListenerCallback(ChangeBroadcaster *source)
{
    float xVal, yVal;
    //is message coming from an xypad
    XYPadAutomation* xyPad = dynamic_cast< XYPadAutomation*>(source);
    if(xyPad)
    {
#ifdef Cabbage_Build_Standalone
        setParameterNotifyingHost(xyPad->paramIndex, xyPad->getXValue());
        setParameterNotifyingHost(xyPad->paramIndex+1, xyPad->getYValue());
#else
        if(xyPad->getMinimumXValue()>=0)
            xVal = (xyPad->getXValue()/xyPad->getXRange())+(fabs(xyPad->getMinimumXValue())/xyPad->getXRange());
        else
            xVal = (xyPad->getXValue()/xyPad->getXRange())-(fabs(xyPad->getMinimumXValue())/xyPad->getXRange());

        if(xyPad->getMinimumYValue()<=0)
            yVal = (xyPad->getYValue()/xyPad->getYRange())+(fabs(xyPad->getMinimumYValue())/xyPad->getYRange());
        else
            yVal = (xyPad->getYValue()/xyPad->getYRange())-(fabs(xyPad->getMinimumYValue())/xyPad->getYRange());

        //Logger::writeToLog("Param:"+String(xyPad->paramIndex)+"  xyPadXVal:"+String(xVal));
        //Logger::writeToLog("Param:"+String(xyPad->paramIndex+1)+"  xyPadYVal:"+String(yVal));

        setParameterNotifyingHost(xyPad->paramIndex, xVal);
        setParameterNotifyingHost(xyPad->paramIndex+1, yVal);
#endif
    }
}

//==============================================================================
// getTable data from Csound so table editor can draw table
//==============================================================================
StringArray CabbagePluginAudioProcessor::getTableStatement(int tableNum)
{
StringArray fdata;
fdata.add(String::empty);
#ifndef Cabbage_No_Csound
    if(csCompileResult==OK)
    {
        MYFLT* argsPtr, *temp;
		int noOfArgs = -1;//csoundGetTableArgs(csound->GetCsound(), &argsPtr, tableNum);
		if(noOfArgs!=-1)
		{
		int tableSize = csound->GetTable(temp, tableNum);
		fdata.add(String(tableNum));	
		fdata.add("0");
		fdata.add(String(tableSize));
		for(int i=0;i<noOfArgs;i++)
		//Logger::writeToLog("TableNum:"+String(tableNum)+" ArgsCount:"+String(noOfArgs));
			fdata.add(String(argsPtr[i]));
		}
        //int tableSize = csound->GetTable(temp, tableNum);
        //if(tableSize>0)
        //{
			//uncomment with dev and modified Csound
            /*EVTBLK* e = (EVTBLK*)csoundTableGetEvtblk(csound->GetCsound(), tableNum);
            for(int i=0; i<=e->pcnt; i++)
                fdata.add(String(e->p[i]));*/
        //}
    }
#endif
    return fdata;
} 
//==============================================================================
const Array<double, CriticalSection> CabbagePluginAudioProcessor::getTable(int tableNum)
{
    Array<double, CriticalSection> points;
    if(csCompileResult==OK)
    {
        int tableSize=0;
#ifndef Cabbage_No_Csound
        MYFLT* temp;
        tableSize = csound->GetTable(temp, tableNum);
#else
        float *temp;
#endif
        if(tableSize>0)
            points = Array<double, CriticalSection>(temp, tableSize);
    }
    return points;
}

const Array<float, CriticalSection> CabbagePluginAudioProcessor::getTableFloats(int tableNum)
{
    Array<float, CriticalSection> points;
    if(csCompileResult==OK)
    {
        points.clear();

        int tableSize=0;
#ifndef Cabbage_No_Csound

        tableSize = csound->TableLength(tableNum);
        temp.clear();
        //not good if table size is -1!
        if(tableSize<0)
            return points;

        temp.reserve(tableSize);
        csound->TableCopyOut(tableNum, &temp[0]);
#else
        float *temp;
#endif
        if(tableSize>0)
            points = Array<float, CriticalSection>(&temp[0], tableSize);
    }
    return points;
}

int CabbagePluginAudioProcessor::checkTable(int tableNum)
{
#ifndef Cabbage_No_Csound	
	return  csound->TableLength(tableNum);	
#else
	return -1;
#endif
}

//=================================================================================
// Get and Set Parameter methods, called by our editor, and the plugin host...
//=================================================================================
float CabbagePluginAudioProcessor::getParameter (int index)
{
    if(isPositiveAndBelow(index, getGUICtrlsSize()))
    {
        float range = getGUICtrls(index).getNumProp(CabbageIDs::range);
        float min = getGUICtrls(index).getNumProp(CabbageIDs::min);
        //Logger::writeToLog("parameterGet-"+String(index)+String("-Min:")+String(min)+" Range:"+String(range)+ " Val:"+String(getGUICtrls(index).getNumProp(CabbageIDs::value)));
        //Logger::writeToLog("parameterGet:"+String(index)+String(":")+String(guiCtrls[index].getNumProp(CabbageIDs::value)));

        /* this gets called at any time by our host or out GUI editor */
        if(index<(int)guiCtrls.size()) //make sure index isn't out of range
        {
#ifndef Cabbage_Build_Standalone
            float val = (getGUICtrls(index).getNumProp(CabbageIDs::value)/range)-(min/range);
            if(getGUICtrls(index).getStringProp(CabbageIDs::type)==CabbageIDs::combobox)
                return (getGUICtrls(index).getNumProp(CabbageIDs::value)/getGUICtrls(index).getNumProp(CabbageIDs::comborange));
            else if(getGUICtrls(index).getStringProp(CabbageIDs::type)==CabbageIDs::checkbox ||
                    getGUICtrls(index).getStringProp(CabbageIDs::type)==CabbageIDs::button)
                return getGUICtrls(index).getNumProp(CabbageIDs::value);
            else
                return (getGUICtrls(index).getNumProp(CabbageIDs::value)/range)-(min/range);
#else
            return guiCtrls[index].getNumProp(CabbageIDs::value);
#endif
        }
        else
            return 0.0f;
    }
    else
        return 0.0f;
}


void CabbagePluginAudioProcessor::setParameter (int index, float newValue)
{
    String stringMessage;
#ifndef Cabbage_No_Csound
    /* this will get called by the plugin GUI sliders or
    by the host, via automation. The timer thread in the plugin's editor
    will constantly update with the values that have been set here.
    We don't actually change any parameters here, we simply add the messages
    to a queue. See next method. The updates will only happen when it's safe to do. */
    float range, min, max, comboRange;
//add index of control that was changed to dirty control vector
    dirtyControls.addIfNotAlreadyThere(index);
//Logger::writeToLog("parameterSet:"+String(newValue));
    if(index<(int)guiCtrls.size())//make sure index isn't out of range
    {
#ifndef Cabbage_Build_Standalone
        //scaling in here because incoming values in plugin mode range from 0-1
        range = getGUICtrls(index).getNumProp(CabbageIDs::range);
        comboRange = getGUICtrls(index).getNumProp(CabbageIDs::comborange);
        //Logger::writeToLog("inValue:"+String(newValue));
        min = getGUICtrls(index).getNumProp(CabbageIDs::min);

        if(getGUICtrls(index).getStringProp(CabbageIDs::type)==CabbageIDs::xypad)
            newValue = (jmax(0.f, newValue)*range)+min;
        else if(getGUICtrls(index).getStringProp(CabbageIDs::type)==CabbageIDs::combobox)//combo box value need to be rounded...
            newValue = (newValue*comboRange);
        else if(getGUICtrls(index).getStringProp(CabbageIDs::type)==CabbageIDs::checkbox ||
                getGUICtrls(index).getStringProp(CabbageIDs::type)==CabbageIDs::button)
            range=1;
        else
            newValue = (newValue*range)+min;

#endif
        if(getGUICtrls(index).getStringProp(CabbageIDs::type)==CabbageIDs::combobox &&
                getGUICtrls(index).getStringProp(CabbageIDs::channeltype)==CabbageIDs::stringchannel)
        {
            stringMessage = getGUICtrls(index).getStringArrayPropValue(CabbageIDs::text, newValue-1);
            Logger::writeToLog(stringMessage);
            messageQueue.addOutgoingChannelMessageToQueue(guiCtrls.getReference(index).getStringProp(CabbageIDs::channel),
                    stringMessage,
                    CabbageIDs::stringchannel);
        }
        else
        {
            messageQueue.addOutgoingChannelMessageToQueue(guiCtrls.getReference(index).getStringProp(CabbageIDs::channel),
                    newValue,
                    guiCtrls.getReference(index).getStringProp(CabbageIDs::type));
        }

        guiCtrls.getReference(index).setNumProp(CabbageIDs::value, newValue);
    }
#endif
}

//==============================================================================
//this method gets called after a performKsmps() to update our GUI controls
//with messages from Csound. For instance, a user might wish to change the value
//of a GUI slider from Csound by using a chnset opcode. The speed at which this is
//updated can be teaked, so as not to hog resources...
void CabbagePluginAudioProcessor::updateCabbageControls()
{
#ifndef Cabbage_No_Csound
    String chanName, channelMessage;
    if(csCompileResult==OK)
    {
        //update all control widgets
        for(int index=0; index<getGUICtrlsSize(); index++)
        {
            if(guiCtrls[index].getStringProp(CabbageIDs::channeltype).equalsIgnoreCase(CabbageIDs::stringchannel))
            {
                //THIS NEEDS TO ALLOW COMBOBOXEX THAT CONTAIN SNAPSHOTS TO UPDATE..
            }
            else
            {
                float value = csound->GetChannel(guiCtrls[index].getStringProp(CabbageIDs::channel).getCharPointer());
                //Logger::writeToLog("Channel:"+guiCtrls[index].getStringProp(CabbageIDs::channel));
                //Logger::writeToLog("value:"+String(value));
                if(value!=guiCtrls.getReference(index).getNumProp(CabbageIDs::value))
                {
                    guiCtrls.getReference(index).setNumProp(CabbageIDs::value, value);
                    dirtyControls.addIfNotAlreadyThere(index);
                }
            }

            if(guiCtrls[index].getStringProp(CabbageIDs::identchannel).isNotEmpty())
            {
                //if controls has an identifier channel send data from Csound to control
                char string[1024] = {0};
                csound->GetStringChannel(guiCtrls[index].getStringProp(CabbageIDs::identchannel).toUTF8().getAddress(), string);
                channelMessage = String(string);
                if(channelMessage!="")
                {
                    guiCtrls.getReference(index).setStringProp(CabbageIDs::identchannelmessage, channelMessage.trim());
                    guiCtrls.getReference(index).parse(" "+channelMessage, "");
                    dirtyControls.addIfNotAlreadyThere(index);
                }
                //else
                //guiCtrls.getReference(index).setStringProp(CabbageIDs::identchannelmessage, "");
                //zero channel message so that we don't keep sending the same string
                csound->SetChannel(guiCtrls[index].getStringProp(CabbageIDs::identchannel).toUTF8().getAddress(), "");
            }
        }

        //update all layout control widgets
        //currently this is only needed for table widgets as other layout controls
        //don't use channel messages...
        for(int index=0; index<getGUILayoutCtrlsSize(); index++)
        {
            if(guiLayoutCtrls[index].getStringProp(CabbageIDs::type)==CabbageIDs::table)
            {
                for(int y=0; y<guiLayoutCtrls[index].getStringArrayProp(CabbageIDs::channel).size(); y++)
                {
                    //String test = getGUILayoutCtrls(index).getStringArrayPropValue(CabbageIDs::channel, y);
                    float value = csound->GetChannel(guiLayoutCtrls[index].getStringArrayPropValue(CabbageIDs::channel, y).getCharPointer());
                    guiLayoutCtrls[index].setTableChannelValues(y, value);
                }
            }

            if(guiLayoutCtrls[index].getStringProp(CabbageIDs::identchannel).isNotEmpty())
            {
                char string[1024] = {0};
                //guiLayoutCtrls[index].getStringProp(CabbageIDs::identchannel).toUTF8().getAddress()
                csound->GetStringChannel(guiLayoutCtrls[index].getStringProp(CabbageIDs::identchannel).toUTF8().getAddress(), string);
                channelMessage = String(string);
                //Logger::writeToLog(guiLayoutCtrls[index].getStringProp(CabbageIDs::identchannel));
                if(channelMessage!="")
                {
                    guiLayoutCtrls.getReference(index).parse(" "+channelMessage, "");
                    guiLayoutCtrls.getReference(index).setStringProp(CabbageIDs::identchannelmessage, channelMessage.trim());
                }
                //else
                //	guiLayoutCtrls.getReference(index).setStringProp(CabbageIDs::identchannelmessage, "");
                //zero channel message so that we don't keep sending the same string
                csound->SetChannel(guiLayoutCtrls[index].getStringProp(CabbageIDs::identchannel).toUTF8().getAddress(), "");
            }
        }
    }
    sendChangeMessage();
#endif
}


//==============================================================================
//this method only gets called when it's safe to do so, i.e., between calls to performKsmps()
//this method sends any channel messages that are in the queue to from Cabbage to Csound
void CabbagePluginAudioProcessor::sendOutgoingMessagesToCsound()
{
#ifndef Cabbage_No_Csound
    if(csCompileResult==OK)
    {
#ifndef Cabbage_Build_Standalone
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::hostbpm.toUTF8(), hostInfo.bpm);
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::timeinseconds.toUTF8(), hostInfo.timeInSeconds);
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::isplaying.toUTF8(), hostInfo.isPlaying);
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::isrecording.toUTF8(), hostInfo.isRecording);
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (hostInfo))
            csound->SetChannel(CabbageIDs::hostppqpos.toUTF8(), hostInfo.ppqPosition);
#endif

        for(int i=0; i<messageQueue.getNumberOfOutgoingChannelMessagesInQueue(); i++)
        {
            //Logger::writeToLog("MessageType:"+messageQueue.getOutgoingChannelMessageFromQueue(i).type);
            if(messageQueue.getOutgoingChannelMessageFromQueue(i).type=="directoryList")
            {
                for(int y=0; y<scoreEvents.size(); y++)
                    csound->InputMessage(scoreEvents[y].toUTF8());
                //scoreEvents.clear();
            }
            //update Csound function tables with values from table widget
            else if(messageQueue.getOutgoingChannelMessageFromQueue(i).type=="updateTable")
            {
                //Logger::writeToLog(messageQueue.getOutgoingChannelMessageFromQueue(i).fStatement.toUTF8());
                csound->InputMessage(messageQueue.getOutgoingChannelMessageFromQueue(i).fStatement.getCharPointer());
            }
            //catch string messags
            else if(messageQueue.getOutgoingChannelMessageFromQueue(i).type==CabbageIDs::stringchannel)
            {
                Logger::writeToLog(messageQueue.getOutgoingChannelMessageFromQueue(i).channelName);
                Logger::writeToLog(messageQueue.getOutgoingChannelMessageFromQueue(i).stringVal);
                csound->SetChannel(messageQueue.getOutgoingChannelMessageFromQueue(i).channelName.getCharPointer(),
                                   messageQueue.getOutgoingChannelMessageFromQueue(i).stringVal.toUTF8().getAddress());
            }
            else
                csound->SetChannel(messageQueue.getOutgoingChannelMessageFromQueue(i).channelName.getCharPointer(),
                                   messageQueue.getOutgoingChannelMessageFromQueue(i).value);
        }

        messageQueue.flushOutgoingChannelMessages();
    }

#endif
}


//========================================================================
// Standard plugin methods, getName, getNumParameters, setParamterName, get ProgramName, etc....
//==============================================================================
const String CabbagePluginAudioProcessor::getName() const
{
    return pluginName;
}

int CabbagePluginAudioProcessor::getNumParameters()
{
    return guiCtrls.size();
}

const String CabbagePluginAudioProcessor::getParameterName (int index)
{
    if(index<(int)guiCtrls.size())//make sure index isn't out of range
        return guiCtrls.getReference(index).getStringProp(CabbageIDs::channel);
    else return String::empty;
}

const String CabbagePluginAudioProcessor::getParameterText (int index)
{
    if(index<(int)guiCtrls.size())//make sure index isn't out of range
        return String (guiCtrls.getReference(index).getNumProp(CabbageIDs::value), 2);
    else return String::empty;
}

const String CabbagePluginAudioProcessor::getInputChannelName (int channelIndex) const
{
    if(channelIndex<(int)guiCtrls.size())//make sure index isn't out of range
        return String (channelIndex + 1);
    else return String::empty;
}

const String CabbagePluginAudioProcessor::getOutputChannelName (int channelIndex) const
{
    if(channelIndex<(int)guiCtrls.size())//make sure index isn't out of range
        return String (channelIndex + 1);
    else return String::empty;
}

bool CabbagePluginAudioProcessor::isInputChannelStereoPair (int /*index*/) const
{
    return true;
}

bool CabbagePluginAudioProcessor::isOutputChannelStereoPair (int /*index*/) const
{
    return true;
}

bool CabbagePluginAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
    return true;
#else
    return false;
#endif
}

bool CabbagePluginAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
    return true;
#else
    return false;
#endif
}

void CabbagePluginAudioProcessor::setGuiEnabled(bool val)
{
#if defined(Cabbage_Build_Standalone) && !defined(AndroidBuild)
    guiON = val;
    CabbagePluginAudioProcessorEditor* editor = dynamic_cast< CabbagePluginAudioProcessorEditor*>(this->getActiveEditor());
    if(editor)
    {
        if(val==false)
            if(getPreference(appProperties, "ExternalEditor"))
                csdFile.replaceWithText(codeEditor->getAllText());

    }
#endif
}

int CabbagePluginAudioProcessor::getNumPrograms()
{
    return 0;
}

int CabbagePluginAudioProcessor::getCurrentProgram()
{
    return 0;
}

void CabbagePluginAudioProcessor::setCurrentProgram (int /*index*/)
{
}

const String CabbagePluginAudioProcessor::getProgramName (int /*index*/)
{
    return String::empty;
}

void CabbagePluginAudioProcessor::changeProgramName (int /*index*/, const String& /*newName*/)
{
}

//==============================================================================
void CabbagePluginAudioProcessor::prepareToPlay (double sampRate, int samplesPerBlock)
{
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
	//showMessage(String(this->getNumOutputChannels()));
    keyboardState.reset();
    sampleRate = sampRate;
}
//==============================================================================
void CabbagePluginAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
    keyboardState.reset();
}
//==============================================================================
//this following callback only runs in plugin mode, and only when one of the
//host widgets are being used
void CabbagePluginAudioProcessor::timerCallback()
{
#ifndef Cabbage_No_Csound
    for(int y=0; y<xyAutomation.size(); y++)
    {
        if(xyAutomation[y])
            xyAutomation[y]->update();
    }
#endif
}

//==============================================================================
void CabbagePluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
    float* audioBuffer;
	int numSamples = buffer.getNumSamples();

    if(stopProcessing || isGuiEnabled())
    {
		buffer.clear();
	}
	else{
#ifndef Cabbage_No_Csound

			if(csCompileResult==OK)
			{

				keyboardState.processNextMidiBuffer (midiMessages, 0, buffer.getNumSamples(), true);
				midiBuffer = midiMessages;
				ccBuffer = midiMessages;

				//if no inputs are used clear buffer in case it's not empty..
				if(getNumInputChannels()==0)
					buffer.clear();

	#if JucePlugin_ProducesMidiOutput
				if(!midiOutputBuffer.isEmpty())
					midiMessages.swapWith(midiOutputBuffer);
	#endif

				for(int i=0; i<numSamples; i++, csndIndex++)
				{
					if(csndIndex == csound->GetKsmps())
					{
						getCallbackLock().enter();
						//slow down calls to these functions, no need for them to be firing at k-rate
						yieldCounter = (yieldCounter>guiRefreshRate) ? 0 : yieldCounter+1;
						if(yieldCounter==0)
						{
							sendOutgoingMessagesToCsound();
							updateCabbageControls();
						}


						csCompileResult = csound->PerformKsmps();

						if(csCompileResult!=OK)
							stopProcessing = true;
						else
							ksmpsOffset++;

						getCallbackLock().exit();
						csndIndex = 0;
					}
					if(csCompileResult==OK)
					{
						for(int channel = 0; channel < getNumOutputChannels(); channel++ )
						{
							audioBuffer = buffer.getWritePointer(channel,0);
							pos = csndIndex*getNumOutputChannels();
							CSspin[channel+pos] = audioBuffer[i]*cs_scale;
							audioBuffer[i] = (CSspout[channel+pos]/cs_scale);
						}
					}
					else
						buffer.clear();


				}

				if (activeWriter != 0 && !isWinXP)
					activeWriter->write (buffer.getArrayOfReadPointers(), buffer.getNumSamples());


			}//if not compiled just mute output
			else
			{
				for(int channel = 0; channel < getNumInputChannels(); channel++)
				{
					audioBuffer = buffer.getWritePointer(channel,0);
					for(int i=0; i<numSamples; i++, csndIndex++)
					{
						audioBuffer[i]=0;
					}
				}
			}

	#endif
		}

#if JucePlugin_ProducesMidiOutput
    if(!midiBuffer.isEmpty())
        midiMessages.swapWith(midiOutputBuffer);
#endif
}

//==============================================================================
// breakpoint functions
//==============================================================================
void CabbagePluginAudioProcessor::setCsoundInstrumentBreakpoint(int instr, int line=0)
{
#if defined(BUILD_DEBUGGER) && !defined(Cabbage_No_Csound)
    getCallbackLock().enter();
    if(breakpointInstruments.size()==0)
        csoundDebuggerInit(csound->GetCsound());
    csoundSetBreakpointCallback(csound->GetCsound(), breakpointCallback, (void*)this);
    csoundSetInstrumentBreakpoint(csound->GetCsound(), instr, 0);
    breakpointInstruments.add(instr);
    getCallbackLock().exit();
#endif
}

void CabbagePluginAudioProcessor::removeCsoundInstrumentBreakpoint(int instr)
{
#if defined(BUILD_DEBUGGER) && !defined(Cabbage_No_Csound)
    getCallbackLock().enter();
    breakpointInstruments.removeAllInstancesOf(instr);
    if(breakpointInstruments.size()==0)
        breakCount=0;
    csoundDebuggerInit(csound->GetCsound());
    csoundRemoveInstrumentBreakpoint(csound->GetCsound(), instr);
    getCallbackLock().exit();
#endif
}

void CabbagePluginAudioProcessor::continueCsoundDebug()
{
#if defined(BUILD_DEBUGGER) && !defined(Cabbage_No_Csound)

    if(breakpointInstruments.size()>0)
    {
        getCallbackLock().enter();
        csoundDebugContinue(csound->GetCsound());
        getCallbackLock().exit();
    }

#endif
}

void CabbagePluginAudioProcessor::nextCsoundDebug()
{
#if defined(BUILD_DEBUGGER) && !defined(Cabbage_No_Csound)
    //not yet implemented....
    if(breakpointInstruments.size()>0)
    {
        getCallbackLock().enter();
        //csoundDebugNext(csound->GetCsound());
        getCallbackLock().exit();
    }

#endif
}

void CabbagePluginAudioProcessor::cleanCsoundDebug()
{
#if defined(BUILD_DEBUGGER) && !defined(Cabbage_No_Csound)
    csoundClearBreakpoints(csound->GetCsound());
#endif
}
//==============================================================================
// MIDI functions
//==============================================================================

#ifndef Cabbage_No_Csound
int CabbagePluginAudioProcessor::OpenMidiInputDevice(CSOUND * csound, void **userData, const char* /*devName*/)
{
    *userData = csoundGetHostData(csound);
    if(!userData)
        cout << "\n\ncan't open midi in\n\n";
    return 0;
}

//==============================================================================
// Reads MIDI input data from host, gets called every time there is MIDI input to our plugin
//==============================================================================
int CabbagePluginAudioProcessor::ReadMidiData(CSOUND* /*csound*/, void *userData,
        unsigned char *mbuf, int nbytes)
{
    CabbagePluginAudioProcessor *midiData = (CabbagePluginAudioProcessor *)userData;
    if(!userData)
    {
        cout << "\n\nInvalid";
        return 0;
    }
    int cnt=0;

    if(!midiData->midiBuffer.isEmpty() && cnt <= (nbytes - 3))
    {
        MidiMessage message(0xf4, 0, 0, 0);
        MidiBuffer::Iterator i (midiData->midiBuffer);
        int messageFrameRelativeTothisProcess;
        while (i.getNextEvent (message, messageFrameRelativeTothisProcess))
        {
            if(message.isNoteOn())
            {
                *mbuf++ = (unsigned char)0x90 + message.getChannel()-1;
                *mbuf++ = (unsigned char)message.getNoteNumber();
                *mbuf++ = (unsigned char)message.getVelocity();
                cnt += 3;
            }
            else if(message.isNoteOff())
            {
                *mbuf++ = (unsigned char)0x80 + message.getChannel()-1;
                *mbuf++ = (unsigned char)message.getNoteNumber();
                *mbuf++ = (unsigned char)message.getVelocity();
                cnt += 3;
            }
            else if(message.isAllSoundOff())
            {
                *mbuf++ = (unsigned char)0x7B + message.getChannel()-1;
                *mbuf++ = (unsigned char)message.getNoteNumber();
                *mbuf++ = (unsigned char)message.getVelocity();
                cnt += 3;
            }
            else if(message.isController())
            {
                *mbuf++ = (unsigned char)0xB0 + message.getChannel()-1;
                *mbuf++ = (unsigned char)message.getControllerNumber();
                *mbuf++ = (unsigned char)message.getControllerValue();
                cnt += 3;
            }
            else if(message.isProgramChange())
            {
                *mbuf++ = (unsigned char)0xC0 + message.getChannel()-1;
                *mbuf++ = (unsigned char)message.getProgramChangeNumber();
                cnt += 2;
            }
            else if(message.isPitchWheel())
            {
                *mbuf++ = (unsigned char)0xE0 + message.getChannel()-1;
                *mbuf++ = (unsigned char)message.getPitchWheelValue();
                cnt += 3;
            }

        }
        midiData->midiBuffer.clear();
    }
    return cnt;
}

//==============================================================================
// Opens MIDI output device, adding -QN to your CsOptions will causes this method to be called
// as soon as your plugin loads
//==============================================================================
int CabbagePluginAudioProcessor::OpenMidiOutputDevice(CSOUND * csound, void **userData, const char* /*devName*/)
{
    *userData = csoundGetHostData(csound);
    if(!userData)
        Logger::writeToLog("\n\ncan't open midi out\n\n");
    return 0;
}

//==============================================================================
// Write MIDI data to plugin's MIDI output. Each time Csound outputs a midi message this
// method should be called. Note: you must have -Q set in your CsOptions
//==============================================================================
int CabbagePluginAudioProcessor::WriteMidiData(CSOUND* /*csound*/, void *_userData,
        const unsigned char *mbuf, int nbytes)
{
    CabbagePluginAudioProcessor *userData = (CabbagePluginAudioProcessor *)_userData;
    if(!userData)
    {
        Logger::writeToLog("\n\nInvalid");
        return 0;
    }

    MidiMessage message(mbuf, nbytes, 0);
//Logger::writeToLog(String(message.getNoteNumber()));
    userData->midiOutputBuffer.addEvent(message, 0);
    return nbytes;
}

#endif
//==============================================================================
bool CabbagePluginAudioProcessor::hasEditor() const
{
    return true; // (change this to false if you choose to not supply an editor)
}

AudioProcessorEditor* CabbagePluginAudioProcessor::createEditor()
{
    if(!nativePluginEditor)
        return new CabbagePluginAudioProcessorEditor (this);
    else
        return new CabbageGenericAudioProcessorEditor (this);
}

//==============================================================================
void CabbagePluginAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // Here's an example of how you can use XML to make it easy and more robust:

    // Create an outer XML element..
    XmlElement xml ("CABBAGE_PLUGIN_SETTINGS");

    for(int i=0; i<guiCtrls.size(); i++)
        xml.setAttribute(guiCtrls[i].getStringProp(CabbageIDs::channel),getParameter(i));

    for(int i=0; i<guiLayoutCtrls.size(); i++)
		if(guiLayoutCtrls[i].getStringProp(CabbageIDs::type)==CabbageIDs::filebutton)
		{
			//save filebutton last opened file...
			char string[1024] = {0};
            csound->GetStringChannel(guiLayoutCtrls[i].getStringProp(CabbageIDs::channel).toUTF8().getAddress(), string);
			xml.setAttribute ("filebutton_"+guiLayoutCtrls[i].getStringProp(CabbageIDs::channel), String(string));
			Logger::writeToLog("filebutton_"+guiLayoutCtrls[i].getStringProp(CabbageIDs::channel));
			Logger::writeToLog(String(string));
		}

    // then use this helper function to stuff it into the binary blob and return it..
    copyXmlToBinary (xml, destData);
}

void CabbagePluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    // You should use this method to restore your parameters from this memory block,
    // whose contents will have been created by the getStateInformation() call.
    // This getXmlFromBinary() helper function retrieves our XML from the binary blob..
    ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));

    if (xmlState != nullptr)
    {
        // make sure that it's actually our type of XML object..
        if (xmlState->hasTagName ("CABBAGE_PLUGIN_SETTINGS"))
        {
            for(int i=0; i<this->getNumParameters(); i++)
			{
			this->setParameter(i, (float)xmlState->getDoubleAttribute(guiCtrls[i].getStringProp(CabbageIDs::channel)));
			}
			
		
			for(int i=0;i<xmlState->getNumAttributes();i++)
			{
				//if the instrument uses a filebutton, then retreive the last known
				//file that was loaded with it
				if(xmlState->getAttributeName(i).contains("filebutton_"))
				{
					String channel = xmlState->getAttributeName(i).substring(11);
					//Logger::writeToLog(xmlState->getAttributeValue(i));
					csound->SetChannel(channel.toUTF8().getAddress(), xmlState->getAttributeValue(i).toUTF8().getAddress());
				}
				
			}
		}
    }
}

//==============================================================================


