/* Copyright (c) 1997 The Regents of the University of California.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.TERMS," in this distribution.  */

/* "LT" = comments/mods  by Larry Troxler lt@westnet.com */

#include "m_imp.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/mman.h>
  
#include <linux/soundcard.h>

#include "linux/mididefs.h"
#include "linux/midi_io.h"
#include "linux/midiparse.h"

/* LT */
/* #define DEBUG_IOCTL */


#define DEFAULTCH 2
#define DEB(a) 
#define SAMPLEWIDTH 2

#define FRAGMENT_NUMBER (fragmentsize == 0x8 ? cp_advance : fragmentnum)
#define BUF_EXPONENT (fragmentsize)  /* makes up a whole dsp buffer = 64 audio  frames */

int fragmentnum = 4;
int fragmentsize = 0x8;


static int fd_out;
static int fd_in;
static char ossdspout[] = "/dev/dsp"; 
static char ossdspin[] = "/dev/dsp1";

static int cp_sr;
static int cp_ch;

static int cp_advance = 16; 
static char *config_string; 
static 	char full_duplex[] = "b";

/* The default audiobufferlength in us */

int sys_schedadvance = 50000; /* scheduler advance in microseconds */

  /*
    set the special "flush zero" but (FS, bit 24) in the
    Control Status Register of the FPU of R4k and beyond
    so that the result of any underflowing operation will
    be clamped to zero, and no exception of any kind will
    be generated on the CPU.
    
    thanks to cpirazzi@cp.esd.sgi.com (Chris Pirazzi).

    TODO: Implement similar thing for linux (GGeiger) 

    One day we will figure this out, I hope, because it 
    costs CPU time dearly on Intel  - LT
  */

static void linux_flush_all_underflows_to_zero(void)
{
  /*    union fpc_csr f;
    f.fc_word = get_fpc_csr();
    f.fc_struct.flush = 1;
    set_fpc_csr(f.fc_word);
    */
}



static void linux_setchsr(int ch, int sr)
{
    cp_ch = ch;
    sys_dacsr = cp_sr = sr;
    cp_advance = (sys_schedadvance * (float)cp_sr) / (1000000. * DACBLKSIZE);
    fprintf(stderr, "advance %d\n", cp_advance);
    memset(sys_soundout, 0, cp_ch * (DACBLKSIZE*sizeof(float)));
    memset(sys_soundin, 0, cp_ch * (DACBLKSIZE*sizeof(float)));
}

