/*
 * ZamSFZ polyphonic SFZ player
 * Copyright (C) 2014  Damien Zammit <damien@zamaudio.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */

#include "ZamSFZPlugin.hpp"

START_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------

ZamSFZPlugin::ZamSFZPlugin()
    : Plugin(paramCount, 1, 1) // 1 program, 1 state
{
    // set default values
    loadProgram(0);
}

// -----------------------------------------------------------------------
// Init

void ZamSFZPlugin::initParameter(uint32_t index, Parameter& parameter)
{
    switch (index)
    {
    case paramLoading:
        parameter.hints      = kParameterIsOutput | kParameterIsBoolean;
        parameter.name       = "Loading";
        parameter.symbol     = "load";
        parameter.unit       = "";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = 0.0f;
        parameter.ranges.max = 1.0f;
        break;
    case paramGain:
        parameter.hints      = kParameterIsAutomatable;
        parameter.name       = "Gain";
        parameter.symbol     = "gain";
        parameter.unit       = "dB";
        parameter.ranges.def = 0.0f;
        parameter.ranges.min = -30.0f;
        parameter.ranges.max = 30.0f;
        break;
    }
}

void ZamSFZPlugin::initProgramName(uint32_t index, String& programName)
{
    if (index != 0)
        return;

    programName = "Default";
}

// -----------------------------------------------------------------------
// Internal data

float ZamSFZPlugin::getParameterValue(uint32_t index) const
{
    switch (index)
    {
    case paramLoading:
        return (float)loading;
        break;
    case paramGain:
        return gain;
        break;
    default:
        return 0.0f;
    }
}

void ZamSFZPlugin::setParameterValue(uint32_t index, float value)
{
    switch (index)
    {
    case paramLoading:
        loading = value > 0.5;
        break;
    case paramGain:
        gain = value;
        break;
    }
}

void ZamSFZPlugin::setState(const char* key, const char* value)
{
	if (strcmp(key, "filepath") == 0) {
		int curr_srate = (int)getSampleRate();
		char *tmp;
		char file[1024] = {0};
		snprintf(file, 1024, "%s", value);
		tmp = strrchr(file, '/');
		filename.assign(tmp, 1, strlen(tmp) - 1);
		path.assign(value, 0, strlen(value) - strlen(tmp));
		loading = true;
		printf("Path: %s\nFile: %s\n", path.c_str(), filename.c_str());
		sfz.clearsamples();
		sfz.loadsamples(path, filename, curr_srate);
		sfz.pitchshiftsamples(curr_srate);
		loading = false;
	}
}

String ZamSFZPlugin::getState(const char*) const
{
	char fullpath[1024];
	if ((path.size() == 0) || (filename.size() == 0)) {
		return String("");
	} else {
		snprintf(fullpath, 1024, "%s/%s", path.c_str(), filename.c_str());
		return String(fullpath);
	}
}

void ZamSFZPlugin::initState(unsigned int key, String& val, String& def)
{
	char fullpath[1024];
	if (key == 0) {
		val = String("filepath");
		if ((path.size() == 0) || (filename.size() == 0)) {
			def = String("");
		} else {
			snprintf(fullpath, 1024, "%s/%s", path.c_str(), filename.c_str());
			def = String(fullpath);
		}
	}
}

void ZamSFZPlugin::loadProgram(uint32_t index)
{
    if (index != 0)
        return;

    /* Default parameter values */
    gain = 0.0f;
    speed = 10.0f;

    /* Default variable values */
    for (int i = 0; i < MAX_VOICES; i++) {
        voice[i].playing = false;
	voice[i].notenum = -1;
	voice[i].envpos = 0;
	voice[i].slowcount = 0;
	voice[i].curamp = 0.f;
	voice[i].vi = 0.f;
        voice[i].rampstate = 0;
        voice[i].maxsamples = MAX_SAMPLES;
    }

    curvoice = voice; //ptr to first voice

    for (int i = 0; i < MAX_ENV/2; i++) {
        env_y[i] = 1.f;
    }
    for (int i = MAX_ENV/2; i < MAX_ENV; i++) {
        env_y[i] = (MAX_ENV-i)/((float)(MAX_ENV/2.));
    }
    env_y[0] = 0.f;
    env_y[MAX_ENV-1] = 0.f;
    /* reset filter values */
    activate();
}

// -----------------------------------------------------------------------
// Process

void ZamSFZPlugin::activate()
{
}


