/*  SpiralSynth
 *  Copyright (C) 2000 David Griffiths <dave@blueammonite.f9.co.uk>
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/ 

#include "Synth.h"

#include <FL/Fl.H>
#include <FL/Enumerations.H>
#include <fstream>
#include <strstream>
#include <string>
#include <time.h>
#include <stdlib.h>

#include "Oscillator.h"
#include "Output.h"
#include "Midi.h"

Synth::Synth():
m_Oct(5),
m_Databuf(NULL), 
m_Databuf2(NULL), 
m_Databuf3(NULL), 
m_OscControlbuf1(NULL), 
m_OscControlbuf2(NULL), 
m_OscControlbuf3(NULL), 
m_EnvControlbuf(NULL), 
m_LFOControlbuf(NULL), 
m_BothControlbuf(NULL),
m_Osc1GUI(&m_Osc1),
m_Osc2GUI(&m_Osc2),
m_Osc3GUI(&m_Osc3),
m_Env1GUI(&m_Env1),
m_Env2GUI(&m_Env2),
m_Env3GUI(&m_Env3),
m_FilterGUI(&m_Filter),
m_MixerGUI(&m_Mixer),
m_Mixer2GUI(&m_Mixer2),
m_DelayGUI(&m_Delay),
m_FilterEnvGUI(&m_FilterEnv),
m_LFOGUI(&m_LFO),
m_OutGUI(&m_Out)
{ 
	cerr<<"Allocating Buffer Memory"<<endl;
	
	AllocMem();
		
	m_VoiceNote =   new int[SynthInfo::POLY];
	m_VoiceOctave = new int[SynthInfo::POLY];
	for (int n=0; n<SynthInfo::POLY; n++)
	{
		m_VoiceNote[n] = -1;
		m_VoiceOctave[n] = -1;
	}
	
	if (!m_Databuf || !m_Databuf2 || !m_Databuf3 || !m_OscControlbuf1 
	    || !m_OscControlbuf2 || !m_OscControlbuf3 || !m_EnvControlbuf 
		|| !m_LFOControlbuf || !m_BothControlbuf ) 
	{
		cerr<<"Not enough memory availible"<<endl;
		exit(0);
	}

	m_KeyVoice = new char[SynthInfo::POLY];

	for (int n=0; n<SynthInfo::POLY; n++)
	{
		m_KeyVoice[n]=' ';
	}

	LoadPatch(1);
}

Synth::~Synth()
{ 
	CloseMidi();
	
	delete[] m_Databuf;
	delete[] m_Databuf2;
	delete[] m_Databuf3;
	delete[] m_OscControlbuf1;
	delete[] m_OscControlbuf2;
	delete[] m_OscControlbuf3;
	delete[] m_EnvControlbuf;
    delete[] m_LFOControlbuf;
	delete[] m_BothControlbuf; 
	delete[] m_XModbuf1; 
	delete[] m_XModbuf2; 
	delete[] m_VoiceNote; 
	delete[] m_VoiceOctave; 
	delete[] m_KeyVoice;
}

void Synth::AllocMem()
{
	m_Databuf = new short[SynthInfo::BUFSIZE];
	m_Databuf2 = new short[SynthInfo::BUFSIZE];
	m_Databuf3 = new short[SynthInfo::BUFSIZE];
	m_OscControlbuf1 = new short[SynthInfo::BUFSIZE];
	m_OscControlbuf2 = new short[SynthInfo::BUFSIZE];
	m_OscControlbuf3 = new short[SynthInfo::BUFSIZE];
	m_EnvControlbuf = new short[SynthInfo::BUFSIZE];
    m_LFOControlbuf = new short[SynthInfo::BUFSIZE];
	m_BothControlbuf = new short[SynthInfo::BUFSIZE];
	
	m_XModbuf1 = new short*[SynthInfo::POLY];
	m_XModbuf2 = new short*[SynthInfo::POLY];
	
	for (int n=0; n<SynthInfo::POLY; n++)
	{
		m_XModbuf1[n] = new short[SynthInfo::BUFSIZE];
		m_XModbuf2[n] = new short[SynthInfo::BUFSIZE];
	}
}

Fl_Window *Synth::CreateWindow()
{
 Fl_Window* w;
  { Fl_Window* o = Window = new Fl_Window(785, 395);
    w = o;
	o->color(SynthInfo::GUIBG_COLOUR);
 
	// put part guis here
	m_Osc1GUI.CreateGUI(5,5,"Osc1");
	m_Osc2GUI.CreateGUI(5,120,"Osc2");
	m_Osc3GUI.CreateGUI(5,235,"Osc3");
	m_Env1GUI.CreateGUI(255,5,"Envelope1");
	m_Env2GUI.CreateGUI(255,120,"Envelope2");
	m_Env3GUI.CreateGUI(255,235,"Envelope3");
	m_FilterGUI.CreateGUI(485,5,"Filter");
	m_MixerGUI.CreateGUI(380,5,"Mixer 1&2");
	m_Mixer2GUI.CreateGUI(380,120,"Mixer [1,2]&3");
	m_DelayGUI.CreateGUI(610,5,"Delay");
	m_FilterEnvGUI.CreateGUI(485,120,"Extra Envelope");
	m_LFOGUI.CreateGUI(610,120,"LFO");
	m_OutGUI.CreateGUI(695,5,"Output");
	m_PatchBank.CreateGUI(5,350,"Patch Bank");
	m_LFORouteGUI.CreateGUI(610,292,"LFO Route");
	m_EnvRouteGUI.CreateGUI(610,235,"Envelope Route");
		
	m_Scope.CreateGUI(380,235,"Scope");
  }  
    	
 return w;
}

void Synth::Trigger(int octave, int note, int vol=128)
{	
	// Move to the next voice
	m_CurrentVoice++;
	
	if (m_CurrentVoice>=SynthInfo::POLY)
	{
		m_CurrentVoice=0;
	}
		
	// if it's not free
	if (m_VoiceNote[m_CurrentVoice]!=-1)
	{
		// look for a free voice
		for (int n=0; n<SynthInfo::POLY; n++)
		{
			if (m_VoiceNote[n]==-1 && m_VoiceOctave[n]==-1)
			{
				m_CurrentVoice=n;
			} 
		}
	}
			
	m_VoiceNote[m_CurrentVoice]=note;
	m_VoiceOctave[m_CurrentVoice]=octave;
	
	m_Osc1.NoteTrigger(m_CurrentVoice,octave,note,vol); m_Env1.Trigger(m_CurrentVoice);
	m_Osc2.NoteTrigger(m_CurrentVoice,octave,note,vol); m_Env2.Trigger(m_CurrentVoice);
	m_Osc3.NoteTrigger(m_CurrentVoice,octave,note,vol); m_Env3.Trigger(m_CurrentVoice);
	m_FilterEnv.Trigger(m_CurrentVoice);
}

void Synth::UnTrigger(int octave=-1,int note=-1)
{
	// clear all voices
	if (octave==-1 && note==-1)
	{
		for (int n=0; n<SynthInfo::POLY; n++)
		{
			m_Env1.UnTrigger(n);
			m_Env2.UnTrigger(n);
			m_Env3.UnTrigger(n);
			m_FilterEnv.UnTrigger(n);
			m_VoiceNote[n]=-1;
			m_VoiceOctave[n]=-1;
		}
	}
	else
	{	
		// look for the voice and clear it
		for (int n=0; n<SynthInfo::POLY; n++)
		{
			if (m_VoiceOctave[n]==octave && m_VoiceNote[n]==note)
			{
				m_Env1.UnTrigger(n);
				m_Env2.UnTrigger(n);
				m_Env3.UnTrigger(n);
				m_FilterEnv.UnTrigger(n);
				m_VoiceNote[n]=-1;
				m_VoiceOctave[n]=-1;
			}
		}
	}

}

void Synth::DoIdle()
{
	if (Fl::event_key(FL_F+1)) m_Oct=0; 
	if (Fl::event_key(FL_F+2)) m_Oct=1; 
	if (Fl::event_key(FL_F+3)) m_Oct=2; 
	if (Fl::event_key(FL_F+4)) m_Oct=3; 
	if (Fl::event_key(FL_F+5)) m_Oct=4; 
	if (Fl::event_key(FL_F+6)) m_Oct=5; 
	if (Fl::event_key(FL_F+7)) m_Oct=6; 
	if (Fl::event_key(FL_F+8)) m_Oct=7; 
	if (Fl::event_key(FL_F+9)) m_Oct=8; 
	if (Fl::event_key(FL_F+10)) m_Oct=9; 
	if (Fl::event_key(FL_F+11)) m_Oct=10; 

	int note=0;
	int oct=0;
	char KeyChar=0;
	bool KeyPressed=false;

	for (int key=0; key<SynthInfo::NKEYS; key++)
	{
		KeyChar=SynthInfo::KEYMAP.c_str()[key];
		
		// check if a key's been pressed
		if (Fl::event_key(KeyChar)) 
		{
			KeyPressed=true;
			
			// check it was pressed last time
			bool Found=false;
			for (int n=0; n<SynthInfo::POLY; n++)
			{
				if (m_KeyVoice[n]==KeyChar)
				{
					Found=true;
				}				
			}
			
			if (!Found)
			{
				Trigger(m_Oct+oct,note); 
				m_KeyVoice[m_CurrentVoice]=KeyChar;
			}
		}
		else // it's not pressed down 
		{		
			//see if the note was pressed down last time			
			for (int n=0; n<SynthInfo::POLY; n++)
			{
				if (m_KeyVoice[n]==KeyChar)
				{
					UnTrigger(m_Oct+oct,note);
					m_KeyVoice[n]=' ';
				}				
			}
		}
		
		note++;
		if (note>11)
		{
			note=0;
			oct++;
		}
	}
	// reset if no keys have been pressed
	/*if (!KeyPressed)
	{
		if (LastKeyPressed!=0)
		{
			UnTrigger();
		}
		LastKeyPressed=0;
	}*/
	
	m_CurrentEvent=m_Midi.GetEvent();
	
	if (m_CurrentEvent.GetType()==MidiEvent::ON)
	{
		Trigger(m_CurrentEvent.GetOctave(),
		        m_CurrentEvent.GetNote(),
				m_CurrentEvent.GetVolume()); 		
	}
		
	if (m_CurrentEvent.GetType()==MidiEvent::OFF)
	{
		UnTrigger(m_CurrentEvent.GetOctave(),m_CurrentEvent.GetNote());
	}

	float f;
	m_Out.ClearBuffer();
	
	m_LFO.GetOutput(0,m_LFOControlbuf);
	
	// run the synth!
	for (int Voice=0; Voice<SynthInfo::POLY; Voice++)
	{
		// Xmod routing
		if (m_Mixer.GetType()==Mixer::XMOD)
		{
			m_Osc2.ModulateFreq(m_XModbuf1[Voice]);		
		}
			
		if (m_Mixer2.GetType()==Mixer::XMOD)
		{
			m_Osc3.ModulateFreq(m_XModbuf2[Voice]);		
		}		

		m_FilterEnv.GetOutput(Voice, m_EnvControlbuf);
	
		for (int n=0; n<SynthInfo::BUFSIZE; n++)
			m_BothControlbuf[n] = m_LFOControlbuf[n]+m_EnvControlbuf[n];
	
		m_Osc1.GetOutput(Voice, m_Databuf);
		m_Osc2.GetOutput(Voice, m_Databuf2);
		m_Osc3.GetOutput(Voice, m_Databuf3);
		m_Env1.GetOutput(Voice, m_OscControlbuf1);
		m_Env2.GetOutput(Voice, m_OscControlbuf2);
		m_Env3.GetOutput(Voice, m_OscControlbuf3);
		m_OscAmp1.GetOutput(m_OscControlbuf1,m_Databuf);
		m_OscAmp2.GetOutput(m_OscControlbuf2,m_Databuf2);
		m_OscAmp3.GetOutput(m_OscControlbuf3,m_Databuf3);
		
		// store the buffer here for feeding back into the oscillator
		if (m_Mixer.GetType()==Mixer::XMOD)
		{
			CopyBuffer(m_Databuf,m_XModbuf1[Voice]);	
		}
		
		m_Mixer.GetOutput(m_Databuf2,m_Databuf);
		
		// store the buffer here for feeding back into the oscillator
		if (m_Mixer2.GetType()==Mixer::XMOD)
		{
			CopyBuffer(m_Databuf2,m_XModbuf2[Voice]);	
		}	
		
		m_Mixer2.GetOutput(m_Databuf3,m_Databuf);
		m_Filter.GetOutput(Voice,m_Databuf);
		
		m_Out.Send(m_Databuf);
	}
		
	m_Delay.GetOutput(m_Out.GetBuffer());
	
	m_Scope.Display(m_Out.GetBuffer());
	
	m_Out.Play();
	
	m_CurrentPatch=m_PatchBank.GetOutput();	
	
	if (m_CurrentPatch>=0) 
	{
		m_Filter.Reset();
		if (m_PatchBank.IsSaving()) 
		{
			SavePatch(m_CurrentPatch);
		}
		else
		{
			LoadPatch(m_CurrentPatch);
			UpdateValues();
		}
	}
	
	if (m_CurrentPatch==-2) 
	{
		Randomise();
	}
	
	Route();
}