/* I expanded and clarified some of the error messages here. - LT */
static int linux_open_audio(int srate)
{  
	int param;
	int orig;
	int tmp;
	
	/* Open the ADC/DAC devices, after this fd_out should be the
           output file descriptor and fd_in should be input
           non valid ones set to -1 */
	
	fd_in = fd_out = -1;
	if (!config_string) config_string = full_duplex;
	/* First check if we can do full duplex */
	
	if ((fd_out = open(ossdspout,O_WRONLY)) == -1) {
		error("failed to open %s; continuing without audio",ossdspout);
		linux_setchsr(2,44100);
		return 0;
	}
	
	if (ioctl(fd_out,SNDCTL_DSP_GETCAPS,&orig) == -1)
		fprintf(stderr,"Pd: SNDCTL_DSP_GETCAPS failed %s\n",ossdspout);
	
	
	/* Try to open full duplex */

	if (*config_string == 'b') {
		if (orig & DSP_CAP_DUPLEX) {
			close(fd_out);
			if ((fd_in = fd_out = open(ossdspout,O_RDWR)) < 0) {
				fprintf(stderr,
					"Pd: opened %s in write only mode\n",
					ossdspout);
			}		
		}
		else {
			if ((fd_in = open(ossdspin,O_RDONLY)) == -1) {
				fprintf(stderr,
					"Pd: opened %s in write only mode\n",
					ossdspout);
			}		
		}		
	}
		  
	if (*config_string == 'a') {
		close (fd_out);
		fd_out=-1;
		if ((fd_in = open(ossdspout,O_RDONLY)) < 0) {
				fprintf(stderr,
					"Pd: unable to open %s for read -- continuing without audio\n",
					ossdspout);
		}
		else 	fprintf(stderr,
					"Pd: opened %s in read only mode\n",
					ossdspout);
	}

		  
		  
	/* configure the soundcard */
	
	/* setting samplerate */
	
	orig = param = srate; 
	
	if (fd_out != -1) {
		if (ioctl(fd_out,SNDCTL_DSP_SPEED,&param) == -1)
			fprintf(stderr,"Could not set sampling rate for %s\n",ossdspout);
		else if( orig != param )
			fprintf(stderr,"%s sampling rate: wanted %d, got %d\n",
				ossdspout, orig, param );
				
		linux_setchsr(DEFAULTCH,param);
	}

	orig = param = srate;
	
	if (fd_in != -1)	 {
		if (ioctl(fd_in,SNDCTL_DSP_SPEED,&param) == -1)
			fprintf(stderr,"Could not set sampling rate for %s\n",
				ossdspin);
		else if( orig != param )
			fprintf(stderr,"%s sampling rate: wanted %d, got %d\n",
				ossdspin, orig, param );
		linux_setchsr(DEFAULTCH,param);
	}
	
	
	/* fragmentsize */
	
	param = (FRAGMENT_NUMBER<<16) + BUF_EXPONENT;
	
	fprintf(stderr,"setting bufferexponent to %x\n",param);
	if (fd_out != -1)
		if (ioctl(fd_out,SNDCTL_DSP_SETFRAGMENT,&param) == -1)
			fprintf(stderr,"Could not set fragment size on %s\n",
				ossdspout);
	
	
	if (fd_in != -1)
		if (ioctl(fd_in,SNDCTL_DSP_SETFRAGMENT,&param) == -1)
			fprintf(stderr,"Could not set fragment size on %s\n",
				ossdspin);
	
	
	/* reset the device, must be done after fragmentsize */
	if (fd_out != -1)
		if (ioctl(fd_out,SNDCTL_DSP_RESET) == -1)
			fprintf(stderr,"Could not reset %s",ossdspout);
	
	if (fd_in != -1)
		if (ioctl(fd_in,SNDCTL_DSP_RESET) == -1)
			fprintf(stderr,"Could not reset %s",ossdspin);
	
	/* setting resolution */
	
	orig = param = AFMT_S16_LE;
	if (fd_out != -1) {
		if (ioctl(fd_out,SNDCTL_DSP_SETFMT,&param) == -1)
			fprintf(stderr,"Could not set DSP format for %s\n",
				ossdspout);
		else if( orig != param )
			fprintf(stderr,"%s DSP format: wanted %d, got %d\n",
				ossdspout, orig, param );
	}
	
	orig = param = AFMT_S16_LE;
	if (fd_in != -1) {
		if (ioctl(fd_in,SNDCTL_DSP_SETFMT,&param) == -1)
			fprintf(stderr,"Could not set format for %s\n",ossdspin);
		else if( orig != param )
			fprintf(stderr,"%s DSP format: wanted %d, got %d\n",
				ossdspin, orig, param );
	}
	
	
	/* setting channels */
	
	orig = param = DEFAULTCH;
	
	if (fd_out != -1) {
		if (ioctl(fd_out,SNDCTL_DSP_CHANNELS,&param) == -1)
			fprintf(stderr,"Could not set channels %s\n",ossdspout);
		else if( orig != param )
			fprintf(stderr,"%s num channels: wanted %d, got %d\n",
				ossdspout, orig, param );		
	}

	orig = param = DEFAULTCH; 
	
	if (fd_in != -1) {
		char* buf[32];
		if (ioctl(fd_in,SNDCTL_DSP_CHANNELS,&param) == -1)
			fprintf(stderr,"Could not set channels %s\n",ossdspin);
		else if( orig != param )
			fprintf(stderr,"%s num channels: wanted %d, got %d\n",
				ossdspin, orig, param );
		
		/* We have to do a read to start the engine. This is 
		   necessary because sys_send_dacs waits until the input
		   buffer is filled and only reads on a filled buffer.
		   This is good, because it's a way to make sure that we
		   will not block */
		post("starting engine");
		read(fd_in,buf,32);
		
	}
	
	
	/* linux_opened = 1; <- never used! */
	return (0);
}



    /* this is unused at the moment. */

void sys_set_dacs(int chans,  int rate)
{
  /*
    if (rate < 1) rate = 44100;
    if (chans != 2 && chans != 4) chans = 2;
    if (sgi_config)
    {
	long pvbuf[4];
	long pvbuflen;
	
	pvbuf[0] = AL_INPUT_RATE;
	pvbuf[2] = AL_OUTPUT_RATE;
	pvbuf[3] = pvbuf[1] = (long)rate;
	pvbuflen = 4;
	ALsetparams(AL_DEFAULT_DEVICE, pvbuf, pvbuflen);
	
	ALgetparams(AL_DEFAULT_DEVICE, pvbuf, pvbuflen);
	
	rate = pvbuf[1];
	if (rate < 1) rate = 44100;
	post("sample rate = %d",  rate);
	ALsetchannels(sgi_config,  chans);
	
	ALcloseport(oport);
	ALcloseport(iport);
	oport = ALopenport("the ouput port", "w", sgi_config);
	if (!oport)
	{
	    fprintf(stderr,"Pd: failed to open audio write port\n");
	}
	iport = ALopenport("the input port", "r", sgi_config);
	if (!iport)
	{
	    fprintf(stderr,"Pd: failed to open audio read port\n");
	}
    
	sgi_setchsr(chans, rate);
    }
    */
}

