/*
  ==============================================================================

    This file was auto-generated!

    It contains the basic startup code for a Juce application.

  ==============================================================================
*/
#include <math.h>

#include "PluginProcessor.h"
#include "JaySynthAudioProcessorEditor.h"
#include "synth/vco.h"
#include <synth/vector_utils.h>

using namespace juce;

JaySynthAudioProcessorEditor* getEditor(JaySynthAudioProcessor *pThis)
{
	return dynamic_cast<JaySynthAudioProcessorEditor*>(pThis->getActiveEditor());
}

//==============================================================================
JaySynthAudioProcessor::JaySynthAudioProcessor()
{
	currProgram = 0;
	currpatch = &patches[currProgram];
	the_editor = NULL;

	m_isWavesAvailable = isWavesAvailable();

	// Initialise the synth...
	pSynth = new JaySynth(MAX_POLYPHONY, wavesGetPath());
	pSynth->setNoteStealingEnabled(true);
	pSynth->addListener(this);

	setCurrentProgram(0);

	m_limiter_env = 0;
	startTimer (10);

#ifdef DEBUG
		SynthDebug ("Jay Synth V %s (DEBUG) initialized.\n", JucePlugin_VersionString);
#else
		SynthDebug ("Jay Synth V %s initialized.\n", JucePlugin_VersionString);
#endif


}

JaySynthAudioProcessor::~JaySynthAudioProcessor()
{
	delete (pSynth);
}

void JaySynthAudioProcessor::timerCallback()
{
	if (getEditor(this))
		actionListener.callLimiterChanged(m_limiter_active);
}

String JaySynthAudioProcessor::wavesGetPath()
{
	bool isExistent, isValid;

	File pluginfile = File::getSpecialLocation (File::currentExecutableFile).getParentDirectory();
#if LINUX
	String wavespath = pluginfile.getFullPathName() + String("/waves.bin");
#else 
	// that'S probably windows
	String wavespath = pluginfile.getFullPathName() + String("\\waves.bin");
#endif
	File wavesfile = File(wavespath);

	isExistent = wavesfile.existsAsFile();
	isValid = true;

	if (isExistent && isValid)
		return wavespath;
	
	return String::empty;
}

bool JaySynthAudioProcessor::isWavesAvailable()
{
	return (wavesGetPath() != String::empty);
}

//==============================================================================
const String JaySynthAudioProcessor::getName() const
{
    return JucePlugin_Name;
}

int JaySynthAudioProcessor::getNumParameters()
{
    return SYNTH_NUM_PARAMS;
}

void JaySynthAudioProcessor::applyCurrentMidiControllers(void)
{
	JaySynth::midiCC_info_t *pMidiCC_info;
	int i;

	for (i=0; i < NUM_MIDI_CONTROLLERS; i++)
	{
		pMidiCC_info = pSynth->getLastMidiCC_info(i);
		if (pMidiCC_info)
			applyMidiController(pMidiCC_info, false);
	}

}

void JaySynthAudioProcessor::applyMidiController(JaySynth::midiCC_info_t *pMidiCC_info, bool forceApply)
{
	int i, numDst, paramID;
	
	midiCC_container_t *pMidi_container;

	numDst = midCC_editBuffer.getNumDestinations(pMidiCC_info->ID);
	for (i=0; i < numDst; i++)
	{
		pMidi_container = midCC_editBuffer.getDestination(pMidiCC_info->ID, i);
		paramID = midCC_editBuffer.getParamID(pMidi_container);
		if (midCC_editBuffer.doApplyOnPatchInit(pMidi_container) || forceApply)
		{
			if (midCC_editBuffer.doChangeParameter(pMidi_container))
			{
				setParam(paramID, toParam(midCC_editBuffer.getParam(pMidi_container), pMidiCC_info->value), false, false);
			}
			else
			{
				controlParam(midCC_editBuffer.getParamID(pMidi_container), toParam(midCC_editBuffer.getControl(pMidi_container), pMidiCC_info->value));
			}
		}
	}

}

