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

    This file was generated by user!

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

#include <math.h>
#include <stdio.h>

#include <synth/synth_defs.h>
#include <synth/voice.h>
#include <synth/vco.h>
#include <synth/noise.h>
#include <synth/smooth.h>
#include <synth/vcf.h>
#include <synth/lfo.h>
#include <synth/env.h>
#include <synth/param_scale.h>

#include "JaySynthSound.h"
#include "JaySynthVoice.h"
#include "JaySynthMonophonicMGR.h"
#include "JaySynthMidiCC.h"
#include "JaySynth.h"

#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
#define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif

#define WITH_OLD_HUMANIZE

#include "synth/lfo.h"
#ifndef WITH_DEV_SYNCED_LFO
#define WITH_DEV_SYNCED_LFO		1
#endif

//==============================================================================
/** A JaySynth that just plays incredible sounds.. */
JaySynth::JaySynth (int num_voices, String pathToWaves)
{
	int i, j;
	char paramName[256];
	pMidCC_editBuffer = NULL;
	
	memset(synth_common.vco.wt.wave_rawdata, 0, sizeof(synth_common.vco.wt.wave_rawdata));
	if (pathToWaves != String::empty)
	{
		File wavesFile = File(pathToWaves);
	    ScopedPointer<FileInputStream>pIn = wavesFile.createInputStream();
		pIn->read(synth_common.vco.wt.wave_rawdata, sizeof(synth_common.vco.wt.wave_rawdata));
	}

	max_num_voices = num_voices;
	total_num_voices = num_voices;

	m_pVoices = new voice_t[num_voices];
    for (i = 0; i < num_voices; i++)
        addVoice (new JaySynthVoice(&m_pVoices[i], i, &synth_common));   // These voices will play our sounds..

    addSound (new JaySynthSound());

	lastPitchWheelValue = 8192;
	lastmidiChannel = 0;

	paramInfoInit(&pb_range[0], 0, NULL);
	paramInfoInit(&pb_range[1], 1, NULL);

	paramInfoSet(&pb_range[0], 1, 1, 0.5, -12, 0, +12, 0);
	paramInfoSet(&pb_range[1], 1, 1, 0.5, -12, 0, +12, 0);

	pPer_voice_controls = new per_voice_control_t[max_num_voices];
	memset(pPer_voice_controls, 0, max_num_voices*sizeof(per_voice_control_t));

	pCurrNoteInfos = new midi_note_info_t[max_num_voices];
	memset(pCurrNoteInfos, 0, max_num_voices*sizeof(midi_note_info_t));

	ClearControls();

	memset(params, 0, SYNTH_NUM_PARAMS*sizeof(synth_float_t));
	memset(last_midiCC_info, 0, NUM_MIDI_CONTROLLERS*sizeof(midiCC_info_t));
	memset(&last_midi_note_info, 0, sizeof(last_midi_note_info));

	clearKeysPressed();

	for (i=0; i < NUM_MIDI_CONTROLLERS; i++)
		last_midiCC_info[i].ID = i;

	// Init humanize parameter from global parameter constraints
	humanize_voice_param = new param_info_t*[SYNTH_NUM_PARAMS];
	ppHumanizedSliders = new synth_float_t*[SYNTH_NUM_PARAMS];
	
	for (i=0; i < SYNTH_NUM_PARAMS; i++)
	{
		humanize_voice_param[i] = new param_info_t[max_num_voices];
		ppHumanizedSliders[i] = new synth_float_t[max_num_voices];
		for (j=0; j < max_num_voices; j++)
		{
			snprintf(paramName, sizeof(paramName), "%s Voice# %03d", getParamInfo(i)->pName, j);
			paramInfoInit(&humanize_voice_param[i][j], getParamInfo(i)->id, paramName);
			paramInfoCopy(&humanize_voice_param[i][j], getParamInfo(i));
			ppHumanizedSliders[i][j] = 0;
		}
	}
	updateParameters();

	next_possible_voice = 0;
	oldest_voice = 0;
	doHumanize = 0;
	isHumanize = 0;
	m_doMonophon = 0;
	m_isMonophon = 0;
	m_doUnison = 0;
	m_isUnison = 0;
	m_unisonGain = 1;
	m_doMonophonRetrigger = true;

	m_num_audiothreads = SystemStats::getNumCpus();
	m_ppAudioThread = new JaySynthThread*[m_num_audiothreads];
	m_ppEventAudioThreadRdy = new WaitableEvent*[m_num_audiothreads];

	SynthDebug("Creating threads...\n");
	for (i=0; i < m_num_audiothreads; i++)
	{
		m_ppEventAudioThreadRdy[i] = new WaitableEvent();
		m_ppAudioThread[i] = new JaySynthThread(m_ppEventAudioThreadRdy[i], m_pVoices, num_voices, i, m_num_audiothreads);
	}

	SynthDebug("Starting %d threads ...\n", m_num_audiothreads);
	for (i=0; i < m_num_audiothreads; i++)
	{
		m_ppAudioThread[i]->startThread (12);
	}

	m_clock_cnt_bar = 0;
	m_numMidiClocksPerBeat = 1;
	m_numMidiClocksPerBar = 1;
	m_isMidiClockStarted = false;
}

JaySynth::~JaySynth()
{
	int i, j;

	SynthDebug("Stopping threads...\n");
	for (i=0; i < m_num_audiothreads; i++)
	{
		m_ppAudioThread[i]->stopThread(10000);
	}
	SynthDebug("Deleting threads...\n");
	for (i=0; i < m_num_audiothreads; i++)
	{
		delete(m_ppAudioThread[i]);
	}
	delete(m_ppAudioThread);

	SynthDebug("delete params\n");
	paramInfoFree(&pb_range[0]);
	paramInfoFree(&pb_range[1]);

	SynthDebug("delete voices\n");
	for (i=0; i < SYNTH_NUM_PARAMS; i++)
	{
		for (j=0; j < max_num_voices; j++)
		{
			paramInfoFree(&humanize_voice_param[i][j]);
		}
		delete (humanize_voice_param[i]);
		delete (ppHumanizedSliders[i]);
	}
	delete (humanize_voice_param);
	delete (ppHumanizedSliders);

	for (i = max_num_voices; --i >= 0;)
	{
		removeVoice(i);
	}

	delete (pPer_voice_controls);
	delete (pCurrNoteInfos);
	delete (m_pVoices);
	for (i=0; i < m_num_audiothreads; i++)
	{
		delete (m_ppEventAudioThreadRdy[i]);
	}
	delete(m_ppEventAudioThreadRdy);


}

synth_float_t JaySynth::getVolume(void)
{
	return master_volume*m_unisonGain;
}

