/*
 * This software is released under GPL. If you don't know what that is
 * immediately point your webbrowser to  http://www.gnu.org and read
 * all about it.
 *
 * The short version: You can modify the sources for your own purposes
 * but you must include the new source  if you distribute a derived work.
 *
 * (C) 2002, 2003, 2004, 2005 Jens M Andreasen <ja@linux.nu>
 *
 *
 *
 *     (
 *      )
 *    c[]
 *
 */ 

#define SAMPLERATE 48000
//#define SAMPLERATE 44100


#define VOICEPOOL  48
int WITH_JACK;
int WITH_ALSA = 0;
#include "mx44.h"
#include "old/mmx4.h"

// Convert from old to new format
//
#define cnv(element) mx44->element = mx4->element
static 
void mx44cnv(Mx44patch *mx44,mmx4patch_t *mx4)
{

  int i;
  for(i = 0;i<4;++i)
    {
      int j;
      /************************
	int harmonic[4];
	int detune[4];
	short velocityfollow[4];
	unsigned char od[4];
	unsigned char button[4];
	int velocity[4];
	int breakpoint[4];
      *************************/
      cnv(harmonic[i]);
      cnv(detune[i]);
      cnv(velocityfollow[i]);
      cnv(od[i]);
      cnv(button[i]);
      cnv(velocity[i]);
      cnv(breakpoint[i]);
      
      for(j=0;j<4;++j)
	{
	  /***************
	    int  pm[4][4];
	    int  am[4][4];
	  ****************/
	  cnv(pm[i][j]);
	  cnv(am[i][j]);
	}
      for(j=0;j<8;++j)
	{
	  /*********************
	    int  env_level[4][8];
	    int  env_time[4][8];
	  **********************/
	  cnv(env_level[i][j]);
	  cnv(env_time[i][j]);
	}

      for(j=0;j<2;++j)
	{
	  /**********************
	    int keybias[4][2];
	    int keyfollow[4][2];
	    short intonation[4][2];
	    int phase[4][2];
	    int mix[4][2];
	  **********************/
	  cnv(keybias[i][j]);
	  cnv(keyfollow[i][j]);
	  cnv(intonation[i][j]);
	  cnv(phase[i][j]);
	  cnv(mix[i][j]);
	}
    }
  /***************
    char name[32];
  ****************/
  for(i=0;i<32;++i)
    cnv(name[i]);
}
#undef cnv


#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <termio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/soundcard.h>

#include <gtk/gtk.h>


int mx44running;
/* Setting up audio and realtime scheduler:
 */
static int    _argc;
static char **_argv;

static struct {
  char * path;
  int speed;
  int format;
  int is_stereo;
  int device;
  int bufsize;
  struct audio_buf_info info;
}audio;


static struct {
  char * path;
  int device;
  unsigned char buffer[128];
  unsigned char data[4]; // count, status, data1, data2


}midi;


int setup_audio(char *name,int jack,int oss_midi)
{

  /** 
   * We would like realtime performance
   *
   */
  struct sched_param schp; 

  memset(&schp, 0, sizeof(schp));
  schp.sched_priority = sched_get_priority_max(SCHED_RR);

  // printf("AudioPriority level: %d\n",schp.sched_priority);
  
  if (sched_setscheduler(0, SCHED_RR, &schp) != 0) 
    {
      fprintf(stderr,
	      "\n Can't run in realtime! Please do (as 'root'): \n"
	      "   chown root.root %s && chmod 4711 %s\n\n",name,name);
    }

  /**
   * Give back ownership to the real user, so that he can read
   * created files, and so that we don't fuck up the machine while
   * posing as root.
   *
   */
  setreuid(getuid(), getuid());	

  midi.path  = "/dev/midi";
  //audio.path = "/dev/dsp";
  audio.path = "/dev/dsp1";

  if(!jack)
    {  

      
    

      /** Setting up the soundcard for CD-HiFidelity Stereo or better 
       * 
       */
      audio.speed     = SAMPLERATE;
      audio.format    = AFMT_S16_LE; 
      audio.is_stereo = TRUE;
      // 3 buffers of 256 bytes == 3 * 64 (16bit) stereo samples
      // 0x0002 buffers == n - 1
      //   0008 bytes   == log2(number of bytes in buffer), 2^8 == 256 == 64 stereo samples;
      
      
      audio.bufsize = 0x00010007; 
      //audio.bufsize = 0x00030008; 
      
      audio.device=open(audio.path, O_WRONLY);
      if (audio.device == -1) {
	perror("Opening audio device");
	
	audio.path = "/dev/dsp";
	audio.device=open(audio.path, O_WRONLY);
	
	if (audio.device == -1) {
	  perror("Opening audio device");
	 	
	  return 1;
	}
      }
      else
	midi.path = "/dev/midi1";

      if (ioctl(audio.device, SNDCTL_DSP_SETFRAGMENT, &audio.bufsize)) 
	perror("DSP_SETFRAGMENT");
      if (ioctl(audio.device, SNDCTL_DSP_SETFMT, &audio.format) == -1) {
	perror("DSP_SETFMT");
	return 2;
      }
      if (ioctl(audio.device, SNDCTL_DSP_STEREO, &audio.is_stereo) == -1) {
	perror("DSP_STEREO");
	return 3;
      }
      if (ioctl(audio.device, SNDCTL_DSP_SPEED, &audio.speed) == -1) {
	perror("DSP_SPEED");
	return 4;
      }
      if (ioctl(audio.device, SNDCTL_DSP_GETOSPACE, &audio.info) == -1) {
	perror("DSP_GETOSPACE");
	return 5;
      }    
	fprintf(stderr,
		"Blocksize: %d\n"
		"Blocks   : %d\n"
		"Bytes    : %d\n"
		,audio.info.fragsize,audio.info.fragstotal,audio.info.bytes);
	  fprintf(stderr,
		  "\nLatency   : %.3f ms\n"
		  "\nSamplerate: %d\n\n",
		  ((1000*audio.info.bytes)/4)/(double)audio.speed,audio.speed);
	  

    
    }
  if(oss_midi)
    {
      //midi.path = "/dev/sound/mx4";

      if((midi.device = open(midi.path, O_RDONLY|O_NONBLOCK,0)) == -1) 
	{
	  perror(midi.path);
	  //return 6;
	  WITH_ALSA = TRUE;
	}

    }
  
  return 0;
}