void JaySynthAudioProcessor::synthChanged(int type, void *pData)
{
	JaySynth::midiCC_info_t *pMidiCC_info;
	JaySynth::midi_note_info_t *pMidi_note_info;
	JaySynth::midi_quarter_clock_info_t *pMidi_quarter_info;
	
	switch(type)
	{
		case JaySynth::SYNTH_CHANGED_PARAM:
			if (getEditor(this) != NULL)
				actionListener.callParamChanged(*((int*)pData));
		break;

		case JaySynth::SYNTH_CHANGED_MIDICC:
			pMidiCC_info = (JaySynth::midiCC_info_t*)pData;
			applyMidiController(pMidiCC_info, true);
			if (getEditor(this) != NULL)
				actionListener.callMidiControllerChanged(pMidiCC_info->channel, pMidiCC_info->ID, (float)pMidiCC_info->value);
		break;

		case JaySynth::SYNTH_CHANGED_NOTE_PRESSED:
			pMidi_note_info = (JaySynth::midi_note_info_t*)pData;
			if (getEditor(this) != NULL)
				actionListener.callMidiNotePressed(pMidi_note_info->note, (float)pMidi_note_info->velocity);
		break;

		case JaySynth::SYNTH_CHANGED_NOTE_RELEASED:
			pMidi_note_info = (JaySynth::midi_note_info_t*)pData;
			if (getEditor(this) != NULL)
				actionListener.callMidiNoteReleased(pMidi_note_info->note, (float)pMidi_note_info->velocity);
		break;

		case JaySynth::SYNTH_CHANGED_NUM_VOICES_PLAYING:
			if (getEditor(this) != NULL)
				actionListener.callNumVoicesPlaying(*((int*)pData));
		break;

		case JaySynth::SYNTH_CHANGED_MIDICLOCK:
			pMidi_quarter_info = (JaySynth::midi_quarter_clock_info_t*)pData;
			if (getEditor(this) != NULL)
				actionListener.callMidiClock(pMidi_quarter_info->bpm, pMidi_quarter_info->type);
		break;

		default:
			break;
	}


};

float JaySynthAudioProcessor::getParameter (int index)
{
	param_info_t *pParamInfo = getParamInfo(index);

	if (!pParamInfo)
		return 0;

	return (float)toSlider(pParamInfo, pSynth->getParameter(index));
}

void JaySynthAudioProcessor::setParameter (int index, float newValue)
{
	param_info_t *pParamInfo = getParamInfo(index);

	if (!pParamInfo)
		return;

	setParam(index, toParam(pParamInfo, (synth_float_t)newValue), true, false);

}

synth_float_t JaySynthAudioProcessor::getParam(int index)
{
	return (synth_float_t)pSynth->getParameter(index);
}

void JaySynthAudioProcessor::setParam(int index, synth_float_t param, bool doModifyPatch, bool doNotifyHost)
{
	param_info_t *pParamInfo = getParamInfo(index);

	if (!pParamInfo)
		return;

	if (doModifyPatch)
	{
		setPatchModified();
	}

	if (doNotifyHost)
		sendParamChangeToHost(index, (float)toSlider(pParamInfo, param));

	pSynth->setParameter(index, clampParam(pParamInfo, param));

}

void JaySynthAudioProcessor::controlParam(int index, synth_float_t param)
{
	pSynth->setControl(index, param);
}

const String JaySynthAudioProcessor::getParameterName (int index)
{
	param_info_t *pParamInfo = getParamInfo(index);

	if (pParamInfo)
		return pParamInfo->pName;

    return String::empty;
}