void JaySynth::humanizeParam (int paramID, bool useRandom, UINT32 random_seed, synth_float_t spread_min, synth_float_t variance)
{
#ifdef WITH_OLD_HUMANIZE
	int j;
	synth_float_t spread;

	noise_gen_t noise;

	Noise_Init(&noise, random_seed);

	for (j=0; j < max_num_voices; j++)
	{
		if (useRandom)
			do
			{
				spread = Noise_Gaussian(&noise, variance*sqrt(1.f/12), 0.0);
			} while (fabs(spread) < spread_min);

		else
			spread = variance*((synth_float_t)j/max_num_voices -0.5);

		paramInfoSet(&humanize_voice_param[paramID][j], 
					/*base*/getParamInfo(paramID)->base, 
					/*kexp*/getParamInfo(paramID)->kexp, 
					/*scenter*/getParamInfo(paramID)->scenter, 
					/*pmin*/getParamInfo(paramID)->pmin ,
					/*pcenter*/-1E6/*getParamInfo(i)->pcenter*/, 
					/*pmax*/getParamInfo(paramID)->pmax * (1+spread), 
					/*pinterval*/0);
		
		ppHumanizedSliders[paramID][j] = spread;
	}
	Noise_Free(&noise);
#else
	int j;
	synth_float_t spread, spread_dir, value_lo, value_hi;

	noise_gen_t noise;

	Noise_Init(&noise, random_seed);
	
	spread_dir = 1.0;
	for (j=0; j < GET_NUM_VOICES; j++)
	{
		if (useRandom)
		{
			do
			{
				spread = Noise_Gaussian(&noise, variance*sqrt(1.f/12), 0.0);
			} while (fabs(spread) < spread_min);
			spread = fabs(spread)*spread_dir;
		}
		else
			spread = variance*((synth_float_t)j/GET_NUM_VOICES -0.5);


		value_lo = clampParam(getParamInfo(paramID), getParamInfo(paramID)->pmin * (1 + spread));
		value_hi = clampParam(getParamInfo(paramID), getParamInfo(paramID)->pmax * (1 + spread));
		paramInfoSet(&humanize_voice_param[paramID][j], 
					/*base*/getParamInfo(paramID)->base, 
					/*kexp*/getParamInfo(paramID)->kexp, 
					/*scenter*/getParamInfo(paramID)->scenter, 
					/*pmin*/ value_lo,
					/*pcenter*/-1E6/*getParamInfo(i)->pcenter*/, 
					/*pmax*/value_hi, 
					/*pinterval*/0);
	
		spread_dir = -spread_dir;
	}
	Noise_Free(&noise);	
#endif
}

void JaySynth::setSampleRate (synth_float_t sampleRate)
{
	int i;
	Synthesiser::setCurrentPlaybackSampleRate (sampleRate);
	for (i=0; i < max_num_voices; i++)
		((JaySynthVoice*)getVoice(i))->setSampleRate(sampleRate);
	
}

void JaySynth::setBufsize (int /*bufsize*/)
{
}

void JaySynth::humanizeModeChanged(void)
{
	if (doHumanize ^ isHumanize)
	{
		isHumanize = doHumanize;
		humanizeVarianceChanged_VCO();
		humanizeVarianceChanged_VCF();
		humanizeVarianceChanged_ENV();
		updateParameters();
	}
}

void JaySynth::humanizeVarianceChanged_VCO(void)
{
#ifdef WITH_OLD_HUMANIZE
	humanizeParam(SYNTH_PARAM_TUNE, true, 0x1234561, 0, 0.5*humanize_var_vco);
//	humanizeParam(SYNTH_PARAM_OSC_0_DETUNE, true, 0x293857, 0, humanize_var_vco);
//	humanizeParam(SYNTH_PARAM_OSC_1_DETUNE, true, 0x137573, 0, humanize_var_vco);

	// Re-Read parameter
	setParameter(SYNTH_PARAM_TUNE, params[SYNTH_PARAM_TUNE]);
//	setParameter(SYNTH_PARAM_OSC_0_DETUNE, params[SYNTH_PARAM_OSC_0_DETUNE]);
//	setParameter(SYNTH_PARAM_OSC_1_DETUNE, params[SYNTH_PARAM_OSC_1_DETUNE]);
#else
	int i;
	synth_float_t f_ref;
	noise_gen_t noise;

	Noise_Init(&noise, 0x1234561);
	
	for (i=0; i < GET_NUM_VOICES; i++)
	{
		f_ref = Noise_Gaussian(&noise, 100*humanize_var_vco, 440.0);
		
		if (isHumanize)
			((JaySynthVoice*)getVoice(i))->setParameter(VOICE_PARAM_MASTER_FREF, f_ref);
		else
			((JaySynthVoice*)getVoice(i))->setParameter(VOICE_PARAM_MASTER_FREF, 440.0);

	}
	Noise_Free(&noise);
#endif
}

void JaySynth::humanizeVarianceChanged_VCF(void)
{
	humanizeParam(SYNTH_PARAM_VCF_F, true, 0x1234561, 0, 0.5*humanize_var_vcf);
	humanizeParam(SYNTH_PARAM_VCF_Q, true, 0x1234561, 0, 0.05*humanize_var_vcf);

	// Re-Read parameter
	setParameter(SYNTH_PARAM_VCF_F, params[SYNTH_PARAM_VCF_F]);
	setParameter(SYNTH_PARAM_VCF_Q, params[SYNTH_PARAM_VCF_Q]);
}

void JaySynth::humanizeVarianceChanged_ENV(void)
{
	humanizeParam(SYNTH_PARAM_VCA_ENV_A, true, 0x1234561, 0, humanize_var_env);
	humanizeParam(SYNTH_PARAM_VCA_ENV_D, true, 0x1234561, 0, humanize_var_env);
	humanizeParam(SYNTH_PARAM_VCA_ENV_R, true, 0x1234561, 0, humanize_var_env);

	humanizeParam(SYNTH_PARAM_VCF_ENV_A, true, 0x1234561, 0, humanize_var_env);
	humanizeParam(SYNTH_PARAM_VCF_ENV_D, true, 0x1234561, 0, humanize_var_env);
	humanizeParam(SYNTH_PARAM_VCF_ENV_R, true, 0x1234561, 0, humanize_var_env);

	humanizeParam(SYNTH_PARAM_ENV_2_A, true, 0x1234561, 0, humanize_var_env);
	humanizeParam(SYNTH_PARAM_ENV_2_D, true, 0x1234561, 0, humanize_var_env);
	humanizeParam(SYNTH_PARAM_ENV_2_R, true, 0x1234561, 0, humanize_var_env);

	humanizeParam(SYNTH_PARAM_ENV_3_A, true, 0x1234561, 0, humanize_var_env);
	humanizeParam(SYNTH_PARAM_ENV_3_D, true, 0x1234561, 0, humanize_var_env);
	humanizeParam(SYNTH_PARAM_ENV_3_R, true, 0x1234561, 0, humanize_var_env);

	// Re-Read parameter
	setParameter(SYNTH_PARAM_VCA_ENV_A, params[SYNTH_PARAM_VCA_ENV_A]);
	setParameter(SYNTH_PARAM_VCA_ENV_D, params[SYNTH_PARAM_VCA_ENV_D]);
	setParameter(SYNTH_PARAM_VCA_ENV_R, params[SYNTH_PARAM_VCA_ENV_R]);

	setParameter(SYNTH_PARAM_VCF_ENV_A, params[SYNTH_PARAM_VCF_ENV_A]);
	setParameter(SYNTH_PARAM_VCF_ENV_D, params[SYNTH_PARAM_VCF_ENV_D]);
	setParameter(SYNTH_PARAM_VCF_ENV_R, params[SYNTH_PARAM_VCF_ENV_R]);

	setParameter(SYNTH_PARAM_ENV_2_A, params[SYNTH_PARAM_ENV_2_A]);
	setParameter(SYNTH_PARAM_ENV_2_D, params[SYNTH_PARAM_ENV_2_D]);
	setParameter(SYNTH_PARAM_ENV_2_R, params[SYNTH_PARAM_ENV_2_R]);

	setParameter(SYNTH_PARAM_ENV_3_A, params[SYNTH_PARAM_ENV_3_A]);
	setParameter(SYNTH_PARAM_ENV_3_D, params[SYNTH_PARAM_ENV_3_D]);
	setParameter(SYNTH_PARAM_ENV_3_R, params[SYNTH_PARAM_ENV_3_R]);
}

//==============================================================================
// Parameter stuff
//==============================================================================
void JaySynth::ClearControls(void)
{
	memset(controls, 0, SYNTH_NUM_PARAMS*sizeof(synth_float_t));
}

