/*
 *  Copyright (c) 1994/95 by Jaroslav Kysela (Perex soft)
 *  Sequencer routines (/dev/sequencer and /dev/sequencer2 devices)
 *  VoxWare 3.00 compatible - interface is copyrighted by Hannu Savolainen
 */

#include "gus_dev.h"

#ifdef VOXWARE_SEQUENCER

/*
 *
 */

extern unsigned char command[];
extern short cmd_size;

extern unsigned long gf1_abs_tick;

extern short gf1_abort_flag;

/*
 *
 */

#define SEQ_IQUEUE_LEN	128

#define TICK_US	( ( 60 * 1000000 ) / ( curr_tempo * curr_timebase ) )

#define SEQ_MODE_NONE	0
#define SEQ_MODE_1	1
#define SEQ_MODE_2	2

#define SEQ_DEV_CNT	2
#define SEQ_DEV_GF1	0		/* GF1 chip */
#define SEQ_DEV_MIDI	1		/* MIDI port */

#define EVENT_SIZE	8

#define GF1_VOL_MODE_NONE	0
#define GF1_VOL_MODE_ADAGIO	1
#define GF1_VOL_MODE_LINEAR	2

/*
 *
 */

struct gus_sample_add_info {		/* additional info for midi samples */
  short next;				/* next patch for same pgm # */
  short pgm;				/* MIDI pgm chn # */
  unsigned int base_freq;
  unsigned int base_note;
  unsigned int high_note;
  unsigned int low_note;
  short volume;
};

struct gus_seq_voice {
  short pgm;
  unsigned int orig_freq;
  int bender;
  int bender_range;
  int midi_main_vol;
  int midi_volume;
  int midi_expression_vol;
  int midi_patch_vol;
  short panning;
};

/*
 *
 */

static struct synth_info gus_info_vox =
{
  "Gravis UltraSound",
  0,
  SYNTH_TYPE_SAMPLE,
  SAMPLE_TYPE_GUS,
  0,
  16,
  0,
  GUS_MAX_SAMPLES			/* MAX_PATCH */
};

static struct synth_info midi_info_vox =
{
  "GUS MIDI interface", 
  0, 
  SYNTH_TYPE_MIDI,
  0,
  0,
  128,
  0,
  128,
  SYNTH_CAP_INPUT
};

static unsigned short semitone_tuning[24] = 
{
/*   0 */ 10000, 10595, 11225, 11892, 12599, 13348, 14142, 14983, 
/*   8 */ 15874, 16818, 17818, 18877, 20000, 21189, 22449, 23784, 
/*  16 */ 25198, 26697, 28284, 29966, 31748, 33636, 35636, 37755
};

static unsigned short cent_tuning[100] =
{
/*   0 */ 10000, 10006, 10012, 10017, 10023, 10029, 10035, 10041, 
/*   8 */ 10046, 10052, 10058, 10064, 10070, 10075, 10081, 10087, 
/*  16 */ 10093, 10099, 10105, 10110, 10116, 10122, 10128, 10134, 
/*  24 */ 10140, 10145, 10151, 10157, 10163, 10169, 10175, 10181, 
/*  32 */ 10187, 10192, 10198, 10204, 10210, 10216, 10222, 10228, 
/*  40 */ 10234, 10240, 10246, 10251, 10257, 10263, 10269, 10275, 
/*  48 */ 10281, 10287, 10293, 10299, 10305, 10311, 10317, 10323, 
/*  56 */ 10329, 10335, 10341, 10347, 10353, 10359, 10365, 10371, 
/*  64 */ 10377, 10383, 10389, 10395, 10401, 10407, 10413, 10419, 
/*  72 */ 10425, 10431, 10437, 10443, 10449, 10455, 10461, 10467, 
/*  80 */ 10473, 10479, 10485, 10491, 10497, 10503, 10509, 10515, 
/*  88 */ 10521, 10528, 10534, 10540, 10546, 10552, 10558, 10564, 
/*  96 */ 10570, 10576, 10582, 10589
};

static int seq_mode[ SEQ_DEV_CNT ] = { SEQ_MODE_NONE, SEQ_MODE_NONE };
static struct gus_seq_voice seq_voices[ 32 ];
static struct gus_sample_add_info samples_add[ GUS_MAX_SAMPLES ];
static short patch_map[ GUS_MAX_SAMPLES ];

static int volume_base, volume_scale;
static short gf1_vol_mode = GF1_VOL_MODE_NONE;

static int curr_tempo;
static int curr_timebase;

static unsigned long seq_abs_tick;

static int iqueue_size;
static short iqueue_head;
static short iqueue_tail;
static short iqueue_event[ SEQ_IQUEUE_LEN ][ EVENT_SIZE ]; 
static unsigned long iqueue_time[ SEQ_IQUEUE_LEN ];

/*
 * next two functions are borrowed from VoxWare driver
 */