const String JaySynthAudioProcessor::getParameterText (int index)
{
	param_info_t *pParamInfo = getParamInfo(index);

	switch (index)
    {
		case SYNTH_PARAM_VCF_TYPE:
			switch ((int)pSynth->getParameter(index))
			{
				case VCF_FILTERTYPE_LOWPASS:
					return "LPF";
				case VCF_FILTERTYPE_HIGHPASS:
					return "HPF";
				case VCF_FILTERTYPE_BANDPASS:
					return "BPF";
				default:
					return "Unknown (" + String((int)pSynth->getParameter(index)) + ")";
			}

		case SYNTH_PARAM_NOISE_ENABLE:
		case SYNTH_PARAM_OSC_0_ENABLE:
		case SYNTH_PARAM_OSC_1_ENABLE:
			if (!(int)pSynth->getParameter(index))
				return "Off";
			return "On";

		case SYNTH_PARAM_NOISE_SHAPE:
			switch ((int)pSynth->getParameter(index))
			{
				case VOICE_NOISE_SHAPE_WHITE:
					return "Uniform";
				case VOICE_NOISE_SHAPE_PINK:
					return "Gauss";
				default:
					return "Unknown (" + String((int)pSynth->getParameter(index)) + ")";
			}

		case SYNTH_PARAM_OSC_0_WAVEFORM:
		case SYNTH_PARAM_OSC_1_WAVEFORM:
			switch ((int)pSynth->getParameter(index))
			{
				case OSC_WAVEFORM_SAWTOOTH:
					return "Saw";
				case OSC_WAVEFORM_SQUARE:
					return "Pulse";
				case OSC_WAVEFORM_TRIANGLE:
					return "Tri";
				case OSC_WAVEFORM_SINE:
					return "Sine";
				case OSC_WAVEFORM_WAVETABLE:
					return "Wavetable";
				case OSC_WAVEFORM_IMPULSE:
					return "Impulse";
				default:
					return "Unknown (" + String((int)pSynth->getParameter(index)) + ")";
			}

		case SYNTH_PARAM_LFO_0_SHAPE:
		case SYNTH_PARAM_LFO_1_SHAPE:
		case SYNTH_PARAM_LFO_2_SHAPE:
		case SYNTH_PARAM_LFO_3_SHAPE:
			switch ((int)pSynth->getParameter(index))
			{
				case LFO_WAVEFORM_SINE:
					return "Sine";
				case LFO_WAVEFORM_COSINE:
					return "Cosine";
				case LFO_WAVEFORM_SAW:
					return "Saw";
				case LFO_WAVEFORM_SQUARE:
					return "Square";
				case LFO_WAVEFORM_TRIANGLE:
					return "Tri";
				case LFO_WAVEFORM_SH_UNI:
					return "S/H 1";
				case LFO_WAVEFORM_SH_GAUSS:
					return "S/H 2";
				default:
					return "Unknown (" + String((int)pSynth->getParameter(index)) + ")";
			}

		case SYNTH_PARAM_OSC_0_AM_SRC_0:
		case SYNTH_PARAM_OSC_1_AM_SRC_0:
		case SYNTH_PARAM_OSC_0_FM_SRC_0:
		case SYNTH_PARAM_OSC_1_FM_SRC_0:
		case SYNTH_PARAM_OSC_0_PWM_SRC_0:
		case SYNTH_PARAM_OSC_1_PWM_SRC_0:
		case SYNTH_PARAM_VCF_F_SRC_0:
		case SYNTH_PARAM_VCF_Q_SRC_0:
		case SYNTH_PARAM_VCA_AM_SRC_0:
		case SYNTH_PARAM_VCA_PAN_SRC_0:
		case SYNTH_PARAM_OSC_0_AM_SRC_1:
		case SYNTH_PARAM_OSC_1_AM_SRC_1:
		case SYNTH_PARAM_OSC_0_FM_SRC_1:
		case SYNTH_PARAM_OSC_1_FM_SRC_1:
		case SYNTH_PARAM_OSC_0_PWM_SRC_1:
		case SYNTH_PARAM_OSC_1_PWM_SRC_1:
		case SYNTH_PARAM_VCF_F_SRC_1:
		case SYNTH_PARAM_VCF_Q_SRC_1:
			switch ((int)pSynth->getParameter(index))
			{
				case 0:
					return "Off";
				case 1:
					return "LFO 1";
				case 2:
					return "LFO 2";
				case 3:
					return "LFO 3";
				case 4:
					return "LFO 4";
				default:
					return "Unknown (" + String((int)pSynth->getParameter(index)) + ")";
			}

		case SYNTH_PARAM_LFO_0_MODE:
		case SYNTH_PARAM_LFO_1_MODE:
		case SYNTH_PARAM_LFO_2_MODE:
		case SYNTH_PARAM_LFO_3_MODE:
			switch ((int)pSynth->getParameter(index))
			{
				case VOICE_LFO_MODE_GLOBAL:
					return "Global";
				case VOICE_LFO_MODE_INDIVIDUAL:
					return "Individual";
				default:
					return "Unknown (" + String((int)pSynth->getParameter(index)) + ")";
			}

		case SYNTH_PARAM_LFO_0_SYNC:
		case SYNTH_PARAM_LFO_1_SYNC:
		case SYNTH_PARAM_LFO_2_SYNC:
		case SYNTH_PARAM_LFO_3_SYNC:
			switch ((int)pSynth->getParameter(index))
			{
				case VOICE_LFO_SYNC_FREE:
					return "Free";
				case VOICE_LFO_SYNC_RESTART:
					return "Restart";
				default:
					return "Unknown (" + String((int)pSynth->getParameter(index)) + ")";
			}

		case SYNTH_PARAM_LFO_0_MIDISYNC_MODE:
		case SYNTH_PARAM_LFO_1_MIDISYNC_MODE:
		case SYNTH_PARAM_LFO_2_MIDISYNC_MODE:
		case SYNTH_PARAM_LFO_3_MIDISYNC_MODE:
			switch ((int)pSynth->getParameter(index))
			{
				case VOICE_LFO_MIDISYNC_OFF:
					return "Off";
				case VOICE_LFO_MIDISYNC_F:
					return "Freq";
				case VOICE_LFO_MIDISYNC_P:
					return "Phase";
				case VOICE_LFO_MIDISYNC_F_P:
					return "Freq+Phase";
				default:
					return "Unknown (" + String((int)pSynth->getParameter(index)) + ")";
			}

		default:
			if (pParamInfo->pinterval == 0.0)
				return String (pSynth->getParameter(index));
			else
				return String (pSynth->getParameter(index), (int)(-log(pParamInfo->pinterval)/log(10.0) + 0.5));
    }
}