void JaySynth::updateParameters(void)
{
	int paramID, voice;

	for (paramID=0; paramID < SYNTH_NUM_PARAMS; paramID++)
	{
		if (pMidCC_editBuffer)
		{
			for (voice=0; voice < GET_NUM_VOICES; voice++)
			{
				setPerVoiceControl(voice, SYNTH_PER_VOICE_CONTROL_VELKEY, paramID, pMidCC_editBuffer->getVelKeyControl(paramID, pCurrNoteInfos[voice].velocity, pCurrNoteInfos[voice].note));
			}
		}

		setParam(paramID, -1);
	}
}

void JaySynth::updateParameter(int paramID)
{
	int voice;

	if (paramID >= SYNTH_NUM_PARAMS)
		return;

	if (pMidCC_editBuffer)
	{
		for (voice=0; voice < GET_NUM_VOICES; voice++)
		{
			setPerVoiceControl(voice, SYNTH_PER_VOICE_CONTROL_VELKEY, paramID, pMidCC_editBuffer->getVelKeyControl(paramID, pCurrNoteInfos[voice].velocity, pCurrNoteInfos[voice].note));
		}
	}

	setParam(paramID, -1);
}

synth_float_t JaySynth::getParameter(int paramID)
{
	if (paramID < SYNTH_NUM_PARAMS)
		return params[paramID];

	return 0;
}

void JaySynth::setParameter(int paramID, synth_float_t param)
{
	params[paramID] = param;
	setParam(paramID, -1);

	paramID_changed = paramID;
    listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_PARAM, &paramID_changed);
}

void JaySynth::setControl(int paramID, synth_float_t param)
{
	controls[paramID] = param;
	setParam(paramID, -1);

	paramID_changed = paramID;
    listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_PARAM, &paramID_changed);
}

void JaySynth::setPerVoiceControl(int voice, int channel, int paramID, synth_float_t param)
{
	pPer_voice_controls[voice].ctrl[channel][paramID] = param;
	setParam(paramID, voice);
}