void Synth::SavePatch(int n)
{
	char buf[1024];
	ostrstream os(buf,1024,ios::out);
	os<<*this<<"| ";
	WritePatch(n,buf);	
}

void Synth::LoadPatch(int n)
{
	char buf[1024];
	if (ReadPatch(n,buf))
	{
		istrstream is(buf);
		is>>*this;
	}
}


int Synth::ReadPatch(int n, char *out)
{
	string Patchfn(SynthInfo::Get()->GetHomeDir()+"/.SpiralPatches.bank");
	ifstream is(Patchfn.c_str());
	
	if (!is.good()) // make a patch file
	{
		cerr<<"SpiralPatches.bank not found - writing new patch file."<<endl;
		
		ofstream os(Patchfn.c_str());
		for (int n=0; n<NUM_PATCHES; n++)
			os<<*this<<"| ";
		
		return 0;
	}
	
	char c=0;
	int count=0;
	
	while(is.good() && count!=n)
	{
		is.get(c);
		//cerr<<"c="<<c<<" count="<<count<<endl;
		if (c=='|') count++;
	}
	
	if(is.good()) 
	{
		is.get(out,1024);
	}
	
	return 1;
}

void Synth::WritePatch(int n, char *in)
{
	// todo: get rid of this cheesy implementation
	string Patchfn(SynthInfo::Get()->GetHomeDir()+"/.SpiralPatches.bank");
	string Tempfn(SynthInfo::Get()->GetHomeDir()+"/temp");
	ifstream is(Patchfn.c_str());
	ofstream os(Tempfn.c_str());
	
	char c=0;
	int count=0;
	int count2=0;
	bool IgnorePatch=false;
	
	while(is.good())
	{
		is.get(c);		
		
		while (count==n) // this is the patch to overwrite
		{
			c=in[count2++];
			
			if (c=='|') // end of patch
			{
				IgnorePatch=true;
				count++;
				os<<c<<" ";
				is.get(c);
			}
			else os<<c;
		}
		
		if (IgnorePatch) // step through until end
		{
		
			if (c=='|') 
			{
				IgnorePatch=false;
				count++;
			}
		}
		else // normal write mode
		{
			if (c=='|') 
			{
				count++;
			}
			
			os<<c;
		}
		
	}
	
	string command("mv -f "+Tempfn+" "+Patchfn);
	system(command.c_str());
}