const String JaySynthAudioProcessor::getInputChannelName (int channelIndex) const
{
    return String (channelIndex + 1);
}

const String JaySynthAudioProcessor::getOutputChannelName (int channelIndex) const
{
    return String (channelIndex + 1);
}

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

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

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

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

int JaySynthAudioProcessor::getNumPrograms()
{
    return NUM_PROGRAMS;
}

int JaySynthAudioProcessor::getCurrentProgram()
{
    return currProgram;
}

void JaySynthAudioProcessor::setCurrentProgram (int index)
{
	int i;

	param_info_t *pParamInfo;

	if ((unsigned)index >= NUM_PROGRAMS)
		index = 0;

	currProgram = index;
	currpatch = &patches[index];

	// Set SYNTH_PARAM_TOTAL_NUM_VOICES first
	setParam(SYNTH_PARAM_TOTAL_NUM_VOICES, currpatch->getParameter(SYNTH_PARAM_TOTAL_NUM_VOICES), false, false);

	midCC_editBuffer = *((JaySynthMidiCC*)currpatch->getMidiCC());

	pSynth->setMidiCC_editBuffer(&midCC_editBuffer);
	pSynth->ClearControls();
	for (i=0; i < SYNTH_NUM_PARAMS; i++)
	{
		pParamInfo = getParamInfo(i);
		setParam(i, currpatch->getParameter(i), false, true);
	}
	unisonoModeChanged();
	humanizeModeChanged();
	humanizeVarianceChanged_VCO();
	humanizeVarianceChanged_VCF();
	humanizeVarianceChanged_ENV();
	perVoiceControlsChanged();

	clearPatchModified();

	// toDo: do this by synth->setParam via message
	if (getEditor(this))
	{
		actionListener.callProgramChanged();
		for (i=0; i < SYNTH_NUM_PARAMS; i++)
			getEditor(this)->paramUpdate(i, false);
	}
	applyCurrentMidiControllers();
	updateHostDisplay();
}

const String JaySynthAudioProcessor::getProgramName (int index)
{
    return patches[index].getName();
}

void JaySynthAudioProcessor::changeProgramName (int index, const String& newName)
{
	patches[index].setName(newName);
	if (getEditor(this))
	{
		actionListener.callProgramChanged();
	}
}

//==============================================================================
void JaySynthAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
	m_fs = sampleRate;
	pSynth->setSampleRate (sampleRate);
	pSynth->setBufsize (samplesPerBlock);
	keyboardState.reset();

}

void JaySynthAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
	keyboardState.reset();

}

void JaySynthAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
	const int numSamples = buffer.getNumSamples();
    int i;
	float *buf1 = buffer.getWritePointer (0, 0);
	float *buf2 = buffer.getWritePointer (1, 0);
	synth_float_t buf[2][SYNTH_MAX_BUFSIZE];
	float synth_gain;

	AudioPlayHead::CurrentPositionInfo posInfo;
	getPlayHead()->getCurrentPosition(posInfo);
	
	// Parse midi buffer and add midi clock messages
	m_JK_MidiClock.generateMidiclock(posInfo, &midiMessages, numSamples, getSampleRate());
	
	// Now pass any incoming midi messages to our keyboard state object, and let it
    // add messages to the buffer if the user is clicking on the on-screen keys
    keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);

    // First remove garbage from buffers
	buffer.clear (0, 0, numSamples);
	buffer.clear (1, 0, numSamples);

	// and now get the synth to process these midi events and generate its output.
	// Render individual voices
	pSynth->renderNextBlock (buffer, midiMessages, 0, numSamples);

	// apply master volume
	synth_gain = (float)pSynth->getVolume();

	for (i=0; i <numSamples; i++)
		buf[0][i] = (synth_float_t)buf1[i] * synth_gain;

	for (i=0; i <numSamples; i++)
		buf[1][i] = (synth_float_t)buf2[i] * synth_gain;

	m_limiter_active = 0;

	synth_float_t master_gain = 0.5;
	