static int note_to_freq( int note_num )
{
  /*
   * This routine converts a midi note to a frequency (multiplied by 1000)
   */
  int note, octave, note_freq;
  int notes[] = {
    261632, 277189, 293671, 311132, 329632, 349232,
    369998, 391998, 415306, 440000, 466162, 493880
  };

#define BASE_OCTAVE	5

  octave = note_num / 12;
  note = note_num % 12;

  note_freq = notes[ note ];

  if ( octave < BASE_OCTAVE )
    note_freq >>= ( BASE_OCTAVE - octave );
   else 
  if ( octave > BASE_OCTAVE )
    note_freq <<= ( octave - BASE_OCTAVE );
  return note_freq;
}

static unsigned long compute_finetune( unsigned long base_freq, int bend, int range )
{
  unsigned long amount;
  int negative, semitones, cents, multiplier = 1;

  if ( !bend || !range || !base_freq ) return base_freq;

  if ( range >= 8192 ) range = 8191;

  bend = ( bend * range ) / 8192;
  if ( !bend ) return base_freq;

  negative = bend < 0;

  if ( negative ) bend = -bend;
  if ( bend > range ) bend = range;

  while ( bend > 2399 )
    {
      multiplier *= 4;
      bend -= 2400;
    }

  semitones = bend / 100;
  cents = bend % 100;

  amount = (int) (semitone_tuning[semitones] * multiplier * cent_tuning[cents])
    / 10000;

  if (negative)
    return (base_freq * 10000) / amount;	/* Bend down */
   else
    return (base_freq * amount) / 10000;	/* Bend up */
}

/*
 *
 */

static void gus_reset_seq_voices( void )
{
  short i;
  
  for ( i = 0; i < 32; i++ )
    {
      seq_voices[ i ].pgm =
      /* seq_voices[ i ].sample = */ -1;
      seq_voices[ i ].orig_freq = 20000;
      seq_voices[ i ].bender = 0;
      seq_voices[ i ].bender_range = 200;
      seq_voices[ i ].midi_main_vol = 127;
      seq_voices[ i ].midi_volume = 127;
      seq_voices[ i ].midi_expression_vol = 127;
      seq_voices[ i ].midi_patch_vol = 127;
      seq_voices[ i ].panning = 0;
    }
}
 
static void gus_reset_patches( void )
{
  short i;

  gus_reset_samples();
  for ( i = 0; i < GUS_MAX_SAMPLES; i++ )
    patch_map[ i ] = -1;
}

static int gus_patch_load( struct patch_info *fs_patch )
{
  struct patch_info patch;
  struct GUS_STRU_DOWNLOAD smp;
  int result;
  
  if ( VERIFY_AREA( VERIFY_READ, fs_patch, sizeof( *fs_patch ) ) ) 
    return -ENXIO;
  MEMCPY_FROMFS( &patch, fs_patch, sizeof( patch ) );
  if ( patch.key != GUS_PATCH || patch.device_no != SEQ_DEV_GF1 )
    return -ENXIO;
  smp.sample_ptr = (unsigned char *)&fs_patch -> data;
  smp.voice_cntrl_reg = 0;
  if ( patch.mode & WAVE_16_BITS ) smp.voice_cntrl_reg |= GUS_WAVE_16BIT;
  if ( patch.mode & WAVE_UNSIGNED ) smp.voice_cntrl_reg |= GUS_WAVE_INVERT;
  if ( patch.mode & WAVE_LOOPING ) smp.voice_cntrl_reg |= GUS_WAVE_LOOP;
  if ( patch.mode & WAVE_BIDIR_LOOP ) smp.voice_cntrl_reg |= GUS_WAVE_BIDIRECTIONAL;
  if ( patch.mode & WAVE_LOOP_BACK ) smp.voice_cntrl_reg |= GUS_WAVE_LOOP_BACK;
#if 0
  if ( patch.mode & WAVE_SUSTAIN_ON )
  if ( patch.mode & WAVE_ENVELOPES )
  if ( patch.mode & WAVE_VIBRATO )
  if ( patch.mode & WAVE_TREMOLO )
  if ( patch.mode & WAVE_SCALE )
#endif
  if ( patch.mode & WAVE_LOOPING )
    {
      if ( patch.loop_start < 0 || patch.loop_start >= patch.len )
        return -EINVAL;
      if ( patch.loop_end < patch.loop_start || patch.loop_end > patch.len )
        return -EINVAL;
    }
  smp.loop_start = patch.loop_start;
  smp.loop_end = patch.loop_end;
  smp.length = patch.len;
#if 0
  if ( smp.loop_end > smp.length ) smp.loop_end = smp.length;
#endif
  if ( ( result = gus_download1( &smp ) ) < 0 )
    return result;
  samples_add[ gus_last_sample ].next = -1;	/* no next patch */
  samples_add[ gus_last_sample ].pgm = patch.instr_no;
  samples_add[ gus_last_sample ].base_freq = patch.base_freq;
  samples_add[ gus_last_sample ].base_note = patch.base_note;
  samples_add[ gus_last_sample ].high_note = patch.high_note;
  samples_add[ gus_last_sample ].low_note = patch.low_note;
  samples_add[ gus_last_sample ].volume = patch.volume;
  for ( result = gus_last_sample - 1; result >= 0; result-- )
    if ( samples_add[ result ].pgm == patch.instr_no &&
         samples_add[ result ].next == -1 )
      {
        samples_add[ result ].next = gus_last_sample;
        break;
      }
  if ( patch_map[ patch.instr_no ] < 0 )
    patch_map[ patch.instr_no ] = gus_last_sample;
  return 0;
}