void sys_close_audio( void)
{
  if (fd_out != -1) close(fd_out);
  if ((fd_in != -1) && (fd_in != fd_out)) close(fd_in);
}



t_sample sys_soundout[MAXCH*DACBLKSIZE];
t_sample sys_soundin[MAXCH*DACBLKSIZE];
float sys_dacsr;

int sys_send_dacs(void)
{
#ifdef DEBUG_IOCTL
	static int ii; 
	t_systime st; 
#endif
	int16_t buf[SAMPLEWIDTH*MAXCH * DACBLKSIZE],*fp2;
	float *fp1;
	long fill;
	int i,j,  nwait = 0;
	int sampstomove = cp_ch * DACBLKSIZE;
	int bytestomove = sampstomove * SAMPLEWIDTH;
	int border = bytestomove*(cp_advance+1);
	audio_buf_info  ainfo;
	
#ifdef DEBUG_IOCTL
	if( !ii )
		sys_gettime(&st);
#endif
	if ((fd_in == -1) && (fd_out == -1)) return 0;
	if (fd_out > 0) ioctl(fd_out, SOUND_PCM_GETOSPACE,&ainfo);
	else ainfo.bytes = bytestomove+1; /* bad hack */

#ifdef DEBUG_IOCTL
	if( !ii )
		printf("GETOSPACE: %d uS, %d samps \n", 
		       sys_microsecsince(&st),
		       ainfo.bytes/(SAMPLEWIDTH*cp_ch));
	if( ++ii > 199 )
		ii = 0;
#endif
	
	fill = ainfo.bytes;
	if (fill <= bytestomove) return (0);
	
	/* do output */
	
	for (i = DACBLKSIZE,  fp1 = sys_soundout,  fp2 = buf; i--;
	     fp1++,  fp2 += cp_ch) {
		for (j=0;j<cp_ch;j++)
			fp2[j] = (int) (32767.0f*fp1[DACBLKSIZE*j]);
	}
	
	write(fd_out, buf,bytestomove);
	
	
	/* do input */
	
	if (fd_in != -1) {
		
		/* Is it possible to read at least one buffer ? */
		
		ioctl(fd_in, SOUND_PCM_GETISPACE,&ainfo);
		fill = ainfo.bytes;
		if (fill > border) {
			while ((fill = ainfo.bytes) > border) {
				post("drop ADC buf");
				ioctl(fd_in, SOUND_PCM_GETISPACE,&ainfo);
				read(fd_in,buf,bytestomove);
			}
		}
		if (fill >= bytestomove)
			read(fd_in, buf,bytestomove);
		else {
		  // fprintf(stderr,"doit fill = %d\n",fill);
			// post("extra ADC buf");
		  //	memset(buf, 0, bytestomove);
			return 0;

		}
		
		for (i = DACBLKSIZE,fp1 = sys_soundin,fp2 = buf; i--;
		     fp1++,  fp2 += cp_ch) {
			for (j=0;j<cp_ch;j++)
				fp1[j*DACBLKSIZE] = (float)fp2[j]/32767.;
			/*3.051850e-05;*/
		}
		
		
	}
	
	memset(sys_soundout, 0, sampstomove*sizeof(float));
	return (1);
}

/* ------------------------- MIDI -------------------------- */
#define NPORTS 16

static int  linux_nports;
MIDIParser *mpa[NPORTS];
MIDIPort *midiport[NPORTS];

void inmidi_noteon(int portno, int channel, int pitch, int velo);

void inmidi_noteonoff(int portno, int channel, int pitch, int velon,int veloff)
{
  /* ignore veloff, pd does not respect it */
  inmidi_noteon(portno, channel, pitch, velon);
}

void inmidi_controlchange(int portno, int channel, int ctlnumber, int value);
void inmidi_programchange(int portno, int channel, int value);
void inmidi_pitchbend(int portno, int channel, int value);
void inmidi_aftertouch(int portno, int channel, int value);
void inmidi_polyaftertouch(int portno, int channel, int pitch, int value);

