/* 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.txt," in this distribution.  */

#include "m_imp.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <bstring.h>
#include <sys/types.h>
#include <sys/time.h>

#include <dmedia/audio.h>
#include <sys/fpu.h>
#include <dmedia/midi.h>
int mdInit(void);   	    	/* prototype was messed up in midi.h */
/* #include "sys/select.h" */

static ALport iport;
static ALport oport;
static int sgi_opened;
static ALconfig sgi_config;
#define BUFTIME 100	/* .1 sec buffering -- should depend on sched advance */
#define MAXCH 4
#define DEFAULTCH 2

static int cp_ch;
static int cp_advance;

int sys_schedadvance = 50000;	/* scheduler advance in microseconds */
    	/* (this is set rediculously high until we can get the real-time
    	scheduling act together.) */

  /*
    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).
  */

static void sgi_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 sgi_setchsr(int ch, int sr)
{
    cp_ch = ch;
    sys_dacsr = sr;
    cp_advance = (sys_schedadvance * sys_dacsr) / (1000000. * DACBLKSIZE);
    memset(sys_soundout, 0, cp_ch * (DACBLKSIZE*sizeof(float)));
    memset(sys_soundin, 0, cp_ch * (DACBLKSIZE*sizeof(float)));
}

static int sgi_open_audio(void)
{  
    long pvbuf[4];
    long pvbuflen;
    /*
	    *  get current sample rate -- should initialize to this...
	    */
    pvbuf[0] = AL_INPUT_RATE;
    pvbuf[2] = AL_OUTPUT_RATE;
    pvbuflen = 4;
    
    ALgetparams(AL_DEFAULT_DEVICE, pvbuf, pvbuflen);

    if (pvbuf[1] != sys_dacsr)
    	post("warning: input sample rate (%d) doesn't match mine (%f)\n",
    	    pvbuf[1], sys_dacsr);
    
    if (pvbuf[3] != sys_dacsr)
    	post("warning: output sample rate (%d) doesn't match mine (%f)\n",
    	    pvbuf[3], sys_dacsr);
    
    pvbuf[3] = pvbuf[1];
    ALsetparams(AL_DEFAULT_DEVICE, pvbuf, pvbuflen);

    sgi_config = ALnewconfig();

	/* this is not good.  the queue size should reflect scheduler
		addvance; we should use 24 bit samples; and number of channels
		should be configurable. */
    ALsetqueuesize(sgi_config, BUFTIME * 48);
    /* ALsetqueuesize(sgi_config, 1024L); */
    ALsetwidth(sgi_config, AL_SAMPLE_16);
    ALsetsampfmt(sgi_config, AL_SAMPFMT_FLOAT);
    ALsetchannels(sgi_config, DEFAULTCH);

    oport = ALopenport("the ouput port", "w", sgi_config);
    if (!oport)
    {
	fprintf(stderr,"Pd: failed to open audio write port\n");
	return (1);
    }
	    
    iport = ALopenport("the input port", "r", sgi_config);
    if (!iport)
    {
	fprintf(stderr,"Pd: failed to open audio read port\n");
	return (1);
    }
    sgi_opened = 1;
    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 (iport) ALcloseport(iport);
    if (oport) ALcloseport(oport);
}

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

int sys_send_dacs(void)
{
    float buf[2*MAXCH * DACBLKSIZE],  *fp1,  *fp2;
    long fill,  infill,  fsamps = cp_advance * DACBLKSIZE;
    int i,  nwait = 0;
    int sampstomove = cp_ch * DACBLKSIZE;
    fill = ALgetfillable(oport);
    if (fill <= sampstomove) return (0);

    while ((infill = ALgetfilled(iport)) > sampstomove * (cp_advance+1) )
    {
    	/* post("drop ADC buf"); */
	ALreadsamps(iport, buf, sampstomove);
    }
    if (cp_ch == 2)
    {
	for (i = DACBLKSIZE,  fp1 = sys_soundout,  fp2 = buf; i--;
	    fp1++,  fp2 += 2)
	{
		fp2[0] = fp1[0];
		fp2[1] = fp1[DACBLKSIZE];
	}
	ALwritesamps(oport, buf, 2* DACBLKSIZE);
	if (infill > 2*DACBLKSIZE)
	    ALreadsamps(iport, buf, 2* DACBLKSIZE);
	else
	{
	    /* post("extra ADC buf"); */
	    memset(buf, 0, 2*DACBLKSIZE*sizeof(float));
	}
	for (i = DACBLKSIZE,  fp1 = sys_soundin,  fp2 = buf; i--;
	    fp1++,  fp2 += 2)
	{
		fp1[0] = fp2[0];
		fp1[DACBLKSIZE] = fp2[1];
	}
	memset(sys_soundout, 0, 2*DACBLKSIZE*sizeof(float));
    }
    else
    {
	for (i = DACBLKSIZE,  fp1 = sys_soundout,  fp2 = buf; i--;
	    fp1++,  fp2 += 4)
	{
		fp2[0] = fp1[0];
		fp2[1] = fp1[DACBLKSIZE];
		fp2[2] = fp1[2*DACBLKSIZE];
		fp2[3] = fp1[3*DACBLKSIZE];
	}
	ALwritesamps(oport, buf, 4* DACBLKSIZE);
	if (infill > 4*DACBLKSIZE)
	    ALreadsamps(iport, buf, 4* DACBLKSIZE);
	else memset(buf, 0, 4*DACBLKSIZE*sizeof(float));
	for (i = DACBLKSIZE,  fp1 = sys_soundin,  fp2 = buf; i--;
	    fp1++,  fp2 += 4)
	{
		fp1[0] = fp2[0];
		fp1[DACBLKSIZE] = fp2[1];
		fp1[2*DACBLKSIZE] = fp2[2];
		fp1[3*DACBLKSIZE] = fp2[3];
	}
	memset(sys_soundout, 0, 4*DACBLKSIZE*sizeof(float));
    }
    return (1);
}