static inline unsigned short gus_adagio_vol( int vel, int mainv, int xpn, int voicev )
{
  int x;
  
  x = 256 + 6 * ( voicev - 64 );
  if ( voicev > 65 ) xpn += voicev - 64;
  xpn += ( voicev - 64 ) / 2;
  x = vel * xpn * 6 + ( voicev / 4 ) * x;
#if 1
  x = ( x * 5 ) / 7;	/* perex */
#endif
#if 0
  PRINTK( "x = %d\n", x );
#endif
  return lvol_to_gf1_vol_16( x );
}

static unsigned short get_volume( short voice )
{
  unsigned short volume = 0;

  if ( seq_voices[ voice ].midi_volume > 128 )
    seq_voices[ voice ].midi_volume = 128;
  
  switch ( gf1_vol_mode ) {
    case GF1_VOL_MODE_NONE:
      volume = volume_base + ( seq_voices[ voice ].midi_volume * volume_scale );
      break;
    case GF1_VOL_MODE_ADAGIO:
      volume = gus_adagio_vol( seq_voices[ voice ].midi_volume,
                               seq_voices[ voice ].midi_main_vol,
                               seq_voices[ voice ].midi_expression_vol,
                               seq_voices[ voice ].midi_patch_vol );
      break;
    case GF1_VOL_MODE_LINEAR:
      {
        int i;
        
        i = seq_voices[ voice ].midi_volume *
            seq_voices[ voice ].midi_main_vol;
        i = ( i * 7 ) / 2;
        volume = lvol_to_gf1_vol_16( i );
      }
      break;
    default:
      PRINTK( "get_volume: unknown volume mode\n" );
      return 0;
  }
  return volume;
}

static void volume_change( short voice )
{
  unsigned short volume;

  volume = get_volume( voice );
  cmd_size = 4;
  command[ 0 ] = GUS_CMD_VS_CURRENT_VOL;
  command[ 1 ] = (unsigned char)voice;
  command[ 2 ] = (unsigned char)volume;
  command[ 3 ] = (unsigned char)(volume >> 8);
  if ( do_gus_command() < 0 )
    PRINTK( "volume_change: error\n" );
}

static int gus_note_on( short voice, short note )
{
  unsigned short volume;
  short sample, best_sample;
  int delta_freq;
  unsigned int note_freq, best_delta, freq;
  
#if 0
  PRINTK( "gus_note_on: start (voice=%d, pgm=%d, sample=%d, note=%d)\n",
  				voice, seq_voices[ voice ].pgm,
  				seq_voices[ voice ].pgm < 0 ? -1 :
  				patch_map[ seq_voices[ voice ].pgm ],
  				(int)note );
  PRINTK( "-----------: vol=%d, mvol=%d\n",
  				seq_voices[ voice ].midi_volume,
  				seq_voices[ voice ].midi_main_vol );
#endif
  if ( voice < 0 || voice > 31 )
    return -EINVAL;
  if ( ( sample = seq_voices[ voice ].pgm ) < 0 )
    return -EINVAL;
  if ( ( sample = patch_map[ sample ] ) < 0 )
    return -EINVAL;
  if ( note == 255 )
    {
      volume_change( voice );
      return 0;
    }
#if 0
  PRINTK( "gus_note_on: patch ok\n" );
#endif    
  note_freq = note_to_freq( note );
  best_sample = sample;
  best_delta = 100000000;
  while ( sample >= 0 )
    {
      delta_freq = note_freq - samples_add[ sample ].base_note;
      if ( delta_freq < 0 ) delta_freq = -delta_freq;
      if ( delta_freq < best_delta )
        {
          best_sample = sample;
          best_delta = delta_freq;
        }
      if ( samples_add[ sample ].low_note <= note_freq &&
           samples_add[ sample ].high_note >= note_freq )
        break;
      sample = samples_add[ sample ].next;
    }
  if ( sample < 0 ) 
    sample = best_sample;

#if 0
  PRINTK( "base_freq = %d, note_freq = %d, base_note = %d\n", 
  		samples_add[ sample ].base_freq,
  		note_freq,
  		samples_add[ sample ].base_note );
#endif
  
  freq = ( samples_add[ sample ].base_freq * ( note_freq / 100 ) ) /
         ( samples_add[ sample ].base_note / 100 );

  freq = compute_finetune( freq, 
                           seq_voices[ voice ].bender, 
                           seq_voices[ voice ].bender_range );

  seq_voices[ voice ].orig_freq = freq;
    
  volume = get_volume( voice );
  
#if 0
  PRINTK( "v = %d, smp = %d, freq = %d, vol = %d\n", voice, sample, (int)freq, volume );
#endif
  
  cmd_size = 10;
  command[ 0 ] = GUS_CMD_SAMPLE_START;
  command[ 1 ] = (unsigned char)voice;
  command[ 2 ] = (unsigned char)sample;
  command[ 3 ] = (unsigned char)(sample >> 8);
  command[ 4 ] = (unsigned char)freq;
  command[ 5 ] = (unsigned char)(freq >> 8);
  command[ 6 ] = (unsigned char)(freq >> 16);
  command[ 7 ] = (unsigned char)(freq >> 24);
  command[ 8 ] = (unsigned char)volume;
  command[ 9 ] = (unsigned char)(volume >> 8);
#if 0
  if ( do_gus_command() < 0 )
    {
      PRINTK( "gus_note_on: error\n" );
      return -EINVAL;
    }
   else
    PRINTK( "gus_note_on: end\n" );
  return 0;
#else
  return do_gus_command() < 0 ? -EINVAL : 0;
#endif
}