void JaySynth::setParam(int paramID, int voice)
{
	int i, j, voice_param_type, voice_min, voice_max;
	synth_float_t param;

	param_info_t *pParamInfo = getParamInfo(paramID);

	if (!pParamInfo)
		return;

	voice_param_type = -1;
	param = clampParam(pParamInfo, params[paramID] + controls[paramID]);
	switch (paramID)
	{

		case SYNTH_PARAM_VOLUME:
			master_volume = param/100;
		break;

		case SYNTH_PARAM_TUNE:
			voice_param_type = VOICE_PARAM_MASTER_TUNE;
		break;

		case SYNTH_PARAM_TOTAL_NUM_VOICES:
			total_num_voices = (int)param;
		break;

		case SYNTH_PARAM_VOICE_PAN_SPREAD:
			voice_param_type = VOICE_PARAM_VOICE_PAN;
		break;

		case SYNTH_PARAM_HUMANIZE_ENABLE:
			doHumanize = param >= 0.5;
		break;

		case SYNTH_PARAM_HUMANIZE_VAR_VCO:
			humanize_var_vco = param/100;
		break;

		case SYNTH_PARAM_HUMANIZE_VAR_VCF:
			humanize_var_vcf = param/100;
		break;

		case SYNTH_PARAM_HUMANIZE_VAR_ENV:
			humanize_var_env = param/100;
		break;

		case SYNTH_PARAM_MONOPHONIC_ENABLE:
			m_doMonophon = param >= 0.5;
		break;

		case SYNTH_PARAM_MONOPHONIC_RETRIGGER_ENABLE:
			m_doMonophonRetrigger = param >= 0.5;
		break;

		case SYNTH_PARAM_UNISONO_ENABLE:
			m_doUnison = param >= 0.5;
		break;

		case SYNTH_PARAM_UNISONO_NUM_VOICES:
			unison_num_voices = (int)param;
		break;

		case SYNTH_PARAM_PORTAMENTO_TIME:
		voice_param_type = VOICE_PARAM_PORTAMENTO_TIME;
		break;

		case SYNTH_PARAM_NOISE_ENABLE:
		voice_param_type = VOICE_PARAM_NOISE_ENABLE;
		break;

		case SYNTH_PARAM_NOISE_LEVEL:
		voice_param_type = VOICE_PARAM_NOISE_LEVEL;
		break;

		case SYNTH_PARAM_NOISE_SHAPE:
		voice_param_type = VOICE_PARAM_NOISE_SHAPE;
		break;

		case SYNTH_PARAM_VCF_TYPE:
		voice_param_type = VOICE_PARAM_VCF_TYPE;
		break;

		case SYNTH_PARAM_VCF_ORDER:
		voice_param_type = VOICE_PARAM_VCF_ORDER;
		break;

		case SYNTH_PARAM_VCF_F:
		voice_param_type = VOICE_PARAM_VCF_F;
		break;

		case SYNTH_PARAM_VCF_Q:
		voice_param_type = VOICE_PARAM_VCF_Q;
		break;

		case SYNTH_PARAM_OSC_0_ENABLE:
		case SYNTH_PARAM_OSC_1_ENABLE:
		voice_param_type = VOICE_PARAM_OSC_0_ENABLE + (paramID - SYNTH_PARAM_OSC_0_ENABLE);
		break;

		case SYNTH_PARAM_OSC_0_RESTART_ON_TRIGGER:
		case SYNTH_PARAM_OSC_1_RESTART_ON_TRIGGER:
		voice_param_type = VOICE_PARAM_OSC_0_RESTART_ON_TRIGGER + (paramID - SYNTH_PARAM_OSC_0_RESTART_ON_TRIGGER);
		break;

		case SYNTH_PARAM_OSC_0_LEVEL:
		case SYNTH_PARAM_OSC_1_LEVEL:
		voice_param_type = VOICE_PARAM_OSC_0_LEVEL + (paramID - SYNTH_PARAM_OSC_0_LEVEL);
		break;

		case SYNTH_PARAM_OSC_0_DUTYCYCLE:
		case SYNTH_PARAM_OSC_1_DUTYCYCLE:
		voice_param_type = VOICE_PARAM_OSC_0_DUTYCYCLE + (paramID - SYNTH_PARAM_OSC_0_DUTYCYCLE);
		break;

		case SYNTH_PARAM_OSC_0_DETUNE:
		case SYNTH_PARAM_OSC_1_DETUNE:
		voice_param_type = VOICE_PARAM_OSC_0_DETUNE + (paramID - SYNTH_PARAM_OSC_0_DETUNE);
		break;

		case SYNTH_PARAM_OSC_0_TRANSPOSE:
		case SYNTH_PARAM_OSC_1_TRANSPOSE:
		voice_param_type = VOICE_PARAM_OSC_0_TRANSPOSE + (paramID - SYNTH_PARAM_OSC_0_TRANSPOSE);
		break;

		case SYNTH_PARAM_OSC_0_PB_RANGE_MIN:
		case SYNTH_PARAM_OSC_1_PB_RANGE_MIN:
		paramInfoSetPMIN(&pb_range[(paramID - SYNTH_PARAM_OSC_0_PB_RANGE_MIN)], param);
		handlePitchWheel(lastmidiChannel, lastPitchWheelValue);
		break;

		case SYNTH_PARAM_OSC_0_PB_RANGE_MAX:
		case SYNTH_PARAM_OSC_1_PB_RANGE_MAX:
		paramInfoSetPMAX(&pb_range[(paramID - SYNTH_PARAM_OSC_0_PB_RANGE_MAX)], param);
		handlePitchWheel(lastmidiChannel, lastPitchWheelValue);
		break;

		case SYNTH_PARAM_OSC_0_WAVEFORM:
		case SYNTH_PARAM_OSC_1_WAVEFORM:
		voice_param_type = VOICE_PARAM_OSC_0_WAVEFORM + (paramID - SYNTH_PARAM_OSC_0_WAVEFORM);
		break;

		case SYNTH_PARAM_OSC_0_FM_AMT_0:
		case SYNTH_PARAM_OSC_1_FM_AMT_0:
		voice_param_type = VOICE_PARAM_OSC_0_FM_AMT_0 + (paramID - SYNTH_PARAM_OSC_0_FM_AMT_0);
		break;

		case SYNTH_PARAM_OSC_0_FM_AMT_1:
		case SYNTH_PARAM_OSC_1_FM_AMT_1:
		voice_param_type = VOICE_PARAM_OSC_0_FM_AMT_1 + (paramID - SYNTH_PARAM_OSC_0_FM_AMT_1);
		break;

		case SYNTH_PARAM_OSC_0_FM_SRC_0:
		case SYNTH_PARAM_OSC_1_FM_SRC_0:
		voice_param_type = VOICE_PARAM_OSC_0_FM_SRC_0 + (paramID - SYNTH_PARAM_OSC_0_FM_SRC_0);
		break;

		case SYNTH_PARAM_OSC_0_FM_SRC_1:
		case SYNTH_PARAM_OSC_1_FM_SRC_1:
		voice_param_type = VOICE_PARAM_OSC_0_FM_SRC_1 + (paramID - SYNTH_PARAM_OSC_0_FM_SRC_1);
		break;

		case SYNTH_PARAM_OSC_0_FM_SRC_OP:
		case SYNTH_PARAM_OSC_1_FM_SRC_OP:
		voice_param_type = VOICE_PARAM_OSC_0_FM_SRC_OP + (paramID - SYNTH_PARAM_OSC_0_FM_SRC_OP);
		break;

		case SYNTH_PARAM_OSC_0_AM_AMT_0:
		case SYNTH_PARAM_OSC_1_AM_AMT_0:
		voice_param_type = VOICE_PARAM_OSC_0_AM_AMT_0 + (paramID - SYNTH_PARAM_OSC_0_AM_AMT_0);
		break;

		case SYNTH_PARAM_OSC_0_AM_AMT_1:
		case SYNTH_PARAM_OSC_1_AM_AMT_1:
		voice_param_type = VOICE_PARAM_OSC_0_AM_AMT_1 + (paramID - SYNTH_PARAM_OSC_0_AM_AMT_1);
		break;

		case SYNTH_PARAM_OSC_0_AM_SRC_0:
		case SYNTH_PARAM_OSC_1_AM_SRC_0:
		voice_param_type = VOICE_PARAM_OSC_0_AM_SRC_0 + (paramID - SYNTH_PARAM_OSC_0_AM_SRC_0);
		break;

		case SYNTH_PARAM_OSC_0_AM_SRC_1:
		case SYNTH_PARAM_OSC_1_AM_SRC_1:
		voice_param_type = VOICE_PARAM_OSC_0_AM_SRC_1 + (paramID - SYNTH_PARAM_OSC_0_AM_SRC_1);
		break;

		case SYNTH_PARAM_OSC_0_AM_SRC_OP:
		case SYNTH_PARAM_OSC_1_AM_SRC_OP:
		voice_param_type = VOICE_PARAM_OSC_0_AM_SRC_OP + (paramID - SYNTH_PARAM_OSC_0_AM_SRC_OP);
		break;

		case SYNTH_PARAM_OSC_0_PWM_AMT_0:
		case SYNTH_PARAM_OSC_1_PWM_AMT_0:
		voice_param_type = VOICE_PARAM_OSC_0_PWM_AMT_0 + (paramID - SYNTH_PARAM_OSC_0_PWM_AMT_0);
		break;

		case SYNTH_PARAM_OSC_0_PWM_AMT_1:
		case SYNTH_PARAM_OSC_1_PWM_AMT_1:
		voice_param_type = VOICE_PARAM_OSC_0_PWM_AMT_1 + (paramID - SYNTH_PARAM_OSC_0_PWM_AMT_1);
		break;

		case SYNTH_PARAM_OSC_0_PWM_SRC_0:
		case SYNTH_PARAM_OSC_1_PWM_SRC_0:
		voice_param_type = VOICE_PARAM_OSC_0_PWM_SRC_0 + (paramID - SYNTH_PARAM_OSC_0_PWM_SRC_0);
		break;

		case SYNTH_PARAM_OSC_0_PWM_SRC_1:
		case SYNTH_PARAM_OSC_1_PWM_SRC_1:
		voice_param_type = VOICE_PARAM_OSC_0_PWM_SRC_1 + (paramID - SYNTH_PARAM_OSC_0_PWM_SRC_1);
		break;

		case SYNTH_PARAM_OSC_0_PWM_SRC_OP:
		case SYNTH_PARAM_OSC_1_PWM_SRC_OP:
		voice_param_type = VOICE_PARAM_OSC_0_PWM_SRC_OP + (paramID - SYNTH_PARAM_OSC_0_PWM_SRC_OP);
		break;

		case SYNTH_PARAM_ENV_0_A:
		case SYNTH_PARAM_ENV_1_A:
		case SYNTH_PARAM_ENV_2_A:
		case SYNTH_PARAM_ENV_3_A:
		voice_param_type = VOICE_PARAM_ENV_0_A + (paramID - SYNTH_PARAM_ENV_0_A);
		break;

		case SYNTH_PARAM_ENV_0_D:
		case SYNTH_PARAM_ENV_1_D:
		case SYNTH_PARAM_ENV_2_D:
		case SYNTH_PARAM_ENV_3_D:
		voice_param_type = VOICE_PARAM_ENV_0_D + (paramID - SYNTH_PARAM_ENV_0_D);
		break;

		case SYNTH_PARAM_ENV_0_S:
		case SYNTH_PARAM_ENV_1_S:
		case SYNTH_PARAM_ENV_2_S:
		case SYNTH_PARAM_ENV_3_S:
		voice_param_type = VOICE_PARAM_ENV_0_S + (paramID - SYNTH_PARAM_ENV_0_S);
		break;

		case SYNTH_PARAM_ENV_0_R:
		case SYNTH_PARAM_ENV_1_R:
		case SYNTH_PARAM_ENV_2_R:
		case SYNTH_PARAM_ENV_3_R:
		voice_param_type = VOICE_PARAM_ENV_0_R + (paramID - SYNTH_PARAM_ENV_0_R);
		break;

		case SYNTH_PARAM_VCA_AM_AMT_0:
		voice_param_type = VOICE_PARAM_VCA_AM_AMT_0;
		break;

		case SYNTH_PARAM_VCA_AM_SRC_0:
		voice_param_type = VOICE_PARAM_VCA_AM_SRC_0;
		break;

		case SYNTH_PARAM_VCA_PAN_AMT_0:
		voice_param_type = VOICE_PARAM_VCA_PAN_AMT_0;
		break;

		case SYNTH_PARAM_VCA_PAN_SRC_0:
		voice_param_type = VOICE_PARAM_VCA_PAN_SRC_0;
		break;

		case SYNTH_PARAM_VCA_ENV_AMT:
		voice_param_type = VOICE_PARAM_VCA_ENV_GAIN;
		break;

		case SYNTH_PARAM_VCF_ENV_AMT:
		voice_param_type = VOICE_PARAM_VCF_ENV_GAIN;
		break;

		case SYNTH_PARAM_VCF_F_AMT_0:
		voice_param_type = VOICE_PARAM_VCF_F_AMT_0;
		break;

		case SYNTH_PARAM_VCF_F_AMT_1:
		voice_param_type = VOICE_PARAM_VCF_F_AMT_1;
		break;

		case SYNTH_PARAM_VCF_F_SRC_0:
		voice_param_type = VOICE_PARAM_VCF_F_SRC_0;
		break;

		case SYNTH_PARAM_VCF_F_SRC_1:
		voice_param_type = VOICE_PARAM_VCF_F_SRC_1;
		break;

		case SYNTH_PARAM_VCF_F_SRC_OP:
		voice_param_type = VOICE_PARAM_VCF_F_SRC_OP;
		break;

		case SYNTH_PARAM_VCF_Q_AMT_0:
		voice_param_type = VOICE_PARAM_VCF_Q_AMT_0;
		break;

		case SYNTH_PARAM_VCF_Q_AMT_1:
		voice_param_type = VOICE_PARAM_VCF_Q_AMT_1;
		break;

		case SYNTH_PARAM_VCF_Q_SRC_0:
		voice_param_type = VOICE_PARAM_VCF_Q_SRC_0;
		break;

		case SYNTH_PARAM_VCF_Q_SRC_1:
		voice_param_type = VOICE_PARAM_VCF_Q_SRC_1;
		break;

		case SYNTH_PARAM_VCF_Q_SRC_OP:
		voice_param_type = VOICE_PARAM_VCF_Q_SRC_OP;
		break;

		case SYNTH_PARAM_LFO_0_SPEED:
		case SYNTH_PARAM_LFO_1_SPEED:
		case SYNTH_PARAM_LFO_2_SPEED:
		case SYNTH_PARAM_LFO_3_SPEED:
		voice_param_type = VOICE_PARAM_LFO_0_SPEED + (paramID - SYNTH_PARAM_LFO_0_SPEED);
		break;

		case SYNTH_PARAM_LFO_0_SMOOTH:
		case SYNTH_PARAM_LFO_1_SMOOTH:
		case SYNTH_PARAM_LFO_2_SMOOTH:
		case SYNTH_PARAM_LFO_3_SMOOTH:
		voice_param_type = VOICE_PARAM_LFO_0_SMOOTH + (paramID - SYNTH_PARAM_LFO_0_SMOOTH);
		break;

		case SYNTH_PARAM_LFO_0_DELAY:
		case SYNTH_PARAM_LFO_1_DELAY:
		case SYNTH_PARAM_LFO_2_DELAY:
		case SYNTH_PARAM_LFO_3_DELAY:
		voice_param_type = VOICE_PARAM_LFO_0_DELAY + (paramID - SYNTH_PARAM_LFO_0_DELAY);
		break;

		case SYNTH_PARAM_LFO_0_ATTACK:
		case SYNTH_PARAM_LFO_1_ATTACK:
		case SYNTH_PARAM_LFO_2_ATTACK:
		case SYNTH_PARAM_LFO_3_ATTACK:
		voice_param_type = VOICE_PARAM_LFO_0_ATTACK + (paramID - SYNTH_PARAM_LFO_0_ATTACK);
		break;

		case SYNTH_PARAM_LFO_0_SHAPE:
		case SYNTH_PARAM_LFO_1_SHAPE:
		case SYNTH_PARAM_LFO_2_SHAPE:
		case SYNTH_PARAM_LFO_3_SHAPE:
		voice_param_type = VOICE_PARAM_LFO_0_SHAPE + (paramID - SYNTH_PARAM_LFO_0_SHAPE);
		break;

		case SYNTH_PARAM_LFO_0_MODE:
		case SYNTH_PARAM_LFO_1_MODE:
		case SYNTH_PARAM_LFO_2_MODE:
		case SYNTH_PARAM_LFO_3_MODE:
		voice_param_type = VOICE_PARAM_LFO_0_MODE + (paramID - SYNTH_PARAM_LFO_0_MODE);
		break;

		case SYNTH_PARAM_LFO_0_SYNC:
		case SYNTH_PARAM_LFO_1_SYNC:
		case SYNTH_PARAM_LFO_2_SYNC:
		case SYNTH_PARAM_LFO_3_SYNC:
		voice_param_type = VOICE_PARAM_LFO_0_SYNC + (paramID - SYNTH_PARAM_LFO_0_SYNC);
		break;

		case SYNTH_PARAM_LFO_0_SYMMETRY:
		case SYNTH_PARAM_LFO_1_SYMMETRY:
		case SYNTH_PARAM_LFO_2_SYMMETRY:
		case SYNTH_PARAM_LFO_3_SYMMETRY:
		voice_param_type = VOICE_PARAM_LFO_0_SYMMETRY + (paramID - SYNTH_PARAM_LFO_0_SYMMETRY);
		break;

		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:
		voice_param_type = VOICE_PARAM_LFO_0_MIDISYNC_MODE + (paramID - SYNTH_PARAM_LFO_0_MIDISYNC_MODE);
		break;

		case SYNTH_PARAM_LFO_0_MIDISYNC_BEATDIV:
		case SYNTH_PARAM_LFO_1_MIDISYNC_BEATDIV:
		case SYNTH_PARAM_LFO_2_MIDISYNC_BEATDIV:
		case SYNTH_PARAM_LFO_3_MIDISYNC_BEATDIV:
		voice_param_type = VOICE_PARAM_LFO_0_MIDISYNC_BEATDIV + (paramID - SYNTH_PARAM_LFO_0_MIDISYNC_BEATDIV);
		break;

		default:
		break;
    }

	if (voice_param_type >= 0)
	{
		voice_min = voice;
		voice_max = voice;
		if (voice < 0)
		{
			voice_min = 0;
			voice_max = GET_NUM_VOICES-1;
		}

		if (isHumanize)
		{
			synth_float_t slider;
			for (i=voice_min; i <= voice_max; i++)
			{
				slider = toSlider(pParamInfo, params[paramID]) + ppHumanizedSliders[paramID][i];
				param = toParam(pParamInfo, slider) + controls[paramID];
				for (j=0; j < SYNTH_PER_VOICE_CONTROL_SIZE; j++)
					param += pPer_voice_controls[i].ctrl[j][paramID];

				param = clampParam(pParamInfo, param);
				((JaySynthVoice*)getVoice(i))->setParameter(voice_param_type, param);
			}
		}
		else
		{
			for (i=voice_min; i <= voice_max; i++)
			{
				param = params[paramID] + controls[paramID];
				for (j=0; j < SYNTH_PER_VOICE_CONTROL_SIZE; j++)
					param += pPer_voice_controls[i].ctrl[j][paramID];

				param = clampParam(pParamInfo, param);
				((JaySynthVoice*)getVoice(i))->setParameter(voice_param_type, param);

			}
		}
	}

}