#ifdef WITH_LIMITER		
	synth_float_t limiter_env[SYNTH_MAX_BUFSIZE];
	synth_float_t limiter_gain[SYNTH_MAX_BUFSIZE];
	synth_float_t x, limiter_ar, limiter_af, limiter_threshold;

	// Limiter
	limiter_ar = 5.0/(SYNTH_LIMITER_RISE_TIME*m_fs);
	limiter_af = 5.0/(SYNTH_LIMITER_FALL_TIME*m_fs);
	limiter_threshold = pow((synth_float_t)10.0, (synth_float_t)SYNTH_LIMITER_THRESHOLD/20);

	for (i=0; i <numSamples; i++)
	{
		x = jsy_max(fabs(buf[0][i]), fabs(buf[1][i]));
		if (x > m_limiter_env)
		{
			m_limiter_env = (1.0-limiter_ar)*m_limiter_env + limiter_ar*x;
		}
		else
		{
			m_limiter_env = (1.0-limiter_af)*m_limiter_env;
		}

		limiter_env[i] = m_limiter_env;
	}

	for (i=0; i <numSamples; i++)
	{
		limiter_gain[i] = jsy_min(1.0, 1.0/(limiter_env[i] + 1.0e-6));
		if (limiter_gain[i] < 1.0)
		{
			m_limiter_active = 1;
			break;
		}
	}

	for (i=0; i <numSamples; i++)
	{
		buf1[i] = (float)(master_gain*buf[0][i]*limiter_gain[i]);
	}

	for (i=0; i <numSamples; i++)
	{
		buf2[i] = (float)(master_gain*buf[1][i]*limiter_gain[i]);
	}
#else
	for (i=0; i <numSamples; i++)
	{
		buf1[i] = (float)(master_gain*buf[0][i]);
	}

	for (i=0; i <numSamples; i++)
	{
		buf2[i] = (float)(master_gain*buf[1][i]);
	}
#endif

    // In case we have more outputs than inputs, we'll clear any output
    // channels that didn't contain input data, (because these aren't
    // guaranteed to be empty - they may contain garbage).
  //  for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
  //  {
    //    buffer.clear (i, 0, buffer.getNumSamples());
   // }
}

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

AudioProcessorEditor* JaySynthAudioProcessor::createEditor()
{
	int i;

	the_editor = (void*) new JaySynthAudioProcessorEditor (this, m_isWavesAvailable);

	if (the_editor)
	{
		actionListener.callProgramChanged();
		for (i=0; i < SYNTH_NUM_PARAMS; i++)
			((JaySynthAudioProcessorEditor*)the_editor)->paramUpdate(i, false);
	}

	return (AudioProcessorEditor*)the_editor;
}

//==============================================================================
// magic number to identify memory blocks that we've stored as XML
const uint32 magicXmlNumber = 0x21324356;

void JaySynthAudioProcessor::copyXmlToBinary (const XmlElement& xml, juce::MemoryBlock& destData)
{
    const String xmlString (xml.createDocument (String::empty, true, false));
    const int stringLength = xmlString.getNumBytesAsUTF8();

    destData.setSize ((size_t) stringLength + 10);

    char* const d = static_cast<char*> (destData.getData());
    *(uint32*) d = ByteOrder::swapIfBigEndian ((const uint32) magicXmlNumber);
    *(uint32*) (d + 4) = ByteOrder::swapIfBigEndian ((const uint32) stringLength);

    xmlString.copyToUTF8 (d + 8, stringLength + 1);
}

XmlElement* JaySynthAudioProcessor::getXmlFromBinary (const void* data,
                                              const int sizeInBytes)
{
	XmlElement *pResult = nullptr;
    if (sizeInBytes > 8
         && ByteOrder::littleEndianInt (data) == magicXmlNumber)
    {
        const int stringLength = (int) ByteOrder::littleEndianInt (addBytesToPointer (data, 4));
		XmlDocument doc(String::fromUTF8 ((static_cast<const char*> (data)) + 8, jmin ((sizeInBytes - 8), stringLength)));

        if (stringLength > 0)
            pResult = doc.getDocumentElement();

		if (doc.getLastParseError() != String::empty)
		{
			deleteAndZero(pResult);
		}
    }
    return pResult;
}