/*
 *
 */

static int gus_do_controller_event( short dev, short voice, short cmd, short value )
{
  unsigned int freq;

  if ( dev != SEQ_DEV_GF1 ) 
    return -ENXIO;
  if ( voice < 0 || voice > 31 ) 
    return 0;
  cmd_size = 0;
  switch ( cmd ) {
    case CTRL_PITCH_BENDER:
      freq = compute_finetune( seq_voices[ voice ].orig_freq, 
                               seq_voices[ voice ].bender = value,
                               seq_voices[ voice ].bender_range );
      cmd_size = 6;
      command[ 0 ] = GUS_CMD_VS_FREQUENCY;
      command[ 1 ] = voice;
      command[ 2 ] = (unsigned char)freq;
      command[ 3 ] = (unsigned char)( freq >> 8 );
      command[ 4 ] = (unsigned char)( freq >> 16 );
      command[ 5 ] = (unsigned char)( freq >> 24 );
      break;
    case CTRL_PITCH_BENDER_RANGE:
      seq_voices[ voice ].bender_range = value;
      break;
    case CTL_EXPRESSION:
      value /= 128;
    case CTRL_EXPRESSION:
      if ( gf1_vol_mode == GF1_VOL_MODE_ADAGIO )
        {
          seq_voices[ voice ].midi_expression_vol = value;
          volume_change( voice );
        }
      break;
    case CTL_PAN:
      seq_voices[ voice ].panning = ( value * 2 ) - 128;
      break;
    case CTL_MAIN_VOLUME:
      value = ( value * 100 ) / 16383;
    case CTRL_MAIN_VOLUME:
      seq_voices[ voice ].midi_main_vol = value;
      volume_change( voice );
      break;
    default:
      PRINTK( "gus_do_controller_event: cmd = 0x%x ???\n", cmd );
      return -EIO;
  }
#if 1
  if ( cmd_size > 0 && do_gus_command() < 0 )
    {
      PRINTK( "gus_do_controller_event: error\n" );
      return -EINVAL;
    }
#else
  if ( cmd_size > 0 )
    return do_gus_command() < 0 ? -EINVAL : 0;
#endif
  return 0;
}

static int gus_do_seq_voice_event( unsigned char *event )
{
  unsigned char dev = event[ 0 ];
  unsigned char cmd = event[ 1 ];
  unsigned char chn = event[ 2 ];
  unsigned char note = event[ 3 ];
  unsigned char parm = event[ 4 ];
  int voice = -1;
  
  if ( dev >= SEQ_DEV_CNT || seq_mode[ dev ] == SEQ_MODE_NONE )
    return -ENXIO;
  if ( chn > 31 )
    {
      PRINTK( "gus_do_seq_voice_event: chn = %d\n", chn );
      return -EINVAL;
    }
  cmd_size = 0;
  switch ( cmd ) {
    case MIDI_NOTEON:
      if ( note > 127 && note != 255 )
        return -EIO;
      voice = chn;
      seq_voices[ voice ].midi_volume = parm;
      gus_note_on( voice, note );
      return 0;
    case MIDI_NOTEOFF:
      cmd_size = 2;
      command[ 0 ] = GUS_CMD_VS_VOICE_STOP;
      command[ 1 ] = chn;
      break;
    case MIDI_KEY_PRESSURE:
      PRINTK( "MIDI_KEY_PRESSURE: unimplemented\n" );
      return -EIO;
    default:
      PRINTK( "gus_do_seq_voice_event: cmd = 0x%x\n", event[ 1 ] );
  }
#if 1
  if ( cmd_size > 0 && do_gus_command() < 0 )
    {
      PRINTK( "gus_do_controller_event: error\n" );
      return -EINVAL;
    }
#else
  if ( cmd_size > 0 )
    return do_gus_command() < 0 ? -EINVAL : 0;
#endif
  return 0;
}