void linux_open_midi(void)
{
  int i;
/* Look for MIDI devices */
  MIDIPort *mp;

  linux_nports = 0;

  
  if(midi_open()<=0)goto bad;
  
  for (mp=midi_getfirstport();i<NPORTS && mp != NULL;mp=midi_getnextport()){

	 midiport[linux_nports] = mp;

	 mpa[linux_nports] = mp_parse_init(linux_nports);  /* .... */

	 /* initialize all reaction functions */
	 mpa[linux_nports]->noteonoff = inmidi_noteonoff;
	 mpa[linux_nports]->noteon = NULL;
	 mpa[linux_nports]->noteoff = NULL;

	 mpa[linux_nports]->controlchange = inmidi_controlchange;
	 mpa[linux_nports]->programchange = inmidi_programchange;
	 mpa[linux_nports]->pitchbend = inmidi_pitchbend;
	 mpa[linux_nports]->aftertouch = inmidi_aftertouch;
	 mpa[linux_nports]->polyaftertouch = inmidi_polyaftertouch;
	 /* system messages */
	 mpa[linux_nports]->mode = NULL;
	 mpa[linux_nports]->quarterframe = NULL;
	 mpa[linux_nports]->mode = NULL;
	 mpa[linux_nports]->songposition = NULL;
	 mpa[linux_nports]->songselect = NULL;
	 mpa[linux_nports]->tunerequest = NULL;
	 mpa[linux_nports]->sysex = NULL;
	 mpa[linux_nports]->realtime = NULL;


	 linux_nports++;
  };
  

  if (linux_nports == 1) 
	 fprintf(stderr,"MIDIlib: Found one MIDI port on %s\n",midiport[0]->devname);
  else 
	 for(i=0;i<linux_nports;i++) 
		fprintf(stderr,"MIDIlib Found MIDI port %d on %s\n",i,midiport[i]->devname);
			
  return;

 bad:
  error("failed to open MIDI ports; continuing without MIDI.");
  linux_nports = 0;
  return;
}


/* not used but should... */
void linux_close_midi()
{
  int i;
  for(i=0;i<linux_nports;i++){
	 mp_parse_exit(mpa[i]);
	 
  }

  midi_close();
}


void sys_putmidimess(int portno, int a, int b, int c)
{

  switch (md_msglen(a)){
  case 2:
	 midi_out(midiport[portno],a);	 
	 midi_out(midiport[portno],b);	 
	 midi_out(midiport[portno],c);
	 return;
  case 1:
	 midi_out(midiport[portno],a);	 
	 midi_out(midiport[portno],b);	 
	 return;
  case 0:
	 midi_out(midiport[portno],a);	 
	 return;
  };

}


void sys_poll_midi(void)
{
  int i;


  for (i = 0; i < linux_nports; i++)
	 {
		  if(midi_instat(midiport[i]))
			  mp_parse(mpa[i],midi_in(midiport[i]));
    }
}


/* ------------- HELPS ---------------- */

int linux_set_priority() 
{
  struct sched_param par;
  int p1,p2;
#if defined(_POSIX_PRIORITY_SCHEDULING)

  p1 = sched_get_priority_min(SCHED_FIFO);
  p2 = sched_get_priority_max(SCHED_FIFO);

  par.sched_priority = 99; /* Obviously this needs to be refined - LT */

  if (sched_setscheduler(0,SCHED_FIFO,&par) == -1) {
    perror("Setting scheduler");
  }	
#endif

#ifdef _POSIX_MEMLOCK
  if (mlockall(MCL_FUTURE) == -1) perror("Memory locking failed");
#endif


}


    /* public routines */

int sys_init_sysdep(char *config, int srate)
{
    config_string = config; /* LT */
    linux_flush_all_underflows_to_zero();
    DEB(fprintf(stderr,"Opening audio\n");)
    if (linux_open_audio(srate)) return (1);
    DEB(fprintf(stderr,"Opening midi\n");)
    linux_open_midi();
    DEB(fprintf(stderr,"Devices Opened\n");)
    linux_set_priority();

    return (0);
}

float sys_getsr(void)
{
    return (sys_dacsr);
}

int sys_getch(void)
{
    return (cp_ch);
}


void sys_audiobuf(int n)
{

    /* set the size, in milliseconds, of the audio FIFO */
    if (n < 5) n = 5;
    else if (n > 5000) n = 5000;
    fprintf(stderr, "audio buffer set to %d milliseconds\n", n);
    sys_schedadvance = n * 1000;

}

void sys_linuxfrags(int n) {
	fragmentnum = n;
	fprintf(stderr,"fragmentnum  = %d\n",n);
}

void sys_linuxfragsize(int n) {
	fragmentsize = n;
	fprintf(stderr,"fragsize = %d\n",n);
}



