/*
 * so-666.c
 *
 * Copyright (C) 2011 - Jeremy Salwen
 * Copyright (C) 2010 - 50m30n3
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "so-666.h"

static float dist( float in )
{
	float out;
	out = tanhf( in );
	return out;
}

inline float feedback_scale(float input) {
	return 0.01+powf( input, 4.0)*0.9;
}
inline float cutoff_scale(float input) {
	return powf( input, 5.0 );
}
void runSO_666( LV2_Handle arg, uint32_t nframes ) {
	
	so_666* so=(so_666*)arg;
	lv2_event_begin(&so->in_iterator,so->MidiIn);
	float **strings=so->strings;
	unsigned int* stringpos=so->stringpos;
	unsigned int* stringlength=so->stringlength;
	float* outbuffer=so->output;
	int * status=so->status;

	int i, note;
	float  sample, damp;

	if(*so->controlmode_p>0) {
		so->ffeedback=feedback_scale(*so->feedback_p);
		so->fcutoff=cutoff_scale(*so->cutoff_p);
		so->freso=*so->resonance_p;
		so->volume=*so->volume_p;
	}
	
	for( i=0; i<nframes; i++ ) {
		while(lv2_event_is_valid(&so->in_iterator)) {
			uint8_t* data;
			LV2_Event* event= lv2_event_get(&so->in_iterator,&data);
			if (event->type == 0) {
				so->event_ref->lv2_event_unref(so->event_ref->callback_data, event);
			} else if(event->type==so->midi_event_id) {
				if(event->frames > i) {
					break;
				} else {
					const uint8_t* evt=(uint8_t*)data;
					if((evt[0]&MIDI_CHANNELMASK)==(int) (*so->channel_p)) {
						if((evt[0]&MIDI_COMMANDMASK)==MIDI_NOTEON) 	{
							note = evt[1];
							if( (note >= BASENOTE) && ( note < BASENOTE+NUMNOTES ) ) {
								note -= BASENOTE;
								status[note] = 1;
							}
						}
						else if((evt[0]&MIDI_COMMANDMASK)==MIDI_NOTEOFF )	{
							note = evt[1];
							if( ( note >= BASENOTE ) && ( note < BASENOTE+NUMNOTES ) ) {
								note -= BASENOTE;
								status[note] = 0;
							}
						}
						else if((*so->controlmode_p<=0) && (evt[0]&MIDI_COMMANDMASK)==MIDI_CONTROL)	{
							if( evt[1] == 74 )	{
								unsigned int cutoff =evt[2];
								so->fcutoff = cutoff_scale((cutoff+50.0)/200.0);
							}
							else if( evt[1]== 71 )	{
								unsigned int resonance = evt[2];
								so->freso = resonance/127.0;
							}
							else if( evt[1] == 7 )	{
								so->volume = evt[2];
							}
							else if( evt[1]== 1 ) {
								unsigned int feedback =evt[2];
								so->ffeedback = feedback_scale (feedback/127.0);
							}
						}
					}
				}
			}
			lv2_event_increment(&so->in_iterator);
		}
		sample = (((float)rand()/(float)RAND_MAX)*2.0-1.0)*0.001;

		for( note=0; note<NUMNOTES; note++ ) {
			damp = so->stringcutoff[note];

			if( stringpos[note] > 0 ) {
				strings[note][stringpos[note]] = strings[note][stringpos[note]]*damp +
					strings[note][stringpos[note]-1]*(1.0-damp);
			} else {
				strings[note][stringpos[note]] = strings[note][stringpos[note]]*damp +
					strings[note][stringlength[note]-1]*(1.0-damp);
			}
			strings[note][stringpos[note]] = dist( strings[note][stringpos[note]] ) * 0.99;

			sample += strings[note][stringpos[note]];
		}

		so->hpval += (sample-so->hplast) * 0.0001;
		so->hplast += so->hpval;
		so->hpval *= 0.96;
		sample -= so->hplast;

		so->lpval += (sample-so->lplast) * so->fcutoff * (1.0-tanh(so->lplast)*tanh(so->lplast)*0.9);
		so->lplast +=so-> lpval;
		so->lpval *= so->freso;
		sample = so->lplast;

		for( note=0; note<NUMNOTES; note++ ) {
			if( status[note] > 0 ) {
				strings[note][stringpos[note]] += sample*so->ffeedback;
			}

			if( fabs( strings[note][stringpos[note]] ) <= 0.0001 ) {
				strings[note][stringpos[note]] = 0.0;
			}
			stringpos[note]++;
			if( stringpos[note] >= stringlength[note] ) {
				stringpos[note] = 0;
			}
		}

		outbuffer[i] = dist( sample ) * (so->volume/127.0);
	}
}

LV2_Handle instantiateSO_666(const LV2_Descriptor *descriptor,double s_rate, const char *path,const LV2_Feature * const* features) {
	so_666* so=malloc(sizeof(so_666));
	LV2_URI_Map_Feature *map_feature;
	const LV2_Feature * const *  ft;
	for (ft = features; *ft; ft++) {
		if (!strcmp((*ft)->URI, "http://lv2plug.in/ns/ext/uri-map")) {
			map_feature = (*ft)->data;
			so->midi_event_id = map_feature->uri_to_id(
			                                           map_feature->callback_data,
			                                           "http://lv2plug.in/ns/ext/event",
			                                           "http://lv2plug.in/ns/ext/midi#MidiEvent");
		                                           } else if (!strcmp((*ft)->URI, "http://lv2plug.in/ns/ext/event")) {
		                                                              so->event_ref = (*ft)->data;
												                                                                 }
	}

	puts( "SO-666 v.1.0 by 50m30n3 2009" );

	unsigned int feedback,cutoff,resonance;
	feedback = 32;
	cutoff = 64;
	resonance = 64;
	so->volume = 100;
	so->fcutoff = powf( (cutoff+50.0)/200.0, 5.0 );
	so->freso = resonance/127.0;
	so->ffeedback = 0.01+powf( feedback/127.0, 4.0)*0.9;
	
	so->lplast=0;
	so->lpval=0;
	so->hplast=0;
	so->hpval=0;
	
	int note;
	for( note=0; note<NUMNOTES; note++ ) {
		float freq = 440.0*powf( 2.0, (note+BASENOTE-69) / 12.0 );
		//so->stringcutoff[note] = ( freq * 16.0 ) / (float)s_rate;
		so->stringcutoff[note] = 0.9;
		int length = (float)s_rate /freq;
		so->stringlength[note] = length;
		so->strings[note] = malloc( length * sizeof( float ) );
		if( so->strings[note] == NULL )	{
			fputs( "Error allocating memory\n", stderr );
			return 0;
		}
		unsigned int i;
		for( i=0; i<(length); i++ )
		{
			so->strings[note][i] = 0.0;
		}
		so->stringpos[note] = 0;
		so->status[note] = 0;
	}

	return so;
}
void cleanupSO_666(LV2_Handle instance) {
	so_666* so=(so_666*)instance;
	int note;
	for(note=0; note<NUMNOTES; note++ )
	{
		free( so->strings[note] );
	}
	free(so);
}

void connectPortSO_666(LV2_Handle instance, uint32_t port, void *data_location) {
	so_666* so=(so_666*) instance;
	switch(port) {
		case PORT_OUTPUT:
			so->output=data_location;
			break;
		case PORT_MIDI:
			so->MidiIn=data_location;
			break;
		case PORT_CONTROLMODE:
			so->controlmode_p=data_location;
			break;
		case PORT_FEEDBACK:
			so->feedback_p=data_location;
			break;
		case PORT_RESONANCE:
			so->resonance_p=data_location;
			break;
		case PORT_CUTOFF:
			so->cutoff_p=data_location;
			break;
		case PORT_VOLUME:
			so->volume_p=data_location;
			break;
		case PORT_CHANNEL:
			so->channel_p=data_location;
			break;
		default:
			fputs("Warning, unconnected port!\n",stderr);
	}
}