static int gus_do_seq_common_event( unsigned char *event )
{
  unsigned char dev = event[ 0 ];
  unsigned char cmd = event[ 1 ];
  unsigned char chn = event[ 2 ];
  unsigned char p1 = event[ 3 ];
  /* unsigned char p2 = event[ 4 ]; */
  unsigned short w14 = *(short *)&event[ 5 ];

#if 0
  PRINTK( "common: cmd = 0x%x\n", cmd );
#endif
  if ( dev >= SEQ_DEV_CNT || seq_mode[ dev ] == SEQ_MODE_NONE )
    return -ENXIO;
  if ( chn > 31 )
    {
      PRINTK( "gus_do_seq_common_event: chn = %d\n", chn );
      return -EINVAL;
    }
  switch ( cmd ) {
    case MIDI_PGM_CHANGE:
#if 0
      PRINTK( "MIDI_PGM_CHANGE: chn %d = instr %d\n", chn, p1 );
#endif
      if ( p1 < GUS_MAX_SAMPLES && patch_map[ p1 ] >= 0 )
        seq_voices[ chn ].pgm = p1;
       else
        PRINTK( "gus_do_seq_common_event: bad instrument %d\n", p1 );
      break;
    case MIDI_CTL_CHANGE:
      if ( p1 == CTRL_MAIN_VOLUME )
        {
          w14 = (unsigned short)( ( (int)w14 * 16383 ) / 100 );
          p1 = CTL_MAIN_VOLUME;
        }
      if ( p1 == CTRL_EXPRESSION )
        {
          w14 *= 128;
          p1 = CTL_EXPRESSION;
        }
      return gus_do_controller_event( dev, chn, p1, w14 );
    case MIDI_PITCH_BEND:
      return gus_do_controller_event( dev, chn, CTRL_PITCH_BENDER, (short)w14 - 8192 );
    default:
      PRINTK( "gus_do_seq_common_event: event = 0x%x\n", cmd );
      return -EIO;
  }
  return 0;
}

static int gus_do_seq_timing_event( unsigned char *event )
{
  unsigned int arg = *(unsigned int *)&event[ 3 ];
  int i;

  switch ( event[ 0 ] ) {
    case TMR_WAIT_REL:
      arg += seq_abs_tick;
    case TMR_WAIT_ABS:
      i = arg - seq_abs_tick;
      seq_abs_tick = arg;
      if ( i > 0 )
        {
          cmd_size = 3;
          command[ 0 ] = GUS_CMD_WAIT;
          command[ 1 ] = (unsigned char)i;
          command[ 2 ] = (unsigned char)(i >> 8);
          if ( ( (int)arg = do_gus_command() ) < 0 )
            {
              PRINTK( "gus_do_seq_timing_event: GUS_CMD_WAIT error (%d)\n", (int)arg );
              return -EINVAL;
            }
        }
      break;
    case TMR_START:
      gus_ioctl_gf1( GUS_IOCTL_START_TIMER, TICK_US );
      break;
    case TMR_STOP:
      break;
    case TMR_CONTINUE:
      break;
    case TMR_TEMPO:
      break;
    case TMR_ECHO:
      if ( iqueue_size >= SEQ_IQUEUE_LEN )
        {
          PRINTK( "Warning!!! Input queue size overflow - TMR_ECHO\n" );
          return -EIO;
        }
       else
        {
          arg <<= 8;
          arg |= SEQ_ECHO;
          CLI();

          memcpy( &iqueue_event[ iqueue_head ], &arg, 4 );
          iqueue_time[ iqueue_head ] = seq_abs_tick;
          iqueue_head++; iqueue_head %= SEQ_IQUEUE_LEN;
          iqueue_size++;

#if 1		/* double it - probably bug in gmod 2.0.2 */
          memcpy( &iqueue_event[ iqueue_head ], &arg, 4 );
          iqueue_time[ iqueue_head ] = seq_abs_tick;
          iqueue_head++; iqueue_head %= SEQ_IQUEUE_LEN;
          iqueue_size++;
#endif

          STI();
        }
      break;
    default:
      PRINTK( "gus_do_seq_timing_event: cmd = 0x%x\n", event[ 0 ] );
      return -EIO;
  }
  return 0;
}

static int gus_do_seq_ext_event( unsigned char *event )
{
  unsigned char dev = event[ 1 ];

  if ( dev >= SEQ_DEV_CNT || seq_mode[ dev ] == SEQ_MODE_NONE )
    return -ENXIO;
  switch ( event[ 0 ] ) {
    case SEQ_PGMCHANGE:
      PRINTK( "MIDI_PGM_CHANGE: chn %d = instr %d\n", event[ 2 ], event[ 3 ] );
      if ( event[ 2 ] < 32 )
        if ( event[ 3 ] < GUS_MAX_SAMPLES && patch_map[ event[ 3 ] ] >= 0 )
          seq_voices[ event[ 2 ] ].pgm = event[ 2 ];
         else
          PRINTK( "gus_do_seq_ext_event: bad instrument %d\n", event[ 3 ] );
      return 0;
    case SEQ_CONTROLLER:
      return gus_do_controller_event( dev, 
                                      event[ 2 ], 
                                      event[ 3 ], 
                                      *(short *)&event[ 4 ] );
    case SEQ_VOLMODE:
      if ( event[ 2 ] == VOL_METHOD_LINEAR )
        gf1_vol_mode = GF1_VOL_MODE_LINEAR;
       else
      if ( event[ 2 ] == VOL_METHOD_ADAGIO )
        gf1_vol_mode = GF1_VOL_MODE_ADAGIO;
      return 0;
    default:
      PRINTK( "gus_do_seq_ext_event: cmd = 0x%x ???\n", event[ 0 ] );
  }
  return -EIO;
}