//==============================================================================
// Midi-Event stuff
//==============================================================================
void JaySynth::handlePitchWheel (const int midiChannel, const int wheelValue)
{
	int i;   
	synth_float_t scale, value;

	lastPitchWheelValue = wheelValue;
	lastmidiChannel = midiChannel;
	value = (synth_float_t)wheelValue/16384;	// PB-Range 0 to 1, center at 0.5

	scale = pow(2, toParam(&pb_range[0], value)/12);
	for (i=0; i < GET_NUM_VOICES; i++)
		((JaySynthVoice*)getVoice(i))->setParameter(VOICE_PARAM_OSC_0_SCALE, scale);
	
	scale = pow(2, toParam(&pb_range[1], value)/12);
	for (i=0; i < GET_NUM_VOICES; i++)
		((JaySynthVoice*)getVoice(i))->setParameter(VOICE_PARAM_OSC_1_SCALE, scale);

}

void JaySynth::handleController (const midiCC_info_t &midiCC_info)
{
	last_midiCC_info[midiCC_info.ID] = midiCC_info;
    listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_MIDICC, (midiCC_info_t*)&midiCC_info);

	switch (midiCC_info.ID)
    {
        case 0x40:  handleSustainPedal   (midiCC_info.channel, midiCC_info.value_raw >= 64); break;
        case 0x42:  handleSostenutoPedal (midiCC_info.channel, midiCC_info.value_raw >= 64); break;
        case 0x43:  handleSoftPedal      (midiCC_info.channel, midiCC_info.value_raw >= 64); break;
        default:    break;
    }

    const ScopedLock sl (lock);

    for (int i = 0; i < GET_NUM_VOICES; i++)
    {
        JaySynthVoice* const voice = voices.getUnchecked (i);

        if (midiCC_info.channel <= 0 || voice->isPlayingChannel (midiCC_info.channel))
            voice->controllerMoved (midiCC_info.ID, midiCC_info.value_raw);
    }
}