const String JaySynthAudioProcessor::getParameterNameXML (int index)
{
	String name = getParameterName(index);
	name = name.removeCharacters (String("."));
	name = name.replaceCharacter (' ','_');

    return name;
}

void JaySynthAudioProcessor::commitEditBuffer()
{
	if (isPatchModified() || midCC_editBuffer.isMidiCCmodified())
	{
		for (int i=0; i < SYNTH_NUM_PARAMS; i++)
		{
			synth_float_t value = getParam(i);
			currpatch->setParameter(i, value);		// Copy synth param to current program 
		}
		JaySynthMidiCC *midiCC = (JaySynthMidiCC*)currpatch->getMidiCC();
		midiCC->copyMidiCC(&midCC_editBuffer); // Copy MidiCC edit buffer to patch
	}
}

void JaySynthAudioProcessor::patchEncodeXml(XmlElement *xml, JaySynthSound const *patch)
{
	XmlElement *xml_patch = xml->createNewChildElement("Patch");
	xml_patch->setAttribute ("NAME", patch->getName());
	for (int i=0; i < SYNTH_NUM_PARAMS; i++)
	{
		synth_float_t value = patch->getParameter(i);
	    xml_patch->setAttribute (getParameterNameXML(i), String(value, 10));
	}
	patch->getMidiCC()->export_midi_velkey(xml->createNewChildElement("VelKey"));
	patch->getMidiCC()->export_midi_cc(xml->createNewChildElement("MidiCC"));

}

void JaySynthAudioProcessor::patchDecodeXml(const XmlElement *xml, JaySynthSound *patch)
{
	// ok, now pull out our parameters..
	patch->setName(xml->getStringAttribute("NAME"));
	for (int i=0; i < SYNTH_NUM_PARAMS; i++)
	{
		synth_float_t value = (synth_float_t) xml->getDoubleAttribute (getParameterNameXML(i), patch->getParameter(i));
		if (i==SYNTH_PARAM_TUNE)
		{
			if (value > 400.0)
			{
				value = 1200*log(value/440)/log(2.0);
			}
		}
		patch->setParameter(i, value);
	}
}

void JaySynthAudioProcessor::patchDecodeXml_legacy(const XmlElement& xml, JaySynthSound *patch)
{
	// ok, now pull out our parameters..
	patch->setName(xml.getStringAttribute("NAME"));
	for (int i=0; i < SYNTH_NUM_PARAMS; i++)
	{
		synth_float_t value = (synth_float_t) xml.getDoubleAttribute (getParameterNameXML(i), patch->getParameter(i));
		if (i==SYNTH_PARAM_TUNE)
		{
			if (value > 400.0)
			{
				value = 1200*log(value/440)/log(2.0);
			}
		}
		patch->setParameter(i, value);
	}
}

void JaySynthAudioProcessor::bankEncodeXml(XmlElement *xml)
{
	XmlElement *xml_bank = xml->createNewChildElement("Bank");
	for (int i=0; i < NUM_PROGRAMS; i++)
	{
		XmlElement *xml_program = xml_bank->createNewChildElement (String("Program_") + String(i));
		JaySynthSound *pPatch = &patches[i];
		xml_program->setAttribute ("ID", i);
		patchEncodeXml(xml_program, pPatch);
	}
}

void JaySynthAudioProcessor::bankDecodeXml(const XmlElement *_xml)
{
	XmlElement *xml = (XmlElement*)_xml;
	while(xml)
	{
		// Read ID
		int progID = xml->getIntAttribute("ID");
		if (progID >= NUM_PROGRAMS)
		{
			break;
		}
		JaySynthSound *pPatch = &patches[progID];
		{
			patchImportXml(xml, pPatch);
		}
		xml = xml->getNextElement();
	}
}

void JaySynthAudioProcessor::getStateInformation (MemoryBlock& destData)
{
	commitEditBuffer();

	// Create an outer XML element..
	ScopedPointer <XmlElement> xml = new XmlElement("JSynth");
	bankEncodeXml(xml);
	
	MemoryOutputStream outputStream(destData, false);
	MemoryBlock tmpBlock(destData.getSize(), /*initialiseToZero*/true);
	GZIPCompressorOutputStream GZIPEnc(&outputStream, /*compressionLevel*/0, /*deleteDestStreamWhenDestroyed*/false, /*windowBits*/0);

    // then use this helper function to stuff it into the binary blob and return it..
    copyXmlToBinary (*xml, tmpBlock);
	GZIPEnc.write(tmpBlock.getData(), tmpBlock.getSize());
	outputStream.flush();
}