static int gus_do_seq_midiputc_event( unsigned char *event )
{
  PRINTK( "gus_do_seq_midiputc_event: UNIMPLEMENTED\n" );
  return -EIO;
}

static int gus_do_seq_private_event( unsigned char *event )
{
  unsigned char dev = event[ 0 ];

  if ( dev >= SEQ_DEV_CNT || seq_mode[ dev ] == SEQ_MODE_NONE )
    return -ENXIO;
  if ( dev == SEQ_DEV_GF1 )
    {
      unsigned char voice = event[ 2 ];
      unsigned short p1 = *(unsigned short *)&event[ 3 ];
      unsigned short p2 = *(unsigned short *)&event[ 5 ];
    
      cmd_size = 0;
#if 0
      PRINTK( "gus_do_seq_private_event: cmd = 0x%x\n", event[ 1 ] );
#endif
      switch ( event[ 1 ] ) {
        case _GUS_NUMVOICES:
          gus_select_active_voices( p1 );
          break;
        case _GUS_VOICESAMPLE:
          PRINTK( "_GUS_VOICESAMPLE: obsolete\n" );
          return -ENXIO;
        case _GUS_VOICEON:
          cmd_size = 2;
          command[ 0 ] = GUS_CMD_VOICE_SELECT;
          command[ 1 ] = voice;
          if ( do_gus_command() < 0 ) return -EINVAL;
          cmd_size = 1;
          command[ 0 ] = GUS_CMD_SAMPLE_START1;
          if ( do_gus_command() < 0 ) return -EINVAL;
          cmd_size = 2;
          command[ 0 ] = GUS_CMD_VOICE_CONTROL;
          command[ 1 ] = (unsigned char)p1 & ~0xa3;	/* unset IRQ bit */
          break;
        case _GUS_VOICEOFF:
          cmd_size = 3;
          command[ 0 ] = GUS_CMD_VS_VOICE_CONTROL;
          command[ 1 ] = voice;
          command[ 2 ] = 3;
          break;
        case _GUS_VOICEFADE:
          PRINTK( "_GUS_VOICEFADE: unimplemented\n" );
          break;
        case _GUS_VOICEMODE:
          cmd_size = 3;
          command[ 0 ] = GUS_CMD_VS_VOICE_CONTROL;
          command[ 1 ] = voice;
          command[ 2 ] = ( (unsigned char)p1 & ~0x23 ) | 0x80;
          break;
        case _GUS_VOICEBALA:
          cmd_size = 3;
          command[ 0 ] = GUS_CMD_VS_CURRENT_PAN;
          command[ 1 ] = voice;
          command[ 2 ] = (unsigned char)p1;
          break;
        case _GUS_VOICEFREQ:
          cmd_size = 6;
          command[ 0 ] = GUS_CMD_VS_FREQUENCY;
          command[ 1 ] = voice;
          command[ 2 ] = (unsigned char)p1;
          command[ 3 ] = (unsigned char)( p1 >> 8 );
          command[ 4 ] = (unsigned char)p2;
          command[ 5 ] = (unsigned char)( p2 >> 8 );
          break;
        case _GUS_VOICEVOL:
          cmd_size = 4;
          command[ 0 ] = GUS_CMD_VS_CURRENT_VOL;
          command[ 1 ] = voice;
          command[ 2 ] = (unsigned char)p1;
          command[ 3 ] = (unsigned char)(p1 >> 8);
          break;
        case _GUS_VOICEVOL2:
          break;
        case _GUS_RAMPRANGE:
          cmd_size = 3;
          command[ 0 ] = GUS_CMD_VS_RAMP_START;
          command[ 1 ] = voice;
          command[ 2 ] = (unsigned char)(p1 >> 4);
          if ( do_gus_command() < 0 ) return -EINVAL;
          cmd_size = 2;
          command[ 0 ] = GUS_CMD_RAMP_END;
          command[ 1 ] = (unsigned char)(p2 >> 4);
          break;
        case _GUS_RAMPRATE:
          cmd_size = 3;
          command[ 0 ] = GUS_CMD_VS_RAMP_RATE;
          command[ 1 ] = voice;
          command[ 2 ] = (unsigned char)( ( ( p1 & 3 ) << 6 ) | ( p2 & 0x3f ) );
          break;
        case _GUS_RAMPMODE:
        case _GUS_RAMPON:
          cmd_size = 3;
          command[ 0 ] = GUS_CMD_VS_RAMP_CONTROL;
          command[ 1 ] = voice;
          command[ 2 ] = ( (unsigned char)p1 & ~0xa3 ) | 
                         ( event[ 1 ] == _GUS_RAMPMODE ? 0x80 : 0 );
          break;
        case _GUS_RAMPOFF:
          cmd_size = 3;
          command[ 0 ] = GUS_CMD_VS_RAMP_CONTROL;
          command[ 1 ] = voice;
          command[ 2 ] = 3;
          break;
        case _GUS_VOLUME_SCALE:
          volume_base = p1;
          volume_scale = p2;
          return 0;
        case _GUS_VOICE_POS:
          cmd_size = 6;
          command[ 0 ] = GUS_CMD_VS_CURRENT_LOC;
          command[ 1 ] = voice;
          command[ 2 ] = (unsigned char)p1;
          command[ 3 ] = (unsigned char)( p1 >> 8 );
          command[ 4 ] = (unsigned char)p2;
          command[ 5 ] = (unsigned char)( p2 >> 8 );
          break;
        default:
          PRINTK( "gus_do_seq_private_event: cmd = 0x%x ???\n", event[ 1 ] );
      }
#if 1
      if ( cmd_size > 0 && do_gus_command() < 0 )
        {
          PRINTK( "gus_do_controller_event: error\n" );
          return -EINVAL;
        }
#else
      if ( cmd_size > 0 )
        return do_gus_command() < 0 ? -EINVAL : 0;
#endif
      return 0;
    }
   else
    PRINTK( "gus_do_seq_private_event: MIDI\n" );
  return -EIO;
}