/* ------------------------- MIDI -------------------------- */

#define NPORT 2

typedef struct _midiport
{
    MDport m_inport;
    MDport m_outport;
} t_midiport;

static t_midiport sgi_port[NPORT];
static int sgi_nports;

void sgi_open_midi(void)
{
    int i;
    sgi_nports = mdInit();
    if (sgi_nports < 0) sgi_nports = 0;
    else if (sgi_nports > NPORT) sgi_nports = NPORT;
    if (!sgi_nports) 
    {
	post("no serial ports are configured for MIDI;");
	post("if you want to use MIDI, try exiting Pd, typing");
	post("'startmidi -d /dev/ttyd2' to a shell, and restarting Pd.");
    }
    else if (sgi_nports == 1) post("Found one MIDI port on %s", mdGetName(0));
    else if (sgi_nports == 2) post("Found MIDI ports on %s and %s",
    	mdGetName(0), mdGetName(1));
    for (i = 0; i < sgi_nports; i++)
    {
    	if (!(sgi_port[i].m_inport = mdOpenInPort(mdGetName(i)))) goto bad;
    	if (!(sgi_port[i].m_outport = mdOpenOutPort(mdGetName(i)))) goto bad;
    }
    return;
bad:
    error("couldn't open MIDI ports; continuing without MIDI.");
    sgi_nports = 0;
}

void sys_putmidimess(int portno, int a, int b, int c)
{
    MDevent mdv;
    if (portno > sgi_nports || portno < 0) return;
    mdv.msg[0] = a;
    mdv.msg[1] = b;
    mdv.msg[2] = c;
    mdv.msg[3] = 0;
    mdv.sysexmsg = 0;
    mdv.stamp = 0;
    mdv.msglen = 0;
    if (mdSend(sgi_port[portno].m_outport, &mdv, 1) < 0)
    	error("MIDI output error\n");
    post("msg out %d %d %d", a, b, c);
}

void inmidi_noteon(int portno, int channel, int pitch, int velo);
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 sys_poll_midi(void)
{
    int i;
    t_midiport *mp;
    for (i = 0, mp = sgi_port; i < sgi_nports; i++, mp++)
    {

	int ret,  status,  b1,  b2, nfds;
	MDevent mdv;
	fd_set inports;
	struct timeval timeout;
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;

	FD_ZERO(&inports);
	FD_SET(mdGetFd(mp->m_inport), &inports);

	if (select(mdGetFd(mp->m_inport)+1 , &inports, 0, 0, &timeout) < 0)
	    perror("midi select");
	if (FD_ISSET(mdGetFd(mp->m_inport),&inports))
	{
    	    if (mdReceive(mp->m_inport, &mdv, 1) < 0)
		error("failure receiving message\n");
	    else if (mdv.msg[0] == MD_SYSEX) mdFree(mdv.sysexmsg);

	    else
	    {
	    	int status = mdv.msg[0];
	    	int channel = (status & 0xf) + 1;
	    	int b1 = mdv.msg[1];
	    	int b2 = mdv.msg[2];
		switch(status & 0xf0)
		{
		case MD_NOTEOFF:
		    inmidi_noteon(i, channel, b1, 0);
		    break;
		case MD_NOTEON:
		    inmidi_noteon(i, channel, b1, b2);
		    break;
		case MD_POLYKEYPRESSURE:
		    inmidi_polyaftertouch(i, channel, b1, b2);
		    break;
		case MD_CONTROLCHANGE:
		    inmidi_controlchange(i, channel, b1, b2);
		    break;
		case MD_PITCHBENDCHANGE:
		    inmidi_pitchbend(i, channel, ((b1 << 7) + b2));
		    break;
		case MD_PROGRAMCHANGE:
		    inmidi_programchange(i, channel, b1);
		    break;
		case MD_CHANNELPRESSURE:
		    inmidi_aftertouch(i, channel, b1);
		    break;
		}
	    }
	}
    }
}

    /* public routines */

int sys_init_sysdep(char *config, int srate)
{
    sgi_setchsr(DEFAULTCH, srate);
    sgi_flush_all_underflows_to_zero();
    if (sgi_open_audio()) return (1);
    sgi_open_midi();
    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;
}

