/***************************************************************************
 midi_adlib.c Copyright (C) 2001 Solomon Peachy

 This program may be modified and copied freely according to the terms of
 the GNU general public license (GPL), as long as the above copyright
 notice and the licensing information contained herein are preserved.

 Please refer to www.gnu.org for licensing details.

 This work is provided AS IS, without warranty of any kind, expressed or
 implied, including but not limited to the warranties of merchantibility,
 noninfringement, and fitness for a specific purpose. The author will not
 be held liable for any damage caused by this work or derivatives of it.

 By using this source code, you agree to the licensing terms as stated
 above.

***************************************************************************/

/* TODO: 
	  Timing issues.
	  ALSA?
*/

#include <stdio.h>
#ifdef HAVE_UNISTD_H
  #include <unistd.h>
#endif
#include <string.h>
#include <midi_device.h>
#include <midiout.h>
#include <soundserver.h>

#ifdef HAVE_SYS_SOUNDCARD_H

#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/soundcard.h>

#if 0
SEQ_DEFINEBUF(2048);
#else
extern unsigned char _seqbuf[2048];
extern int _seqbuflen;
extern int _seqbufptr;
#endif
static int seqfd;

static guint8 instr[MIDI_CHANNELS];
static int dev;
static int free_voices = ADLIB_VOICES;
static long note_time[ADLIB_VOICES];
static unsigned char oper_note[ADLIB_VOICES];
static unsigned char oper_chn[ADLIB_VOICES];

#if 0
void seqbuf_dump()
{
  if (_seqbufptr)
    if (write(seqfd, _seqbuf, _seqbufptr) == -1) {
      perror("ADLIB write ");
      exit(-1);
    }
  _seqbufptr = 0;
}
#endif

/* initialise note/operator lists, etc. */
void adlib_init_lists()
{
  int i;
  for(i = 0 ; i < ADLIB_VOICES ; i++) {
    oper_note[i] = 255;
    oper_chn[i] = 255;
    note_time[i] = 0;
  }
  free_voices = ADLIB_VOICES;
}

int adlib_stop_note(int chn, int note, int velocity)
{
  int i, op=255;
  
  for (i=0;i<ADLIB_VOICES && op==255;i++) {
    if (oper_chn[i] == chn)
      if (oper_note[i] == note)
	op=i;
  }

  if (op==255) {
    printf ("can't stop.. chn %d %d %d\n", chn, note, velocity);
    return 255;	/* not playing */
  }
  
  SEQ_STOP_NOTE(dev, op, note, velocity);
  SEQ_DUMPBUF();

  oper_chn[op] = 255;
  oper_note[op] = 255;
  note_time[op] = 0;
  
  free_voices++;

  return op;
}

int adlib_kill_one_note(int chn)
{
  int oldest = 255, i = 255;
  long time = 0;
     
  if (free_voices >= ADLIB_VOICES) {
    printf("Free list empty but no notes playing\n"); 
    return 255;
  }	/* No notes playing */

  for (i = 0; i < ADLIB_VOICES ; i++) {
    if (oper_chn[i] != chn)
      continue;
    if (note_time[i] == 0)
      continue;
    if (time == 0) {
      time = note_time[i];
      oldest = i;
      continue;
    }
    if (note_time[i] < time) {
      time = note_time[i];
      oldest = i;
    }
  }

  printf("Killing chn %d, oper %d\n", chn, oldest);

  if (oldest == 255) 
    return 255;	/* Was already stopped. Why? */

  SEQ_STOP_NOTE(dev, oldest, oper_note[oldest], 0);
  SEQ_DUMPBUF();

  oper_chn[oldest] = 255;
  oper_note[oldest] = 255;
  note_time[oldest] = 0;
  free_voices++;

  return oldest;
}

void adlib_start_note(int chn, int note, int velocity)
{
  int free;    
  struct timeval now;

  if (velocity == 0) {
    adlib_stop_note(chn, note, velocity);
    return;
  }

  gettimeofday(&now, NULL);  

  if (free_voices <= 0)
    free = adlib_kill_one_note(chn);
  else 
    for (free = 0; free < ADLIB_VOICES ; free++)
      if (oper_chn[free] == 255)
	break;

  printf("play operator %d/%d:  %d %d %d\n", free, free_voices, chn, note, velocity);

  oper_chn[free] = chn;
  oper_note[free] = note;
  note_time[free] = now.tv_sec * 1000000 + now.tv_usec;
  free_voices--;

  SEQ_SET_PATCH(dev, free, instr[chn]);
  SEQ_START_NOTE(dev, free, note, velocity);
  SEQ_DUMPBUF();
}