void Synth::Route()
{
	m_Osc1.ModulateFreq(NULL);
	m_Osc1.ModulatePulseWidth(NULL);
	m_Osc2.ModulateFreq(NULL);
	m_Osc2.ModulatePulseWidth(NULL);
	m_Filter.ModulateCutoff(NULL);
	m_Filter.ModulateResonance(NULL);

	if (m_LFORouteGUI.Query(RouteGUI::OSC1FREQ))
		m_Osc1.ModulateFreq(m_LFOControlbuf);
		
	if (m_LFORouteGUI.Query(RouteGUI::OSC1PW))
		m_Osc1.ModulatePulseWidth(m_LFOControlbuf);
	
	if (m_LFORouteGUI.Query(RouteGUI::OSC2FREQ))
		m_Osc2.ModulateFreq(m_LFOControlbuf);
		
	if (m_LFORouteGUI.Query(RouteGUI::OSC2PW))
		m_Osc2.ModulatePulseWidth(m_LFOControlbuf);
	
	if (m_LFORouteGUI.Query(RouteGUI::FILTERC))
		m_Filter.ModulateCutoff(m_LFOControlbuf);
					
	if (m_LFORouteGUI.Query(RouteGUI::FILTERR))		
		m_Filter.ModulateResonance(m_LFOControlbuf);
		
		
	if (m_EnvRouteGUI.Query(RouteGUI::OSC1FREQ))
		m_Osc1.ModulateFreq(m_EnvControlbuf);
		
	if (m_EnvRouteGUI.Query(RouteGUI::OSC1PW))
		m_Osc1.ModulatePulseWidth(m_EnvControlbuf);
	
	if (m_EnvRouteGUI.Query(RouteGUI::OSC2FREQ))
		m_Osc2.ModulateFreq(m_EnvControlbuf);
		
	if (m_EnvRouteGUI.Query(RouteGUI::OSC2PW))
		m_Osc2.ModulatePulseWidth(m_EnvControlbuf);
	
	if (m_EnvRouteGUI.Query(RouteGUI::FILTERC))
		m_Filter.ModulateCutoff(m_EnvControlbuf);
					
	if (m_EnvRouteGUI.Query(RouteGUI::FILTERR))		
		m_Filter.ModulateResonance(m_EnvControlbuf);
		
		
	if (m_LFORouteGUI.Query(RouteGUI::OSC1FREQ) && 
	    m_EnvRouteGUI.Query(RouteGUI::OSC1FREQ))
		m_Osc1.ModulateFreq(m_BothControlbuf);
		
	if (m_LFORouteGUI.Query(RouteGUI::OSC1PW) && 
		m_EnvRouteGUI.Query(RouteGUI::OSC1PW))
		m_Osc1.ModulatePulseWidth(m_BothControlbuf);
	
	if (m_LFORouteGUI.Query(RouteGUI::OSC2FREQ) &&
		m_EnvRouteGUI.Query(RouteGUI::OSC2FREQ))
		m_Osc2.ModulateFreq(m_BothControlbuf);
		
	if (m_LFORouteGUI.Query(RouteGUI::OSC2PW) &&
		m_EnvRouteGUI.Query(RouteGUI::OSC2PW))
		m_Osc2.ModulatePulseWidth(m_BothControlbuf);
	
	if (m_LFORouteGUI.Query(RouteGUI::FILTERC) &&
		m_EnvRouteGUI.Query(RouteGUI::FILTERC))
		m_Filter.ModulateCutoff(m_BothControlbuf);
					
	if (m_LFORouteGUI.Query(RouteGUI::FILTERR) &&
		m_EnvRouteGUI.Query(RouteGUI::FILTERR))		
		m_Filter.ModulateResonance(m_BothControlbuf);
}