void ZamSFZPlugin::run(const float**, float** outputs, uint32_t frames,
				const MidiEvent* midievent, uint32_t midicount)
{
	float srate = getSampleRate();
	int slowfactor = (int) srate / (speed * 2400); // 1-20 ~ 20-1
	uint32_t i;

  if (loading == true) {
	for (i = 0; i < frames; i++) {
		outputs[0][i] = 0.f;
		outputs[1][i] = 0.f;
	}
  } else {
	for (i = 0; i < midicount; i++) {
		int type = midievent[i].data[0] & 0xF0;
		int chan = midievent[i].data[0] & 0x0F;
		int num = midievent[i].data[1];
		int vel = midievent[i].data[2];
		if (type == 0x90 && chan == 0x0) {
			// NOTE ON
			int newvoice = -1;
			//printf("ON: Note %d\n", num);
			//printf("ON: begin attack\n");
			for (int k = 0; k < 128; k++)
				if (!voice[k].playing)
					newvoice = k;
			if (newvoice == -1)
				newvoice = 0; //steal #0
			curvoice = &voice[newvoice];
			curvoice->envpos = 1; // begin attack
			curvoice->playing = true;
			curvoice->notenum = num;
			curvoice->vi = vel / 127.f;
			curvoice->curamp = curvoice->vi;
			curvoice->rampstate = 0;
		}
		else if (type == 0x80 && chan == 0x0) {
			// NOTE OFF
			//printf("OFF: Note\n");
			//find voice with current notenum
			nvoices = 0;
			for (int k = 0; k < 128; k++) {
				if (voice[k].playing && voice[k].notenum == num) {
					voice[k].envpos = MAX_ENV / 2 + 1; // begin release;
				}
				if (!voice[k].playing && voice[k].notenum == num) {
					voice[k].notenum = -1;
				}
			}
		}
	}

	bool signal;
	float wavel;
	float waver;
	float outl;
	float outr;
	for (i = 0; i < frames; i++) {
		signal = false;
		outl = outr = 0.f;
		int k;
		Voice* j;
		// process envelope positions per sample
		for (k = 0; k < 128; k++) {
			j = &voice[k];
			if (j->playing) {
				if (j->envpos <= 0) {
					//silence
					j->playing = false;
					j->notenum = -1;
					j->envpos = 0;
					j->slowcount = 0;
					j->curamp = 0.f;
					j->vi = 0.f;
					j->rampstate = 0;
				} else if (j->envpos > 0 && (int) j->envpos < MAX_ENV / 2) {
					//attack
					j->curamp = j->vi * env_y[j->envpos];
					//printf("att: %d envpos=%d curamp=%.2f\n",k,j->envpos, j->curamp);
					j->slowcount++;
					j->envpos += ((j->slowcount % slowfactor) == 0) ? 1 : 0;
				} else if (j->envpos > MAX_ENV / 2) {
					//release
					j->curamp = j->vi * env_y[j->envpos];
					//printf("rel: %d envpos=%d curamp=%.2f\n",k,j->envpos, j->curamp);
					j->slowcount++;
					j->envpos += ((j->slowcount % slowfactor) == 0) ? 1 : 0;
					if (j->envpos == MAX_ENV) {
						//end of release
						j->playing = false;
						j->notenum = -1;
						j->envpos = 0;
						j->slowcount = 0;
						j->curamp = 0.f;
						j->vi = 0.f;
						j->rampstate = 0;
						//printf("killed, n=%d\n",k);
					}
				} else if (j->envpos >= j->maxsamples || j->rampstate >= j->maxsamples) {
					j->playing = false;
					j->notenum = -1;
					j->envpos = 0;
					j->slowcount = 0;
					j->curamp = 0.f;
					j->vi = 0.f;
					j->rampstate = 0;
				} else {
					//sustain
					j->curamp = j->vi * env_y[MAX_ENV/2];
					//printf("sustain...\n");
				}
			}
		}
		for (k = 0; k < 128; k++) {
			if (voice[k].curamp < 0.f && voice[k].playing)
				printf("WTF NEG\n");
			if (!voice[k].playing) {
				voice[k].rampstate = 0;
				continue;
			}
			signal = true;
			voice[k].rampstate++;
			if (voice[k].rampstate >= voice[k].maxsamples) {
				wavel = waver = 0.f;
				voice[k].rampstate = 0;
				voice[k].playing = false;
				voice[k].notenum = -1;
				voice[k].envpos = 0;
				voice[k].slowcount = 0;
				voice[k].curamp = 0.f;
				voice[k].vi = 0.f;
			} else {
				int n = voice[k].notenum;
				int v = voice[k].vi * 127.;
				int l = 0;
				for (int k = 0; k < sfz.layers[n].max; k++) {
					if (sfz.layers[n].l[k].lovel <= v && v <= sfz.layers[n].l[k].hivel) {
						l = k;
						break;
					}
				}
				int pos = voice[k].rampstate;
				wavel = sfz.sample[n][l][0][pos];
				waver = sfz.sample[n][l][1][pos];
			}
			outl += wavel*voice[k].curamp/2.;
			outr += waver*voice[k].curamp/2.;
		}
		if (signal) {
			//outl;
			//outr;
			outputs[0][i] = outl*from_dB(gain);
			outputs[1][i] = outr*from_dB(gain);
		} else {
			outputs[0][i] = 0.f;
			outputs[1][i] = 0.f;
		}
	}
  }
}

void ZamSFZPlugin::sampleRateChanged(double newSampleRate)
{
    // TODO reload file when sample rate changes
}

// -----------------------------------------------------------------------

Plugin* createPlugin()
{
    return new ZamSFZPlugin();
}

// -----------------------------------------------------------------------
END_NAMESPACE_DISTRHO