void JaySynthAudioProcessor::loadBankFromFile (const File &file)
{
	String ext = file.getFileExtension();
	if (String(".fxb") == ext)
	{
		MemoryBlock mem;
		file.loadFileAsData(mem);
		fxBank *bank = (fxBank*)mem.getData();
		if (String((const char*)&bank->chunkMagic) == String(cMagic))
		{
			bool is_regular = String((const char*)&bank->fxMagic) == String(bankMagic);
			bool is_opaque  = String((const char*)&bank->fxMagic) == String(chunkBankMagic);
			if (is_opaque)
			{
				uint32_t size = ByteOrder::swap((uint32_t)bank->content.data.size);
				setStateInformation(bank->content.data.chunk, (int)size);
			}
		}
	}
	else if (String(".xmb") == ext)
	{
		ScopedPointer <XmlElement> xml = XmlDocument::parse(file);
		bankImportXml(xml);
	}
}

void JaySynthAudioProcessor::bankImportXml (const XmlElement *xml)
{
	if (xml != 0)
    {
		if (xml->hasTagName ("JSynth"))
		{
			XmlElement *xml2 = xml->getFirstChildElement();
			if (xml2->hasTagName ("Bank"))
			{
				XmlElement *xml3 = xml2->getFirstChildElement();
				bankDecodeXml(xml3);
			}
		}
		else
		{
			// Legacy
			if (xml->hasTagName ("JSYNTH_BANK"))
			{
				XmlElement *xml2 = xml->getFirstChildElement();
				bankDecodeXml_legacy(*xml2);
				
			}
		}
   		setCurrentProgram(getCurrentProgram());
	}
}

void JaySynthAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
	ScopedPointer<char> pUncompressedData = new char[8192*1024];

	MemoryInputStream inputStream(data, sizeInBytes, false);
	GZIPDecompressorInputStream GZIPDec(inputStream);
	int uncompressedSize = GZIPDec.read(pUncompressedData, 8192*1024);

	void* pData = (void *)data;
	int size = sizeInBytes;
	if (uncompressedSize)
	{
		pData = (void *)pUncompressedData;
		size = uncompressedSize;
	}

	ScopedPointer<XmlElement> xml = getXmlFromBinary(pData, size);
	bankImportXml(xml);
}

void JaySynthAudioProcessor::getCurrentProgramStateInformation (MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
    // 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:

	commitEditBuffer();

	MemoryOutputStream outputStream (destData, false);
	MemoryBlock tmpBlock(2*65536, /*initialiseToZero*/true);
	GZIPCompressorOutputStream GZIPEnc(&outputStream, /*compressionLevel*/0, /*deleteDestStreamWhenDestroyed*/false, /*windowBits*/0);

	// Create an outer XML element..
	ScopedPointer<XmlElement> xml = new XmlElement("JSynth");
	patchEncodeXml(xml, currpatch);

	// then use this helper function to stuff it into the binary blob and return it..
    copyXmlToBinary (*xml, tmpBlock);
	GZIPEnc.write(tmpBlock.getData(), tmpBlock.getSize());
}

void JaySynthAudioProcessor::loadPatchFromFile (const File &file, int index)
{
	String ext = file.getFileExtension();
	if (String(".fxp") == ext)
	{
		MemoryBlock mem;
		file.loadFileAsData(mem);
		fxProgram *program = (fxProgram*)mem.getData();
		if (String((const char*)&program->chunkMagic) == String(cMagic))
		{
			bool is_regular = String((const char*)&program->fxMagic) == String(fMagic);
			bool is_opaque  = String((const char*)&program->fxMagic) == String(chunkPresetMagic);
			if (is_opaque)
			{
				uint32_t size = ByteOrder::swap((uint32_t)program->content.data.size);
				setCurrentProgramStateInformation(program->content.data.chunk, (int)size);
			}
		}
	}
	else if (String(".xmp") == ext)
	{
		ScopedPointer<XmlElement> xml = XmlDocument::parse(file);
		if (xml->hasTagName ("JSynth"))
		{
			patchImportXml(xml, currpatch);
		}
	}
}