static int gus_do_seq_event( unsigned char *event )
{
  switch ( event[ 0 ] ) {
    case EV_CHN_VOICE:	return gus_do_seq_voice_event( event + 1 );
    case EV_CHN_COMMON:	return gus_do_seq_common_event( event + 1 );
    case EV_TIMING:	return gus_do_seq_timing_event( event + 1 );
    case SEQ_EXTENDED:	return gus_do_seq_ext_event( event + 1 );
    case SEQ_MIDIPUTC:	return gus_do_seq_midiputc_event( event + 1 );
    case SEQ_PRIVATE:	return gus_do_seq_private_event( event + 1 );
  }
  PRINTK( "gus_do_seq_event: bad event code 0x%x\n", event[ 0 ] );
  return -ENXIO;
}

/*
 *
 */

int gus_lseek_sequencer( short minor, off_t offset, int orig )
{
  return -EIO;
}

int gus_read_sequencer( short minor, char *buf, int count )
{
  int cnt = count;
  int event_size = 4;

  while ( cnt >= event_size && iqueue_size > 0 && 
          iqueue_time[ iqueue_tail ] <= gf1_abs_tick )
    {
      CLI();
      MEMCPY_TOFS( buf, &iqueue_event[ iqueue_tail ], event_size );
      iqueue_size--;
      iqueue_tail++; iqueue_tail %= SEQ_IQUEUE_LEN;
      STI();
      buf += event_size;
      cnt -= event_size;
    }
  return count - cnt;
}

int gus_write_sequencer( short minor, char *buf, int count )
{
  unsigned char event[ EVENT_SIZE ];
  unsigned char event_code, event_size;
  unsigned short dev;
  int cnt, result;
  
  cnt = count;
  while ( cnt >= 4 )
    {
      MEMCPY_FROMFS( event, buf, 4 );
      event_code = event[ 0 ];
      if ( event_code == SEQ_FULLSIZE )
        {
          dev = *(unsigned short *)&event[ 2 ];
          if ( dev >= SEQ_DEV_CNT || seq_mode[ dev ] == SEQ_MODE_NONE )
            return -ENXIO;
          result = -EIO;
          if ( dev == SEQ_DEV_GF1 )
            result = gus_patch_load( (struct patch_info *)buf );
          return result < 0 ? result : count;
        }
      if ( event_code >= 128 )
        {
          event_size = 8;
          if ( cnt < event_size )
            return count - cnt;
          MEMCPY_FROMFS( &event[ 4 ], buf + 4, 4 );
        }
       else
        event_size = 4;
      cnt -= event_size;
      buf += event_size;
      if ( ( result = gus_do_seq_event( event ) ) < 0 )
        return result;
    }
  return count;
}

int gus_seq_reset( void )
{
  gus_ioctl_gf1( GUS_IOCTL_QABORT, 0 );
  gf1_abort_flag = 0;
  iqueue_size = 0;
  iqueue_head = iqueue_tail = 0;
  seq_abs_tick = 0;
  gus_reset_seq_voices();
#if 0
  gus_ioctl_gf1( GUS_IOCTL_START_TIMER, TICK_US );
#endif
  return 0;
}