void JaySynth::handleSustainPedal (int midiChannel, bool isDown)
{
    jassert (midiChannel > 0 && midiChannel <= 16);
    const ScopedLock sl (lock);

    if (isDown)
    {
        sustainPedalsDown.setBit (midiChannel);
    }
    else
    {
        for (int i = 0; i < GET_NUM_VOICES; i++)
        {
            JaySynthVoice* const voice = voices.getUnchecked (i);

            if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown)
                Voicestop (voice, true);
        }

        sustainPedalsDown.clearBit (midiChannel);
    }
}

void JaySynth::handleSostenutoPedal (int midiChannel, bool isDown)
{
    jassert (midiChannel > 0 && midiChannel <= 16);
    const ScopedLock sl (lock);

    for (int i = 0; i < GET_NUM_VOICES; i++)
    {
        JaySynthVoice* const voice = voices.getUnchecked (i);

        if (voice->isPlayingChannel (midiChannel))
        {
            if (isDown)
                voice->sostenutoPedalDown = true;
            else if (voice->sostenutoPedalDown)
                Voicestop (voice, true);
        }
    }
}

void JaySynth::handleSoftPedal (int midiChannel, bool /*isDown*/)
{
    (void) midiChannel;
    jassert (midiChannel > 0 && midiChannel <= 16);
}

//==============================================================================
// Audio data rendering stuff
//==============================================================================
void JaySynth::renderNextBlock (AudioSampleBuffer& outputBuffer,
                                   const MidiBuffer& midiData,
                                   int startSample,
                                   int numSamples)
{
	int i;
	int numVoicesPlaying;
	float *buf1, *buf2;

    // must set the sample rate before using this!
	float sr = Synthesiser::getSampleRate();
    jassert (sr != 0);

	const ScopedLock sl (lock);

    MidiBuffer::Iterator midiIterator (midiData);
    midiIterator.setNextSamplePosition (startSample);
    MidiMessage m (0xf4, 0.0);

    while (numSamples > 0)
    {
        int midiEventPos;
        const bool useEvent = midiIterator.getNextEvent (m, midiEventPos)
                                && midiEventPos < startSample + numSamples;

        const int numThisTime = useEvent ? midiEventPos - startSample
                                         : numSamples;

        if (numThisTime > 0)
        {
			
			// Process global voice data
			buf1 = outputBuffer.getWritePointer (0, startSample);
			buf2 = outputBuffer.getWritePointer (1, startSample);
			VoiceProcessDataV_Common(&m_pVoices[0], buf1, buf2, numThisTime);

			// Start audio threads
			for (i=0; i < m_num_audiothreads; i++)
			{
				m_ppAudioThread[i]->go(numThisTime);
			}

			// Wait for audio threads finishing and add buffer
			for (i=0; i < m_num_audiothreads; i++)
			{
				m_ppEventAudioThreadRdy[i]->wait(-1);
				outputBuffer.addFrom(0, startSample, m_ppAudioThread[i]->getBuffer(), 0, 0, numThisTime, 1.0);
				outputBuffer.addFrom(1, startSample, m_ppAudioThread[i]->getBuffer(), 1, 0, numThisTime, 1.0);
			}
        }

		// Clear note if voice is idle
		for (int i = 0; i < max_num_voices; i++)
		{
			if (m_pVoices[i].state == note_state_idle)
			{
				if (voices.getUnchecked(i)->currentlyPlayingNote >= 0)
				{
					voices.getUnchecked(i)->clearCurrentNote();
					numVoicesPlaying = getNumVoicesPlaying(sounds.getUnchecked(0));
					listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_NUM_VOICES_PLAYING, &numVoicesPlaying);
				}
			}
		}
        if (useEvent)
		{
			handleMidiEvent (m);
		}

        startSample += numThisTime;
        numSamples -= numThisTime;
    }
}

void JaySynth::updateMidiTiming(const MidiMessage& m)
{
	// Quarter clock generation
	int nom;
	int denom;
	m.getTimeSignatureInfo(nom, denom);

	if (denom > 0)
	{
		m_numMidiClocksPerBeat = m_numMidiclocksPerFourQuarterBeats/denom;
		m_numMidiClocksPerBar = nom*m_numMidiClocksPerBeat;
	}
}

void JaySynth::handleMidiEvent (const MidiMessage& m)
{
	updateMidiTiming(m);
	
    if (m.isNoteOn())
    {
        noteOn (m.getChannel(),
                m.getNoteNumber(),
                m.getFloatVelocity());
    }
	if (m.isNoteOff())
    {
        noteOff (m.getChannel(),
                 m.getNoteNumber(),
                 true);
    }
    if (m.isAllNotesOff() || m.isAllSoundOff())
    {
        allNotesOff (m.getChannel(), true);

    }
    if (m.isPitchWheel())
    {
        const int channel = m.getChannel();
        const int wheelPos = m.getPitchWheelValue();
        lastPitchWheelValues [channel - 1] = wheelPos;

        handlePitchWheel (channel, wheelPos);
    }
    if (m.isController())
    {
		midiCC_info_t midiCC_info;
		midiCC_info.channel = m.getChannel();
		midiCC_info.ID = m.getControllerNumber();
		midiCC_info.type = MIDI_CONTROLLER_TYPE_CC;
		midiCC_info.value_raw = m.getControllerValue();
		midiCC_info.value = (synth_float_t)midiCC_info.value_raw/127;

		SynthDebug("CC: controllerNumber=%d, controllerValue=%d", midiCC_info.ID, midiCC_info.value_raw);

		bool isNrpnProcessing = mMidiNrpn.process(midiCC_info.ID, midiCC_info.value_raw);
		if (mMidiNrpn.isValid())
		{
			midiCC_info.ID = mMidiNrpn.getId();
			midiCC_info.type = MIDI_CONTROLLER_TYPE_NRPN;
			midiCC_info.value_raw = mMidiNrpn.getValue();
			midiCC_info.value = (synth_float_t)midiCC_info.value_raw/mMidiNrpn.getMaxValue();
			SynthDebug("NRPN: controllerNumber=%d, controllerValue=%d", midiCC_info.ID, midiCC_info.value_raw);
		}

		if (!isNrpnProcessing)
		{
			handleController (midiCC_info);
		}

    }
	if (m.isMidiMachineControlMessage())
	{
		SynthDebug("MidiMachineControlMessage(%08X)\n", m.getMidiMachineControlCommand());
	}
	if (m.isTempoMetaEvent())
	{
		SynthDebug("TempoMetaEvent\n");
	}
	if (m.isTimeSignatureMetaEvent())
	{
		int nom;
		int denom;
		
		m.getTimeSignatureInfo(nom, denom);

		SynthDebug("isTimeSignatureMetaEvent\n");
		SynthDebug("Time signature %d/%d\n", nom, denom);
		
	}
	if (m.isMidiStart())
	{
		SynthDebug("MidiStart\n");
		m_isMidiClockStarted = true;
	}
	if (m.isMidiContinue())
	{
		SynthDebug("MidiContinue\n");
		m_isMidiClockStarted = true;
	}
	if (m.isMidiStop())
	{
		SynthDebug("MidiStop\n");
		m_isMidiClockStarted = false;
	}
	if (m.isSongPositionPointer())
	{
		SynthDebug("SongPositionPointer\n");
		double bar_16 = 1 + ((double)m.getSongPositionPointerMidiBeat()/4);
		double bar_4 = (bar_16 - (int)bar_16);
		m_clock_cnt_bar = (int)(bar_4*4);
		SynthDebug("getSongPositionPointerMidiBeat(): %f -> %u clocks", bar_4, m_clock_cnt_bar);
	}
	if (m.isQuarterFrame())
	{
		SynthDebug("QuarterFrame\n");
	}
	if (m.isFullFrame())
	{
		SynthDebug("FullFrame\n");
	}
	if (m.isTempoMetaEvent())
	{
		SynthDebug("TempoMetaEvent = %f\n", m.getTempoSecondsPerQuarterNote());
		SynthDebug("TimeStamp = %f\n", m.getTimeStamp());
		
	}
	if (m.isMidiClock())
	{
		// Quarter clock generation
		m_clock_cnt_bar = m_clock_cnt_bar % m_numMidiClocksPerBar;
		if (m_isMidiClockStarted)
		{
#if WITH_DEV_SYNCED_LFO
			// Sync LFO and BPM Estimation
			synth_float_t bpm = voiceSetMidiBeat(m_clock_cnt_bar);
#endif
			if (m_clock_cnt_bar % m_numMidiClocksPerBeat == 0)
			{
				// Send quarter pulse with current BPM estimation
				midi_quarter_clock_info_t info;

				info.bpm = (uint32_t)(bpm + 0.5);
				info.type = 1;
				
				if (m_clock_cnt_bar == 0)
				{
					info.type = 2;
					SynthDebug("1111111111111111111111111");
				}
				else
				{
					SynthDebug("-------------------------");
				}

				listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_MIDICLOCK, &info);
			}
		}
		m_clock_cnt_bar++;
	}
}