void JaySynthAudioProcessor::patchImportXml (const XmlElement *xml, JaySynthSound *patch)
{
	XmlElement *xml2 = xml->getFirstChildElement();
	while(xml2)
	{
		if (xml2->hasTagName ("Patch"))
		{
			patchDecodeXml(xml2, patch);
			XmlElement *pXML_MidiCc = xml2->getChildByName ("JSYNTH_MIDICONTROLLER_TABLE");
			if (pXML_MidiCc)
			{
				((JaySynthMidiCC*)patch->getMidiCC())->import_midi_cc_legacy(pXML_MidiCc);
			}
		}
		else if (xml->hasTagName ("JSYNTH_PATCH"))
		{
			patchDecodeXml_legacy(*xml, patch);
			XmlElement *pXML_MidiCc = xml->getChildByName ("JSYNTH_MIDICONTROLLER_TABLE");
			((JaySynthMidiCC*)patch->getMidiCC())->import_midi_cc_legacy(pXML_MidiCc);

		}
		else if (xml2->hasTagName ("VelKey"))
		{
			((JaySynthMidiCC*)patch->getMidiCC())->import_midi_velkey(xml->getChildByName ("VelKey"));
		}
		else if (xml2->hasTagName ("MidiCC"))
		{
			((JaySynthMidiCC*)patch->getMidiCC())->import_midi_cc(xml->getChildByName ("MidiCC"));
		}
		xml2 = xml2->getNextElement();
	};
	setCurrentProgram(getCurrentProgram());
}

void JaySynthAudioProcessor::setCurrentProgramStateInformation (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.
    int i, uncompressedSize;
	synth_float_t value;
	void* pData;
	int size;

	ScopedPointer<char> pUncompressedData;
	pUncompressedData = new char[2*65536];

	MemoryInputStream inputStream(data, sizeInBytes, false);
	GZIPDecompressorInputStream GZIPDec(inputStream);
	uncompressedSize = GZIPDec.read(pUncompressedData, 2*65536);

	pData = (void *)data;
	size = sizeInBytes;
	if (uncompressedSize)
	{
		pData = (void *)pUncompressedData;
		size = uncompressedSize;
	}
	ScopedPointer<XmlElement> xml = getXmlFromBinary(pData, size);
	if (xml)
    {
		if (xml->hasTagName ("JSynth"))
		{
			XmlElement *xml2 = xml->getFirstChildElement();
			patchImportXml(xml2, currpatch);
		}
	}
}

void JaySynthAudioProcessor::savePatchToFile (const File &file, int index)
{
	String ext = file.getFileExtension();
	if (String(".fxp") == ext)
	{
		// ToDo: implement fxp save
	}
	else if (String(".xmp") == ext)
	{
		commitEditBuffer();

		// Create an outer XML element..
		ScopedPointer<XmlElement> xml = new XmlElement("JSynth");
		patchEncodeXml(xml, currpatch);

		// Write XML to file
		xml->writeToFile (/*const File &destinationFile*/file, /*dtdToUse*/"");
		setCurrentProgram(getCurrentProgram());

	}
}

void JaySynthAudioProcessor::saveBankToFile (const File &file)
{
	String ext = file.getFileExtension();
	if (String(".fxb") == ext)
	{
		// ToDo: implement fxp save
	}
	else if (String(".xmb") == ext)
	{
		commitEditBuffer();

		// Create an outer XML element..
		ScopedPointer<XmlElement> xml = new XmlElement("JSynth");
		bankEncodeXml(xml);

		// Write XML to file
		xml->writeToFile (/*const File &destinationFile*/file, /*dtdToUse*/"");
	}
}

void JaySynthAudioProcessor::bankDecodeXml_legacy(const XmlElement& xml)
{
	// make sure that it's actually our type of XML object..
	XmlElement *pXML = (XmlElement*)&xml;

	while(pXML)
	{
		int progID = pXML->getIntAttribute("ID");
		if (progID >= NUM_PROGRAMS)
			break;

		JaySynthSound *pPatch = &patches[progID];
		patchDecodeXml_legacy(*pXML, pPatch);
		XmlElement *pXML_MidiCc = pXML->getChildByName ("JSYNTH_MIDICONTROLLER_TABLE");
		((JaySynthMidiCC*)pPatch->getMidiCC())->import_midi_cc(pXML_MidiCc);
		pXML = pXML->getNextElement();
	}
}

//==============================================================================
// This creates new instances of the plugin..
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    return new JaySynthAudioProcessor();
}