void Synth::UpdateValues()
{
	m_Osc1GUI.UpdateValues();
	m_Osc2GUI.UpdateValues();
	m_Osc3GUI.UpdateValues();
	m_Env1GUI.UpdateValues();
	m_Env2GUI.UpdateValues();
	m_Env3GUI.UpdateValues();
	m_FilterGUI.UpdateValues();
	m_MixerGUI.UpdateValues();
	m_Mixer2GUI.UpdateValues();
	m_DelayGUI.UpdateValues();
	m_FilterEnvGUI.UpdateValues();
	m_LFOGUI.UpdateValues();
	m_LFORouteGUI.UpdateValues();
	m_EnvRouteGUI.UpdateValues();
}

void Synth::Randomise()
{
	m_Osc1.Randomise();
	m_Osc2.Randomise();
	m_Osc3.Randomise();
	m_Env1.Randomise();
	m_Env2.Randomise();
	m_Env3.Randomise();
	m_Filter.Randomise();
	m_Mixer.Randomise();
	m_Mixer2.Randomise();
	m_FilterEnv.Randomise();
	m_LFO.Randomise();
	m_LFORouteGUI.Randomise();
	m_EnvRouteGUI.Randomise();
	
	UpdateValues();
}

istream &operator>>(istream &s, Synth &o)
{	
	char t;
	s>>o.m_Osc1>>o.m_Osc2>>o.m_Osc3>>o.m_Env1>>o.m_Env2>>
	o.m_Env3>>o.m_Filter>>o.m_Mixer>>o.m_Mixer2>>o.m_Delay>>
	o.m_FilterEnv>>o.m_LFO>>o.m_LFORouteGUI>>o.m_EnvRouteGUI;
	return s;
}

ostream &operator<<(ostream &s, Synth &o)
{
	s<<" "<<o.m_Osc1<<o.m_Osc2<<o.m_Osc3<<o.m_Env1
	<<o.m_Env2<<o.m_Env3<<o.m_Filter<<o.m_Mixer
	<<o.m_Mixer2<<o.m_Delay<<o.m_FilterEnv<<o.m_LFO
	<<o.m_LFORouteGUI<<o.m_EnvRouteGUI<<endl;	
	return s;
}

int main(int argc, char **argv)
{	
	srand(time(NULL));
	SynthInfo::Get()->LoadPrefs();
	
	Synth *synth=new Synth;
	
	Fl::visual(FL_DOUBLE|FL_RGB);
	synth->CreateWindow();
    synth->Window->show(argc, argv);
			
    for (;;) 
	{
    	if (!Fl::check()) break;
		synth->DoIdle();    	
  	}
	
	delete synth;	
	
	return 1;
}