int midi_adlib_open(guint8 *data_ptr, unsigned int data_length)
{
  int nrdevs, i, n;
  struct synth_info info;
  struct sbi_instrument sbi;

  if (data_length < 1344) {
	  sciprintf ("[ADLIB] invalid patch.003: Expected %d, got %d\n", 1344, data_length);
	  return -1;
  }

  for (i = 0; i < 48; i++) 
    make_sbi((adlib_def *)(data_ptr+(28 * i)), adlib_sbi[i]);

  if (data_length > 1344)
    for (i = 48; i < 96; i++) 
      make_sbi((adlib_def *)(data_ptr+2+(28 * i)), adlib_sbi[i]);

  memset(instr, 0, sizeof(instr));
 
  if (!IS_VALID_FD(seqfd=open("/dev/sequencer", O_WRONLY, 0))) {
    perror("/dev/sequencer");
    return(-1);
  }
  if (ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &nrdevs) == -1) {
    perror("/dev/sequencer");
    return(-1);
  }
  for (i=0;i<nrdevs && dev==-1;i++) {
    info.device = i;
    if (ioctl(seqfd, SNDCTL_SYNTH_INFO, &info)==-1) {
      perror("info: /dev/sequencer");
      return(-1);
    }
    if (info.synth_type == SYNTH_TYPE_FM)
      dev = i;
  }
  if (dev == -1) {
    fprintf(stderr, "ADLIB: FM synthesizer not detected\n");
    return(-1);
  }

  /*  free_voices = info.nr_voices; */
  adlib_init_lists();

  printf("ADLIB: Loading patches into synthesizer\n");
  sbi.device = dev;
  sbi.key = FM_PATCH;
  for (i = 0; i < 96; i++) {
    for (n = 0; n < 32; n++)
      memcpy(sbi.operators, &adlib_sbi[i], sizeof(sbi_instr_data));
    sbi.channel=i;
    SEQ_WRPATCH(&sbi, sizeof(sbi));
    SEQ_DUMPBUF();
  }

  return 0;
}


int midi_adlib_close()
{
  SEQ_DUMPBUF();
  return close(seqfd);
}

int midi_adlib_allstop(void) {
  int i;
  for (i = 0; i < ADLIB_VOICES ; i++) {
    if (oper_chn[i] == 255)
      continue;
    adlib_stop_note(oper_chn[i], oper_note[i], 0);
  }
  adlib_init_lists();
  printf("end allstop\n");
  return 0;
}

int midi_adlib_reverb(short param)
{
  printf("reverb NYI %04x \n", param);
  return 0;
}

int midi_adlib_event(guint8 command, guint8 note, guint8 velocity, guint32 delta)
{
  guint8 channel, oper;

  channel = command & 0x0f;
  oper = command & 0xf0;

  switch (oper) {    
  case 0x80:
    adlib_stop_note(channel, note, velocity);
    return 0;
  case 0x90:  
    adlib_start_note(channel,note,velocity);
    return 0;
  case 0xe0:    /* Pitch bend needs scaling? */
    SEQ_BENDER(dev, channel, ((note << 8) & velocity));
    SEQ_DUMPBUF();
    break;
  case 0xb0:    /* CC changes.  we ignore. */
    /* XXXX we need to parse out 0x07 volume, at least. */
    return 0;
  case 0xd0:    /* aftertouch */
    SEQ_CHN_PRESSURE(dev, channel, note);
    SEQ_DUMPBUF();    
    return 0;
  default:
    printf("ADLIB: Unknown event %02x\n", command);
    return 0;
  }
  
  SEQ_DUMPBUF();
  return 0;
}

int midi_adlib_event2(guint8 command, guint8 param, guint32 delta)
{
  guint8 channel;
  guint8 oper;
  
  channel = command & 0x0f;
  oper = command & 0xf0;
  switch (oper) {
  case 0xc0: {  /* change instrument */
    int inst = param;
    instr[channel] = inst; /* XXXX offset? */
    //    SEQ_SET_PATCH(dev, channel, inst);
    //    SEQ_DUMPBUF();
    return 0;
  }
  default:
    printf("ADLIB: Unknown event %02x\n", command);
  }

  SEQ_DUMPBUF();
  return 0;
}

/* the driver struct */

midi_device_t midi_device_adlib = {
  "adlib",
  "v0.01",
  &midi_adlib_open,
  &midi_adlib_close,
  &midi_adlib_event,
  &midi_adlib_event2,
  &midi_adlib_allstop,
  NULL,
  &midi_adlib_reverb,
  003,		/* patch.003 */
  0x04,		/* playflag */
  0, 		/* do not play channel 9 */
  ADLIB_VOICES  /* Max polyphony */
};

#endif /* HAVE_SYS_SOUNDCARD_H */