synth_float_t JaySynth::voiceSetMidiBeat(int barBeatCount) 
{
	synth_float_t bpm = 0;
	for (int i=0; i < VOICE_NUM_LFO; i++)
	{
		lfo_t *pLfo = &m_pVoices[0].pCom->glfo[i];
		synth_float_t phase = Sync_mod((synth_float_t)barBeatCount/m_numMidiClocksPerBar);
		bpm += LFO_sync(pLfo, phase)/VOICE_NUM_LFO;
	}
	
	for (int i=0; i < max_num_voices; i++)
	{
		JaySynthVoice* const voice = voices.getUnchecked (i);
		voice->setMidiBeat(barBeatCount);
	}
	
	return bpm;
}

//==============================================================================
// Voice trigger stuff
//==============================================================================
void JaySynth::Voicestart (JaySynthVoice* const voice,
                              SynthesiserSound* const sound,
                              const int midiChannel,
                              const int midiNoteNumber,
                              const float velocity, bool doTrigger)
{
	int numVoicesPlaying;

    if (voice != nullptr && sound != nullptr)
    {
//        if (voice->getCurrentlyPlayingSound() != nullptr)
//			if (!isMonophon)
//				voice->stopNote (false);

		if (pMidCC_editBuffer)
		{
			for (int paramID=0; paramID < SYNTH_NUM_PARAMS; paramID++)
			{
				if (pMidCC_editBuffer->isAssignedVelKeyControl(paramID))
					setPerVoiceControl(voice->m_pVoice->id, SYNTH_PER_VOICE_CONTROL_VELKEY, paramID, pMidCC_editBuffer->getVelKeyControl(paramID, velocity, midiNoteNumber));
			}
		}

		pCurrNoteInfos[voice->m_pVoice->id].note = midiNoteNumber;
		pCurrNoteInfos[voice->m_pVoice->id].velocity = velocity;

		VoiceParam2Set(voice->m_pVoice, VOICE_PARAM_KEY, midiNoteNumber);

//		if (!m_isMonophon || (m_isMonophon && doTrigger && (m_num_keys_pressed_last == 0)) || (m_isMonophon && doTrigger && m_doMonophonRetrigger && (m_highest_key_last < midiNoteNumber)))
		if (!m_isMonophon || (m_isMonophon && doTrigger && (m_num_keys_pressed_last == 0)) || (m_isMonophon && m_doMonophonRetrigger && (m_latest_key != m_latest_key_last)))
		{
			voice->startNote (midiNoteNumber, velocity, sound,
                          lastPitchWheelValues [midiChannel - 1]);
		}

        voice->currentlyPlayingNote = midiNoteNumber;
//        voice->noteOnTime = ++lastNoteOnCounter;
        voice->currentlyPlayingSound = sound;
        voice->keyIsDown = true;
        voice->sostenutoPedalDown = false;
		numVoicesPlaying = getNumVoicesPlaying(sounds.getUnchecked(0));
		listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_NUM_VOICES_PLAYING, &numVoicesPlaying);

    }
}

void JaySynth::Voicestop (JaySynthVoice* voice, const bool allowTailOff)
{
    jassert (voice != nullptr);

    voice->stopNote (allowTailOff);

    // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()!
    jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0));
}

void JaySynth::allNotesOff (const int midiChannel, const bool allowTailOff)
{
	int i;
	int numVoicesPlaying;
    const ScopedLock sl (lock);

	clearKeysPressed();

    for (i=0; i < max_num_voices; i++)
    {
        JaySynthVoice* const voice = voices.getUnchecked (i);

        if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
            voice->stopNote (allowTailOff);
    }
    sustainPedalsDown.clear();
	next_possible_voice = 0;
	oldest_voice = 0;
}

void JaySynth::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity)
{
	int i, j, latest_note;
 	float latest_vel;
    const ScopedLock sl (lock);

	last_midi_note_info.note = midiNoteNumber;
	last_midi_note_info.velocity = velocity;
    listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_NOTE_PRESSED, &last_midi_note_info);

	setKeyPressed(midiNoteNumber, velocity);
	if (m_isMonophon)
	{
		latest_note = m_latest_key;
		latest_vel = m_monoMGR.getVel(m_latest_key);
		for (i=0; i < sounds.size(); i++)
		{
			SynthesiserSound* const sound = sounds.getUnchecked(i);
			if (m_isUnison)
			{
				for (j=0; j < unison_num_voices; j++)
				{
					Voicestart (voices.getUnchecked (j), sound, midiChannel, latest_note, latest_vel, true);
				}
			}
			else
			{
				Voicestart (voices.getUnchecked (0), sound, midiChannel, latest_note, latest_vel, true);
			}
		}
	}
	else
	{
		for (int i = sounds.size(); --i >= 0;)
		{
			SynthesiserSound* const sound = sounds.getUnchecked(i);

			if (sound->appliesToNote (midiNoteNumber)
				 && sound->appliesToChannel (midiChannel))
			{
				// If hitting a note that's still ringing, stop it first (it could be
				// still playing because of the sustain or sostenuto pedal).
//				for (j=0; j < max_num_voices; j++)
//				{
//					JaySynthVoice* const voice = voices.getUnchecked (j);
//
//					if (!m_isMonophon)
//					{
//						if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel))
//							Voicestop (voice, true);
//					}
//				}
				if (m_isUnison)
				{
					for (j=0; j < unison_num_voices; j++)
					{
						Voicestart (getFreeVoice (sound, isNoteStealingEnabled()), sound, midiChannel, midiNoteNumber, velocity, true);
					}
				}
				else
				{
					Voicestart (getFreeVoice (sound, isNoteStealingEnabled()), sound, midiChannel, midiNoteNumber, velocity, true);
				}
			}
		}
	}
}