int gus_open_sequencer( short minor, unsigned short f_flags )
{
  int i;

  if ( minor == GUS_MINOR_SEQUENCER2 )
    {
      PRINTK( "gus_open_sequencer: /dev/sequencer2 isn't yet implemented\n" );
      return -EIO;
    }
  if ( seq_mode[ SEQ_DEV_GF1 ] != SEQ_MODE_NONE )
    return -EBUSY;
  if ( ( i = gus_open_gf1() ) != 0 )
    return i;
  if ( ( i = gus_ioctl_gf1( GUS_IOCTL_RESET, 24 ) ) != 0 )
    {
      gus_release_gf1();
      return i;
    }
  gus_ioctl_gf1( GUS_IOCTL_VOL_TYPE, GUS_VOL_TYPE_GF1 );
  gus_ioctl_gf1( GUS_IOCTL_FREQ_TYPE, GUS_FREQ_TYPE_HZ );
  volume_base = 3071;
  volume_scale = 4;
  gf1_vol_mode = GF1_VOL_MODE_ADAGIO;
  curr_timebase = HZ;
  curr_tempo = 60;
  seq_mode[ SEQ_DEV_GF1 ] = SEQ_MODE_1;
  iqueue_size = 0;
  iqueue_head = iqueue_tail = 0;
  seq_abs_tick = 0;
  gus_reset_seq_voices();
  MOD_INC_USE_COUNT;
  return 0;		/* success */
}

void gus_release_sequencer( short minor, unsigned short f_flags )
{
  seq_mode[ SEQ_DEV_GF1 ] = SEQ_MODE_NONE;
  gus_release_gf1();
  MOD_DEC_USE_COUNT;
}

int gus_select_sequencer( short minor, unsigned short f_flags )
{
  return -EIO;
}

int gus_ioctl_sequencer( short minor, unsigned int cmd, unsigned long arg )
{
  switch ( cmd ) {
    case SNDCTL_SEQ_RESET:
      return gus_seq_reset();
    case SNDCTL_SEQ_SYNC:	
      gus_ioctl_gf1( GUS_IOCTL_FLUSH, 0 );
      return 0;
    case SNDCTL_SEQ_RESETSAMPLES: 
      if ( IOCTL_IN( arg ) != 0 )
        return -ENXIO;
       else
        {
          gus_reset_patches();
          gus_ioctl_gf1( GUS_IOCTL_RESET0, 0 );
        }
      return 0;
    case SNDCTL_SEQ_NRSYNTHS:	
      return IOCTL_OUT( arg, SEQ_DEV_CNT );
    case SNDCTL_SYNTH_MEMAVL:
      {
        int device = IOCTL_IN( arg );
        
        if ( device >= 0 && device < SEQ_DEV_CNT )
          return IOCTL_OUT( arg, device == SEQ_DEV_GF1 ? gus_mem_free() : 0x7fffffff );
        return -ENXIO;
      }
    case SNDCTL_SYNTH_INFO:
      {
        struct synth_info inf;
        
        MEMCPY_FROMFS( &inf, (void *)arg, sizeof( inf ) );
        if ( inf.device >= 0 && inf.device < SEQ_DEV_CNT )
          {
            MEMCPY_TOFS( (void *)arg, inf.device == SEQ_DEV_GF1 ? 
       		&gus_info_vox : &midi_info_vox, sizeof( struct synth_info ) );
          }
         else
          return -ENXIO;
        return 0;
      }
    
    case SNDCTL_TMR_SOURCE:
      return IOCTL_OUT( arg, TMR_INTERNAL );
    case SNDCTL_TMR_START:
      return gus_ioctl_gf1( GUS_IOCTL_START_TIMER, TICK_US );
    case SNDCTL_TMR_STOP:
      return gus_ioctl_gf1( GUS_IOCTL_STOP_TIMER, 0 );
    case SNDCTL_TMR_CONTINUE:
      PRINTK( "SNDCTL_TMR_CONTINUE: unimplemented\n" );
      return -EIO;
    case SNDCTL_TMR_TIMEBASE:
      {
        int val = IOCTL_IN( arg );
        
        if ( val )
          {
            if ( val < 1 ) val = 1;
            if ( val > 1000 ) val = 1000;
            curr_timebase = val;
          }
        return IOCTL_OUT( arg, curr_timebase );
      }
    case SNDCTL_TMR_TEMPO:
      {
        int val = IOCTL_IN( arg );
        
        if ( val )
          {
            if ( val < 8 ) val = 8;
            if ( val > 250 ) val = 250;
            curr_tempo = val;
          }
        return IOCTL_OUT( arg, curr_tempo );
      }
    case SNDCTL_SEQ_CTRLRATE:
      if ( IOCTL_IN( arg ) != 0 )
        return -EINVAL;
      return IOCTL_OUT( arg, ( ( curr_tempo * curr_timebase ) + 30 ) / 60 );
    case SNDCTL_TMR_METRONOME:
      return -EINVAL;
  }
#if 1
  PRINTK( "gus_ioctl_sequencer: bad ioctl? (0x%x/0x%x)\n", cmd, (int)arg );
#endif
  return -EIO;
}

#endif /* VOXWARE_SEQUENCER */
