/*
 *  Copyleft (C) 2000 David Griffiths <dave@pawfal.org>
 *
 *  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 "Midi.h"
#include "NoteTable.h"
#include "unistd.h"
#include "sys/types.h"
#include "signal.h"

#include "SpiralInfo.h"

static const int MIDI_SCANBUFSIZE=256;
static const int MIDI_KEYOFFSET=0;

MidiDevice::MidiDevice() 
{
	Init();
}

MidiDevice::~MidiDevice() 
{
	Close();
}


void MidiDevice::Close()
{
	if (!SpiralInfo::WANTMIDI) return;
	
	close(m_MidiFd);
	kill(m_ChildPID,9);
}


void MidiDevice::Init()
{
	if (!SpiralInfo::WANTMIDI) return;
	
	m_MidiFd = open(SpiralInfo::MIDIFILE.c_str(),O_RDONLY|O_SYNC);  

	pipe(m_Pipefd);
	fcntl(m_Pipefd[0],F_SETFL,O_NONBLOCK);
	
	m_ChildPID=fork();
	
	if (!m_ChildPID)
	{
		CollectEvents();
	}
  
}

void MidiDevice::CollectEvents()
{		
	unsigned char buf[1];	
	int count,n,nn;
	bool MessageSent;
	unsigned char data[3],last=0;
	
	// constantly scan for relevent input,
	// and write it to the pipe

	// filters out unhandled messages, and attempts to build 
	// coherent messages to send to the midi handler
		
	for(;;)
	{
		read(m_MidiFd,buf,1);
		
		if (*buf!=248) // odd code that gets sent occasionally
		{
			if (*buf>=0x80) // we've got a opcode
			{
				last=data[0]=*buf;
							
				// find out what the opcode means									
				if(data[0]>=0x90 && data[0]<=0x9f) // note on event
				{	
					// get the next two bytes
					read(m_MidiFd,&data[1],1);
					read(m_MidiFd,&data[2],1);
					write(m_Pipefd[1],data,3); 												
				}
				else if(data[0]>=0x80 && data[0]<=0x8f) // note off event
				{
					// get the next two bytes
					read(m_MidiFd,&data[1],1);	
					read(m_MidiFd,&data[2],1);													  
					write(m_Pipefd[1],data,3); 							
				}
				else if(data[0]>=0xe0 && data[0]<=0xef) // pitch wheel event
				{
					// get the next two bytes
					read(m_MidiFd,&data[1],1);
					read(m_MidiFd,&data[2],1);		
					write(m_Pipefd[1],data,3); 							
				}
				else if(data[0]>=0xb0 && data[0]<=0xbf) // parameter event
				{
					// get the next two byte
					read(m_MidiFd,&data[1],1);
					read(m_MidiFd,&data[2],1);						
					write(m_Pipefd[1],data,3); 							
				}
				else
				{
					last=0; // we don't know this opcode
				}
			}
			else // more data 
			{
				if (last!=0) 
				{
					data[0]=last;
					data[1]=*buf;
					
					// get the last byte if it's not a pressure message
					if(data[0]>=0xd0 && data[0]<=0xdf) 
					{
						data[2]=0;
					}					
					else
					{
						read(m_MidiFd,&data[2],1);
					}
							  
					write(m_Pipefd[1],data,3); 							
				}
			}
		}
	}
}
	
MidiEvent MidiDevice::GetEvent()
{
 	// This is a really quick hack based on the outputs from the 
	// /dev/midi device. Doesn't really cope with anything difficult, 
	// such as events being split by progam changes etc etc.

	if (!SpiralInfo::WANTMIDI) return MidiEvent(MidiEvent::NONE,0,0,0);

	unsigned char midi[3];
				
	int count=read(m_Pipefd[0],midi,3);
	
	// no message
	if (count<=0) return MidiEvent(MidiEvent::NONE,0,0,0);
	
	MidiEvent::type MessageType=MidiEvent::NONE;
	int Volume=0,Octave=0,Note=0;

	// todo: assumes midi channel 0 of 16
	switch (midi[0])
	{
		case 0x80: // note off
		{	
			MessageType=MidiEvent::OFF;
			Octave=(midi[1]-MIDI_KEYOFFSET)/12;
	
			// force range of our synth
			if (Octave<0) Octave=0;
			if (Octave>10) Octave=10;
	
			Note=(midi[1]-MIDI_KEYOFFSET)%12;
			
			break;
		}
		case 0x90: // note on
		{
			Volume = midi[2];
			
			// cope with Roland equipment, where note on's 
			// with zero velocity are sent as note offs.
			
			if (Volume) MessageType=MidiEvent::ON;
			else MessageType=MidiEvent::OFF;
			
			Octave=(midi[1]-MIDI_KEYOFFSET)/12;
	
			// force range of our synth
			if (Octave<0) Octave=0;
			if (Octave>10) Octave=10;
	
			Note=(midi[1]-MIDI_KEYOFFSET)%12;
			break;
		}
		case 0xb0: // parameter
		{	
			MessageType=MidiEvent::PARAMETER;
			Octave=0;
			Note=midi[1];
			Volume=midi[2];
			break;
		}
		case 0xe0: // note pitchbend
		{			
			MessageType=MidiEvent::PITCHBEND;
			// should probably take the first byte into account too.
			Volume=midi[2];			
			break;
		}
	}
	
	return MidiEvent(MessageType,Octave,Note,Volume);
}