void JaySynth::noteOff (const int midiChannel,
                           const int midiNoteNumber,
                           const bool allowTailOff)
{
    int i, j, latest_note;
	float latest_vel;
	const ScopedLock sl (lock);

	midi_note_info_t midi_note_info;
	midi_note_info.note = midiNoteNumber;
	midi_note_info.velocity = 0;
    listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_NOTE_RELEASED, &midi_note_info);

	setKeyPressed(midiNoteNumber, 0);
	if (m_isMonophon)
 	{
		latest_note = m_latest_key;
		latest_vel = m_monoMGR.getVel(m_latest_key);
		if (latest_note >= 0)
		{
			for (i = sounds.size(); --i >= 0;)
			{
				SynthesiserSound* const sound = sounds.getUnchecked(i);
				if (m_isUnison)
				{
					for (j=0; j < unison_num_voices; j++)
					{
						Voicestart (voices.getUnchecked (j), sound, midiChannel, latest_note, latest_vel, false);
					}
				}
				else
				{
					Voicestart (voices.getUnchecked (0), sound, midiChannel, latest_note, latest_vel, false);
				}
			}
		}
		else
		{
			for (i=0; i < max_num_voices; i++)
			{
				JaySynthVoice* const voice = voices.getUnchecked (i);
				voice->keyIsDown = false;
				if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown))
				{
					Voicestop (voice, allowTailOff);
				}
			}
		}
	}
	else
	{

		for (i=0; i < max_num_voices; i++)
		{
			JaySynthVoice* const voice = voices.getUnchecked (i);

			if (voice->getCurrentlyPlayingNote() == midiNoteNumber)
			{
				SynthesiserSound* const sound = voice->getCurrentlyPlayingSound();

				if (sound != nullptr
					 && sound->appliesToNote (midiNoteNumber)
					 && sound->appliesToChannel (midiChannel))
				{
					voice->keyIsDown = false;

					if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown))
						Voicestop (voice, allowTailOff);
				}
			}
		}
	}
}

//==============================================================================
// Voice management stuff
//==============================================================================
void JaySynth::unisonoModeChanged(void)
{
	int key;
	float vel;
//	if ((m_doMonophon ^ m_isMonophon) || (m_doUnison ^ m_isUnison))
	{
		m_isMonophon = m_doMonophon;
		m_isUnison = m_doUnison;
		allNotesOff(0, false);
		if (m_isMonophon)
		{
			key =  m_latest_key;
			vel =  m_monoMGR.getVel(m_latest_key);
		}
		if (m_isUnison)
		{
			m_unisonGain = 5.0/(4 + unison_num_voices);
		}
		else
		{
			m_unisonGain = 1;
		}
	}
}

void JaySynth::advanceNextPossibleVoice(void)
{
	next_possible_voice++;
	if (next_possible_voice >= total_num_voices)
		next_possible_voice = 0;
}

int JaySynth::getNextPossibleVoice(void)
{
	return next_possible_voice;
}

void JaySynth::advanceOldestVoice(void)
{
	oldest_voice++;
	if (oldest_voice >= total_num_voices)
		oldest_voice = 0;
}

int JaySynth::getOldestVoice(void)
{
	return oldest_voice;
}

int JaySynth::getNumVoicesPlaying (SynthesiserSound* soundToPlay)
{
	int i, numVoicesFound;
    const ScopedLock sl (lock);

	numVoicesFound = 0;
    for (i = 0; i < max_num_voices; i++)
	{
        if (voices.getUnchecked (i)->getCurrentlyPlayingNote() >= 0
             && voices.getUnchecked (i)->canPlaySound (soundToPlay))
		{
			numVoicesFound++;
		}
	}
	return numVoicesFound;
}

JaySynthVoice* JaySynth::getFreeVoice (SynthesiserSound* soundToPlay,
                                              const bool stealIfNoneAvailable)
{
	int i, freeVoiceFound;
    const ScopedLock sl (lock);

	freeVoiceFound = -1;
    for (i = 0; i < total_num_voices; i++)
	{
        if (voices.getUnchecked (getNextPossibleVoice())->getCurrentlyPlayingNote() < 0
             && voices.getUnchecked (getNextPossibleVoice())->canPlaySound (soundToPlay))
		{
			freeVoiceFound = getNextPossibleVoice();
			advanceNextPossibleVoice();
			break;
		}
		advanceNextPossibleVoice();
	}

	if (freeVoiceFound >= 0)
	{
	    return voices.getUnchecked (freeVoiceFound);
	}

    if (stealIfNoneAvailable)
    {
        // currently this just steals the one that's been playing the longest, but could be made a bit smarter..
        JaySynthVoice* oldest =  voices.getUnchecked (getOldestVoice());

        for (i = 0; i < total_num_voices; i++)
        {
			advanceOldestVoice();
			if (voices.getUnchecked (getOldestVoice())->getCurrentlyPlayingNote() >= 0)
				break;

        }

        jassert (oldest != nullptr);
        return oldest;
    }

    return nullptr;
}

void JaySynth::clearKeysPressed(void)
{
	m_monoMGR.init();
	m_num_keys_pressed = -1;
	m_latest_key = -1;
	m_highest_key = -1;
	m_num_keys_pressed_last = -1;
	m_latest_key_last = -1;
	m_highest_key_last = -1;
}

void JaySynth::setKeyPressed(int key, float vel)
{
	m_num_keys_pressed_last = m_monoMGR.getNumNotes();
	m_latest_key_last = m_monoMGR.getLatestNote();
	m_highest_key_last = m_monoMGR.getHighestNote();
	m_monoMGR.add(key, vel);
	m_num_keys_pressed = m_monoMGR.getNumNotes();
	m_latest_key = m_monoMGR.getLatestNote();
	m_highest_key = m_monoMGR.getHighestNote();
}

void JaySynthThread::run(void)
{
	float *buf1, *buf2;

	SynthDebug("Thread entry\n");
	while(!threadShouldExit())
	{
		if (wait(1000))
		{

			m_buffer->clear (0, 0, m_sample_len);
			m_buffer->clear (1, 0, m_sample_len);

			buf1 = m_buffer->getWritePointer (0, 0);
			buf2 = m_buffer->getWritePointer (1, 0);

			for (int i = m_voice_offset; i < m_num_voices; i += m_voice_step)
			{
				if (m_pVoices[i].state == note_state_idle)
					continue;

				VoiceProcessDataV(&m_pVoices[i], buf1, buf2, m_sample_len);

			}
			m_pEventRdy->signal();
		}
	}
	SynthDebug("Thread exit\n");
}

JaySynthThread::JaySynthThread(WaitableEvent *pEventRdy, voice_t *pVoices, int num_voices, int offset, int step)
: Thread(L"JaySynthAudioThread_" + String(offset))
{
	m_pEventRdy = pEventRdy;
	m_num_voices = num_voices;
	m_pVoices = pVoices;
	m_voice_offset = offset;
	m_voice_step = step;
	m_buffer = new AudioSampleBuffer(2, SYNTH_MAX_BUFSIZE);
	m_sample_len = SYNTH_MAX_BUFSIZE;
}

void JaySynthThread::go(int len)
{
	if (len <= SYNTH_MAX_BUFSIZE)
	{
		m_sample_len = len;
		notify();
	}
}

AudioSampleBuffer& JaySynthThread::getBuffer(void)
{
	return *m_buffer;
}

JaySynthThread::~JaySynthThread()
{
//	delete (m_buffer);
}
//==============================================================================