extern void setwidgets(Mx44patch *patch,int channel,int patchNumber);
extern int main_interface (int argc, char *argv[]);

void * interface(void* t_arg)
{

  setreuid(getuid(), getuid());	
 
  main_interface(_argc,_argv);

  mx44running = FALSE;
  return NULL;
}



Mx44state * mx44;
Mx44patch *mx44patch;
char *mx44patchNo;
Mx44patch *mx44tmpPatch; 

int patch_fd;

void save_state(void)
{
  if(patch_fd > 0)
    {
      int count;
      lseek(patch_fd,0,SEEK_SET);
      count = write(patch_fd,mx44->patch,
		    sizeof(mx44->patch));
      count += write(patch_fd,mx44->tmpPatch,
		     sizeof(mx44->tmpPatch));
      count += write(patch_fd,mx44->patchNo,
		     sizeof(mx44->patchNo));
      count += write(patch_fd,mx44->monomode,
		     sizeof(mx44->monomode));
      
      if(count == sizeof(mx44->patch) 
	 + sizeof(mx44->tmpPatch) 
	 + sizeof(mx44->patchNo)
	 + sizeof(mx44->monomode))
	puts("\nVoice data saved\n");
      else
	puts("\nVoice data not saved (?)\n");
    }
  fprintf(stderr,"Thankyou for using Mx4!\n");
  
}
int main(int argc,char **argv)
{
  
  short outbuf[2][OUTBUF];//  s=0;
  unsigned char ev[128]; // midi events
  
  pthread_t i_thread; // graphic interface
  pthread_attr_t i_thread_attr; 
  
  unsigned int i = 0;
  int isGraphic = 0;
  
  int voicepool = VOICEPOOL;

  if(argc > 1)
    {
      voicepool = atoi(argv[argc -1]);
      if(voicepool == 0)
	voicepool = VOICEPOOL;
    }
  
  
  /*
   * mmx_ok() seems to be broken with gcc 3.x ?
   * 
   if(mmx_ok()) puts("MMX OK!");
   else puts("MMX not available."), exit(0);
   *
   */
  
  
  mx44 = mx44_new(voicepool,"mx44patch");
  
  if(!(WITH_JACK = mx44->samplerate))
    mx44->samplerate=SAMPLERATE;
  
  printf("\nsizeof %d voices: %d, %d keyboards: %d, 128 patches: %d\n",
	 voicepool,
	 sizeof(Mx44voice) * voicepool,
	 MIDICHANNELS,
	 sizeof(Mx44state) - sizeof(Mx44patch) * 128,
	 sizeof(Mx44patch) * 128);
  

  /* FIXME: Globals for the userinterface 
   */
  mx44patch = mx44->patch;
  mx44patchNo = mx44->patchNo;
  mx44tmpPatch= mx44->tmpPatch; 
  
  patch_fd = open("mx41patch",O_RDWR|O_CREAT,0666);
  if(patch_fd > 0)
    {
      int count = read(patch_fd,mx44->patch,
		       sizeof(mx44->patch) 
		       + sizeof(mx44->tmpPatch)
		       + sizeof(mx44->patchNo)
		       + sizeof(mx44->monomode));
      if(count > 0)
	puts("\nVoice data loaded\n");

      else // Try to convert old patches
	{
	  mmx4patch_t legacy[128];
	  int tmp_fd = open("mx4patch",O_RDONLY,0666);
	  
	  puts("\n Voice data (mx44upper/lower) not available\n Trying to find old patches");
	  
	  if(tmp_fd > 0)
	    {
	      int count = read(tmp_fd,legacy,sizeof(mmx4patch_t)*128);
	      if(count > 0)
		{
		  int i;
		  puts(" Found! Converting old patches to new ...\n");
		  for(i=0;i<128;++i)
		    mx44cnv(mx44->patch + i,legacy + i);
		  for(i=0;i < MIDICHANNELS;++i)
		    {
		      mx44->tmpPatch[i] = mx44->patch[0];
		      mx44->patchNo[i] = 0;
		    }
		}
	      else
		puts(" Not found! Sorry :(\n");
	      close(tmp_fd);
	    }
	}
      atexit(save_state);
    }

  mx44_reset(mx44,440.0, mx44->samplerate);
  

  
  mx44running = 1;  

  if(setup_audio(argv[0],WITH_JACK,1)) //!(getopt(argc,argv,"o") == 'o')))
    {
      mx44running = 0;  
      exit(1);
    }
  
  if(getenv("DISPLAY"))
    {
      _argc = argc;
      _argv = argv;

      pthread_attr_init(&i_thread_attr);
      pthread_attr_setinheritsched(&i_thread_attr,PTHREAD_EXPLICIT_SCHED);
      
      pthread_create(&i_thread,&i_thread_attr,interface,NULL);
      
      isGraphic =  mx44->isGraphic = 1; 
    }
  
     
     
  // Running from Jack
  if(WITH_JACK && WITH_ALSA)
    {

      while(mx44running)
	sleep(1);
      exit(0);
    }
  // -------------


  puts("running OK!");

  if(!WITH_ALSA)
    while(read(midi.device,ev,1)>0) // flush midi in
      ;

  while(mx44running)
    {

      if(WITH_JACK)
	usleep(500);
      {
	int n = 0;
	
	if(!WITH_ALSA)
	  for(n = 0; read(midi.device, ev+n, 1) > 0 && n < sizeof(ev); ++n)
	    ;
	  if(n)
	    {
	      int b = 0;

	      while(midi.data[0] < 3 && b < n )
		{
		  switch(midi.data[0]) 
		    {
		    case 0: 
		      if(ev[b] > 127) // new status byte
			midi.data[midi.data[0] = 1] = ev[b++];
		      else // running status, first data
			midi.data[midi.data[0] = 2] = ev[b++];
		      break;
		    case 1: 
		      if(ev[b] < 128) // first data byte
			midi.data[midi.data[0] = 2] = ev[b++];
		      else // unexpected new status byte
			midi.data[midi.data[0] = 1] = ev[b++];
		      break;
		    case 2: 
		      if(ev[b] < 128) // second data byte
			midi.data[midi.data[0] = 3] = ev[b++];
		      else // unexpected new status byte
			midi.data[midi.data[0] = 1] = ev[b++];
		      break;
		    }
		  switch(midi.data[1] & 0x0F0) 
		    {
		      // 2byte messages
		    case 0x0C0: // program change
		      if(midi.data[0] == 2)
			{
			  mx44_pgmchange(mx44,midi.data[1],midi.data[2]);
			  if(isGraphic)
			    setwidgets(mx44tmpPatch,midi.data[1] & 0x0F,midi.data[2]);
			  
			  midi.data[0] =0;
			}
		      break;
		    case 0x0D0: // channel pressure
		      if(midi.data[0] == 2)
			{
			  mx44_chanpress(mx44,midi.data[1],midi.data[2]);
			  midi.data[0] =0;		    		    
			}

		      break;		  
		    case 0x0F0: // SysEX etc
		      midi.data[0] = 0; // ignore data
		      break;
		      
		    default:
		      // assume 3byte message 
		      if(midi.data[0] == 3)
			{
			  pthread_mutex_lock(&mx44->busy);

			  if((midi.data[1]&0x0F0) == 0x090)
			    mx44_noteon(mx44,midi.data[1], midi.data[2], midi.data[3]);
			  else if((midi.data[1]&0x0F0) == 0x0E0 )
			    mx44_pitchbend(mx44,midi.data[1],midi.data[2] ,midi.data[3]);
			  else if((midi.data[1]&0x0F0) == 0x080 )
			    mx44_noteoff(mx44,midi.data[1],midi.data[2] ,midi.data[3]);
			  else if((midi.data[1]&0x0F0) == 0x0B0 )
			    mx44_control(mx44,midi.data[1],midi.data[2] ,midi.data[3]);
			  else printf("midi: %i | %i %i %i\n",
				      midi.data[0],midi.data[1],midi.data[2],midi.data[3]);
			  
			  midi.data[0] =0;
			 
			  pthread_mutex_unlock(&mx44->busy);
			  sched_yield();
			}
		    }
		}
	    }
	}
      if(!WITH_JACK)
	{
	  bzero(outbuf[1&i], 2 * OUTBUF);
	  mx44_play(mx44,outbuf[1&i]);      
	  write(audio.device, outbuf[1&i], sizeof(*outbuf));
	}
      i++;
    }

  return 0;
}

