/*
 *  Copyright (c) 1994/95 by Jaroslav Kysela (Perex soft)
 *  Routines for control of GF1 chip (synth things)
 */

#include "gus_dev.h"

#define QUEUE_LEN		64
#define QUEUE_MASK		0x3f
#define SYNTH_WAKEUP_LIMIT	( QUEUE_LEN - ( QUEUE_LEN / 3 ) )

#define SMOOTH_PAN_VALUE	16

#define NEW_VOICE_CNTRL		0x0001
#define NEW_FREQUENCY		0x0002
#define NEW_LOOP_START		0x0004
#define NEW_LOOP_END		0x0008
#define NEW_RAMP_RATE		0x0010
#define NEW_RAMP_START		0x0020
#define NEW_RAMP_END		0x0040
#define NEW_CURRENT_VOLUME	0x0080
#define NEW_CURRENT_ADDRESS	0x0100
#define NEW_PAN_POSITION	0x0200
#define NEW_RAMP_CNTRL		0x0400

#define NEW_SAMPLE_START	0x1000

struct gus_voice {
  unsigned short what_is_new;		/* see NEW_XXXX constants */

  unsigned char voice_cntrl;
  unsigned short frequency;
  unsigned int loop_start;
  unsigned int loop_end;
  unsigned char ramp_rate;
  unsigned char ramp_start;
  unsigned char ramp_end;
  unsigned short current_volume;
  unsigned int current_address;
  unsigned char pan_position;
  unsigned char ramp_cntrl;
};

typedef struct gus_voice gus_voices_type [ QUEUE_LEN ][ 32 ];

static gus_voices_type *gus_voices = NULL;	/* 64x32 voices in queue */
#if 0
static struct gus_voice irq_voices_wave[ 32 ];
#endif
static struct gus_voice irq_voices_ramp[ 32 ];
static unsigned char irq_voices_goal_pan[ 32 ];

static volatile short voices_used;		/* 0 - QUEUE_LEN */
static volatile short voices_flush;
static volatile short voices_pos_head;
static volatile short voices_pos_tail;
static short active_voice;
static short active_sample;
static short wait_count;
static short wait_count_next[ QUEUE_LEN ];
static short active_timer;	/* -1 (nothing), 0 (timer1) or 1 (timer2) */
static short new_timer_counts[ QUEUE_LEN ];
static short new_timer_count;
static short current_timer_count = -1;
static short current_timer_minus = 0;
static short smooth_flag = 0;
static short smooth_skip = 0;
static short smooth_pan_value = SMOOTH_PAN_VALUE;

short gf1_abort_flag;

unsigned char command[ 32 ];
short cmd_size;
static short cmd_size_need;
static short cmd_curr_pos;

unsigned long gf1_abs_tick;
static int gus_irq_error;

SLEEP_PREPARE( synth )

short gf1_mode = GF1_MODE_NONE;
short gf1_volume_mode = GUS_VOL_TYPE_GF1;
short gf1_freq_mode = GUS_FREQ_TYPE_GF1;

static int gus_timer1_start( short time )	/* 80 us */
{
  if ( active_timer == 1 ) return -EBUSY;	/* timer 2 is active */
  if ( time < 1 || time > 256 ) return -EINVAL;
  active_timer = 0;
  new_timer_count = current_timer_count = current_timer_minus = time;
#if 0
  PRINTK( "gus_timer2_start: time = %d\n", time );
#endif
  CLI();
  gus_write8( 0x45, 0 );
  gus_write8( 0x46, 256 - time );		/* timer 2 count */
  gus_write8( 0x45, 4 );			/* enable timer 2 IRQ */
  gus_adlib_write( 0x04, 1 );			/* timer 2 start */
  STI();
  return 0;
}

static int gus_timer1_stop( void )
{
  if ( active_timer != 0 ) return -EBUSY;
#if 0
  PRINTK( "gus_timer1_stop\n" );
#endif
  CLI();
  gus_write8( 0x45, 0 );			/* disable timers */
  STI();
  active_timer = -1;
  return 0;
}

static int gus_timer2_start( short time )	/* 320 us */
{
  if ( active_timer == 0 ) return -EBUSY;	/* timer 1 is active */
  if ( time < 1 || time > 256 ) return -EINVAL;
  active_timer = 1;
  new_timer_count = current_timer_count = current_timer_minus = time << 2; 
  						/* extend to 80us */
#if 0
  PRINTK( "gus_timer2_start: time = %d\n", time );
#endif
  CLI();
  gus_write8( 0x45, 0 );
  gus_write8( 0x47, 256 - time );		/* timer 2 count */
  gus_write8( 0x45, 8 );			/* enable timer 2 IRQ */
  gus_adlib_write( 0x04, 2 );			/* timer 2 start */
  STI();
  return 0;
}

static int gus_timer2_stop( void )
{
  if ( active_timer != 1 ) return -EBUSY;
#if 0
  PRINTK( "gus_timer2_stop\n" );
#endif
  CLI();
  gus_write8( 0x45, 0 );			/* disable timers */
  STI();
  active_timer = -1;
  return 0;
}

static int gus_timer_start( int time )		/* one us */
{
  if ( time <= 0 || time > 256L * 320L ) return -EINVAL;
  if ( time < 2000 ) time = 2000;
  if ( time > 256 * 80 )
    return gus_timer2_start( ( time + 160L ) / 320L );
  return gus_timer1_start( ( time + 40L ) / 80L );
}

static int gus_timer_stop( void )
{
  if ( active_timer == 1 )
    return gus_timer2_stop();
  return gus_timer1_stop();
}

static void gus_start_smooth_pan( void )
{
  if ( !smooth_pan_value || smooth_flag ) return;	  /* nothing to do */
  smooth_flag = 1;
  smooth_skip = 0;
  if ( current_timer_count <= smooth_pan_value ) return;  /* nothing to do */
  if ( current_timer_minus < smooth_pan_value )
    smooth_skip = 1;
   else
    {
#if 0
      printk( "smooth start\n" );
#endif
      CLI();
#if 0
      gus_write8( 0x45, 0 );				  /* disable timers */
      gus_adlib_write( 0x04, 0x80 );		  	  /* reset both timers */
#endif
      current_timer_minus = smooth_pan_value;
      gus_write8( 0x46, 256 - current_timer_minus );
      gus_write8( 0x45, 4 );				  /* enable timer 1 */
      gus_adlib_write( 0x04, 1 );			  /* timer 1 start */
      STI();
    }
}

void gus_set_voice( int voice, struct gus_voice *gv )
{
  unsigned short wnew = gv -> what_is_new;
  short ramp_ok = 0, w_16bit;
  unsigned char rstart, rend, ramp = 0, vctrl = 0;

  gv -> what_is_new = 0;

  CLI();

  if ( wnew & NEW_SAMPLE_START )
    {
#if 0
      PRINTK( "volume (0): 0x%x %s\n", gus_read16( 9 ), voice & 0x80 ? "[ramp]" : "[new]" );
#endif
      gus_write16( 9, gf1_min_ramp_16 );
      gus_write_addr( 0x0a, 0, 0 );
      gus_write8( 0, 3 );
      gus_write8( 0x0d, 3 );
      if ( gv -> current_volume > gf1_min_ramp_16 ) 
        {
          rstart = 0; rend = gv -> current_volume >> 8;
          if ( ( ramp_ok = gus_ramp_ok( &rstart, &rend ) ) != 0 )
            {
#if 0
              gus_write16( 9, gf1_min_ramp_16 );
#endif
              wnew &= ~NEW_CURRENT_VOLUME;
              if ( ( voice & 0x80 ) == 0 )
	        {
                  irq_voices_ramp[ voice ].what_is_new = NEW_CURRENT_VOLUME;
	          irq_voices_ramp[ voice ].current_volume = gv -> current_volume;
	        }
	       else
	        gv -> what_is_new = NEW_CURRENT_VOLUME;
	    }
	}
    }

#if 0
  if ( ( voice & 0x1f ) == 3 )
    PRINTK( "voice %d, wnew = 0x%x, cntrl = 0x%x, vol = 0x%x\n", voice, wnew, gv -> voice_cntrl, gv -> current_volume );
#endif
  w_16bit = gv -> voice_cntrl & 4;
  if ( wnew & NEW_PAN_POSITION )
    {
      unsigned char new_pan, old_pan;
      
      new_pan = old_pan = gv -> pan_position;
      if ( !( wnew & NEW_SAMPLE_START ) && !( gus_read8( 0 ) & 1 ) )
        {
          old_pan = gus_read8( 0x0c ) & 0x0f;
          if ( old_pan > new_pan && old_pan - new_pan > 1 )
            {
              irq_voices_goal_pan[ voice & 0x1f ] = new_pan | 0x80;
              old_pan--;
              gus_start_smooth_pan();
            }
           else
          if ( new_pan > old_pan && new_pan - old_pan > 1 )
            {
              irq_voices_goal_pan[ voice & 0x1f ] = new_pan | 0x80;
              old_pan++;
              gus_start_smooth_pan();
            }
           else
            irq_voices_goal_pan[ voice & 0x1f ] &= ~0x80;
        }
       else
        irq_voices_goal_pan[ voice & 0x1f ] &= ~0x80;
      gus_write8( 0x0c, old_pan );
    }
  if ( wnew & NEW_LOOP_START )
    gus_write_addr( 2, gv -> loop_start, w_16bit );
  if ( wnew & NEW_LOOP_END )
    gus_write_addr( 4, gv -> loop_end, w_16bit );
  if ( wnew & NEW_RAMP_RATE )
    gus_write8( 6, gv -> ramp_rate );
  if ( wnew & NEW_RAMP_START )
    gus_write8( 7, gv -> ramp_start );
  if ( wnew & NEW_RAMP_END )
    gus_write8( 8, gv -> ramp_end );
  if ( wnew & NEW_FREQUENCY )
    gus_write16( 1, gv -> frequency );
  if ( wnew & NEW_CURRENT_VOLUME )
    gus_write16( 9, gv -> current_volume );
  if ( wnew & NEW_RAMP_CNTRL )
    {
      ramp = gv -> ramp_cntrl;
    
      if ( ramp & 0x80 )
        {
          ramp &= ~0x83;
          ramp |= gus_read8( 0x0d ) & 0x03;
        }
      gus_write8( 0x0d, ramp );
    }
  if ( wnew & NEW_CURRENT_ADDRESS )
    gus_write_addr( 0x0a, gv -> current_address, w_16bit );
  if ( wnew & NEW_VOICE_CNTRL )
    {
      vctrl = gv -> voice_cntrl;
      
      if ( vctrl & 0x80 )
        {
          vctrl &= ~0x83;
          vctrl |= gus_read8( 0 ) & 0x03;
        }
      gus_write8( 0, vctrl );
    }
  gus_delay();
  if ( wnew & NEW_VOICE_CNTRL )
    gus_write8( 0, vctrl );
  if ( wnew & NEW_CURRENT_VOLUME )
    gus_write16( 9, gv -> current_volume );
  if ( wnew & NEW_RAMP_CNTRL )
    gus_write8( 0x0d, ramp );
  STI();

  if ( ramp_ok == 1 )
    gus_set_ramp( rstart, rend, 0x80 | 0x20 );
}

void gf1_synth_interrupt( unsigned char status )
{
  static in_interrupt = 0;
  unsigned short what = 0;
  	/* 0 - nothing, 1 - smooth only, 2 - new tick only, 3 - both */
  int i, running;
  unsigned short tmp;
 
#if 0
  PRINTK( "----------------------->>>>>>>> IRQ status 0x%x\n", status );
#endif

#if 0
  OUTB( 0, GUSP( GF1PAGE ) );
  printk( "v0: cntrl = 0x%x, current = 0x%x, vol = 0x%x, freq = 0x%x\n",
  	gus_read8( 0 ),
  	gus_read_addr( 0x0a, 0 ),
  	gus_read16( 9 ),
  	gus_read16( 1 ) );
#endif

  if ( in_interrupt )
    PRINTK( "gus: Eiaaa, interrupt routine reentered - %d times (synth)!!!\n", in_interrupt );
  in_interrupt++;

interrupt_start:

  if ( status & 0x80 )
    {  
#if 0
      PRINTK( "GF1: DMA IRQ\n" );
#endif
      dma1_lock |= WK_WAKEUP;
      WAKEUP( dma1 );
    }

  if ( ( tmp = ( status & ( 8 | 4 ) ) ) != 0 )
    {
      current_timer_count -= running = current_timer_minus;
#if 0
      printk( "current_timer_count = %d, current_timer_minus = %d\n", current_timer_count, current_timer_minus );
#endif
      if ( current_timer_count < 0 )
        printk( "gus: Warning! current_timer_count < 0\n" );
      if ( current_timer_count <= 0 )
        {
          current_timer_count = current_timer_minus = new_timer_count;
          what |= 2;
        }
       else
        current_timer_minus = current_timer_count;
      if ( smooth_flag )
        {
          if ( !smooth_skip ) what |= 1; else smooth_skip = 0;

#if 0
          printk( "timer (smooth active): " );
#endif
          if ( current_timer_minus > smooth_pan_value )
            {
              if ( current_timer_count >= smooth_pan_value )
                {
#if 0
                  printk( "set" );
#endif
                  current_timer_minus = smooth_pan_value;
                }
               else
                {
#if 0
                  printk( "skip (%d only)", current_timer_minus );
#endif
                  smooth_skip = 1;
                }
            }
#if 0
          printk( " minus=%d\n", current_timer_minus );
#endif
        }
      CLI();
      gus_write8( 0x45, 0 );
      gus_adlib_write( 0x04, 0x80 );			/* reset timers */
      if ( current_timer_minus == running )
        gus_write8( 0x45, tmp );
       else
        if ( new_timer_count >= 256 )			/* forced timer 2 */
          {
            gus_write8( 0x47, 256 - ( current_timer_minus >> 2 ) );
            if ( tmp == 4 ) gus_adlib_write( 0x04, 2 );	/* timer 2 start */
            gus_write8( 0x45, 8 );			/* enable timer 2 */
          }
         else
          {
            gus_write8( 0x46, 256 - current_timer_minus );
            if ( tmp == 8 ) gus_adlib_write( 0x04, 1 );	/* timer 1 start */
            gus_write8( 0x45, 4 );			/* enable timer 1 */
          }
      STI();
#if 0
      printk( "tmp = 0x%x, status = 0x%x\n", tmp, status );
#endif
#if 0
      printk( "setup: current_timer_count = %d, current_timer_minus = %d\n", current_timer_count, current_timer_minus );
#endif
    }

  i = 0;
  if ( ( status & ( 0x40 | 0x20 ) ) != 0 )
    while ( ( ( tmp = gus_read8( 0x0f ) ) & 0xc0 ) != 0xc0 )
      {
        running = tmp & 0x1f;
        if ( ( i >> running ) & 1 ) continue; 	/* double request */
        i |= 1 << running;
        if ( ( tmp & 0x40 ) == 0 )		/* volume ramp IRQ */
          {
#if 0
/*            if ( ( gus_read8( 0x0d ) & 1 ) == 0 ) */
              PRINTK( "ch: %i, win: 0x%x, vol: 0x%x, rs: 0x%x, re: 0x%x, rr: 0x%x, rc: 0x%x\n",
                running, irq_voices_ramp[ running ].what_is_new , gus_read16( 9 ), gus_read8( 7 ), 
                gus_read8( 8 ), gus_read8( 6 ), gus_read8( 0x0d ) );
#endif
            CLI();
            OUTB( running, GUSP( GF1PAGE ) );
            if ( ( gus_read8( 0x0d ) & 1 ) == 1 )	/* another test */
              {
                gus_write8( 0x0d, 3 );
                STI();
                gus_set_voice( 0x80 | running, &irq_voices_ramp[ running ] );
              }
             else
              {
                STI();
              }
          }
        if ( ( tmp & 0x80 ) == 0 )		/* wave IRQ */
          {
            CLI();
            OUTB( running, GUSP( GF1PAGE ) );
            gus_write8( 0, gus_read8( 0 ) & ~0x20 ); 
            STI();
          }
      }

  tmp = INB( GUSP( IRQSTAT ) );
  if ( tmp != status )
    {
#if 0
      if ( tmp & ~status )
        printk( "go to interrupt start, old status = 0x%x, new status = 0x%x\n", status, tmp & ~status );
#endif
      if ( ( status = tmp & ~status ) != 0 ) goto interrupt_start;
    }
    
  if ( what & 1 )
    {
      for ( i = 0; i < gus_active_voices; i++ )
        if ( ( running = irq_voices_goal_pan[ i ] ) & 0x80 )
          {
            CLI();
            OUTB( i, GUSP( GF1PAGE ) );
            tmp = gus_read8( 0x0c ) & 0x0f;
#if 0
	    printk( "voice %i, old_pan = %i, new_pan = %i, new_pana = 0x%x\n", i, tmp, running & 0x0f, running );
#endif
            running &= 0x0f;			/* goal pan */
            if ( tmp > running )
              gus_write8( 0x0c, --tmp );
             else
            if ( tmp < running )
              gus_write8( 0x0c, ++tmp );
            if ( tmp == running )
              irq_voices_goal_pan[ i ] = 0;
             else
              what |= 4;
            STI();
          }
      if ( !(what & 4) ) smooth_flag = 0;	/* smooth pan done for all voices */
    }

  tmp = INB( GUSP( IRQSTAT ) );
  if ( tmp != status )
    {
#if 0
      if ( tmp & ~status )
        printk( "go to interrupt start, old status = 0x%x, new status = 0x%x\n", status, tmp & ~status );
#endif
      if ( ( status = tmp & ~status ) != 0 ) goto interrupt_start;
    }
    
  if ( what & 2 )
    {
      for ( i = 0; i < gus_active_voices; i++ )
        if ( ( running = irq_voices_goal_pan[ i ] ) & 0x80 )
          {
            CLI();
            OUTB( i, GUSP( GF1PAGE ) );
            if ( gus_read8( 0 ) & 1 )		/* stopped? */
              irq_voices_goal_pan[ i ] = 0;
             else
              {
                tmp = gus_read8( 0x0c ) & 0x0f;
#if 0
	        printk( "voice %i, old_pan = %i, new_pan = %i, new_pana = 0x%x\n", i, tmp, running & 0x0f, running );
#endif
                running &= 0x0f;			/* goal pan */
                if ( tmp > running )
                  gus_write8( 0x0c, --tmp );
                 else
                if ( tmp < running )
                  gus_write8( 0x0c, ++tmp );
                if ( tmp == running )
                  irq_voices_goal_pan[ i ] = 0;
              }
            STI();
          }

      if ( wait_count > 0 )
        {
          gf1_abs_tick++;
          wait_count--;
          in_interrupt--;
          return;
        }
       else
        {
          wait_count = wait_count_next[ voices_pos_head ];
          if ( wait_count <= 0 ) 
            {
              in_interrupt--;
              return;
            }
          gf1_abs_tick++;
          wait_count--;
        }

      if ( voices_used > 0 )
        {
          for ( i = 0; i < gus_active_voices; i++ )
            {
	      struct gus_voice *gv = &(*gus_voices)[ voices_pos_head ][ i ];

	      if ( irq_voices_ramp[ i ].what_is_new )
	        {
#if 0
	          PRINTK( "gus: warning - irq_voices_ramp[ %d ].what_is_new = 0x%x\n", 
	                  i, irq_voices_ramp[ i ].what_is_new );
#endif
		  irq_voices_ramp[ i ].what_is_new = 0;
		  gus_irq_error = -EBADRAMP;
		  continue;
	        }

	      if ( gv -> what_is_new == 0 ) continue;

	      OUTB( i, GUSP( GF1PAGE ) );
	      
              running = !( gus_read8( 0 ) & 1 );
              
	      if ( ( gv -> what_is_new & NEW_SAMPLE_START ) && running )
	        {
		  gus_write8( 0x0d, 3 );	/* stop ramp */
	          tmp = gus_read16( 9 );
		  if ( gus_set_ramp( MIN_RAMP_RANGE_8, tmp >> 8, 0x40 | 0x20 ) )
		    {
	              MEMCPY( &irq_voices_ramp[ i ], gv, sizeof( *gv ) );
		      gv -> what_is_new = 0;
		      continue;
		    }
	        }
	       else
	      if ( ( gv -> what_is_new & NEW_CURRENT_VOLUME ) && running )
	        {
		  gus_write8( 0x0d, 3 );	/* stop ramp */
	          tmp = gus_read16( 9 );
                  if ( gv -> current_volume > tmp )
	            {
	              if ( gus_set_ramp( tmp >> 8, gv -> current_volume >> 8, 0x20 ) )
	                {
	                  MEMCPY( &irq_voices_ramp[ i ], gv, sizeof( *gv ) );
	                  gv -> what_is_new = 0;
	                  continue;
	                }
	            }
	           else
	            {
	              if ( gus_set_ramp( gv -> current_volume >> 8, tmp >> 8, 0x40 | 0x20 ) )
	                {
	                  MEMCPY( &irq_voices_ramp[ i ], gv, sizeof( *gv ) );
	                  gv -> what_is_new = 0;
	                  continue;
	                }
	            }
	        }
	      gus_set_voice( i, gv );
            }
          if ( new_timer_counts[ voices_pos_head ] >= 0 )
            {
              new_timer_count = new_timer_counts[ voices_pos_head ];
              new_timer_counts[ voices_pos_head ] = -1;
            }
          voices_pos_head++;
          voices_pos_head &= QUEUE_MASK;
          voices_used--;
          if ( ( voices_used < SYNTH_WAKEUP_LIMIT && !voices_flush ) ||
               ( voices_used == 0 && voices_flush ) )
            {
              WAKEUP( synth );
            }
        }
    }
    
  in_interrupt--;
}

static void gus_partly_init( void )
{
  int i, j;

  for ( i = 0; i < QUEUE_LEN; i++ )
    {
      for ( j = 0; j < 32; j++ )
        (*gus_voices)[ i ][ j ].what_is_new = 0;
      new_timer_counts[ i ] = -1;
      wait_count_next[ i ] = 0;
    }
  for ( i = 0; i < 32; i++ )
    {
      irq_voices_ramp[ i ].what_is_new = 0;
#if 0
      irq_voices_wave[ i ].what_is_new = 0;
#endif
      irq_voices_goal_pan[ i ] = 0;
    }
  voices_used = 0;
  voices_flush = 0;
  voices_pos_head = voices_pos_tail = 0;
  active_voice = active_sample = 0;
}

static void gf1_do_abort( int reset )
{
  short i, ramp_ok;
  unsigned char ramp_end;

  gf1_abort_flag = 1;
  gus_partly_init();
  if ( reset ) 
    { 
      gf1_abort_flag = 2;
      for ( i = 0, ramp_ok = 0; i < 32; i++ )
        {
          CLI();
          OUTB( i, GUSP( GF1PAGE ) );
          ramp_end = gus_read16( 9 ) >> 8;
          if ( ramp_end > gf1_min_ramp_8 )
            {
              ramp_ok++;
              gus_write8( 6, 20 );		/* ramp rate */
              gus_write8( 7, gf1_min_ramp_8 );	/* ramp start */
              gus_write8( 8, ramp_end );	/* ramp end */
              gus_write8( 0x0d, 0x40 );		/* ramp down */
              gus_delay();
              gus_write8( 0x0d, 0x40 );
            }
          STI();
        }
      if ( ramp_ok )
        {
          SLEEP( dma1, 2 );
        }
      for ( i = 0; i < 32; i++ )
        {
          CLI();
          OUTB( i, GUSP( GF1PAGE ) );
          gus_write8( 0, 3 );			/* voice off */
          gus_write8( 0x0d, 3 );		/* ramp off */
          gus_write_addr( 0x0a, 0, 0 );
          gus_write16( 9, 0 );
          STI();
        }
    }
   else
    gus_stop();
}

static void gus_voices_init( void )
{
  gus_partly_init();
  wait_count = 0;
  gf1_abs_tick = 0L;
  gus_irq_error = 0;
  smooth_flag = 0;
  smooth_pan_value = SMOOTH_PAN_VALUE;
  gf1_ramp_auto = GF1_RAMP_AUTO;
}

static inline int gus_voice_select( unsigned char voice )
{
  active_voice = voice;
  if ( active_voice < 0 || active_voice > 31 )
    {
#if 0
      PRINTK( "gus: voice out of range (%d)\n", active_voice );
#endif
      active_voice = 31;
      return 1;
    }
  return 0;
}

static inline unsigned short translate_volume( unsigned short volume )
{
  switch ( gf1_volume_mode ) {
    case GUS_VOL_TYPE_LINEAR: 
#if 0
      printk( "volume = %d (%d)\n", volume, volume / 2 );
#endif
      if ( volume > 128 ) volume = 128;
      return lvol_to_gf1_vol( volume );
    default: 
      return volume;
  }
}

static inline unsigned short translate_freq( unsigned int freq )
{
  switch ( gf1_freq_mode ) {
    case GUS_FREQ_TYPE_HZ2:
      return ( ( ( freq << 9 ) + ( gf1_playback_freq >> 1 ) ) / gf1_playback_freq ) & ~1;
    case GUS_FREQ_TYPE_HZ:
      return ( ( ( freq << 9 ) + ( gf1_playback_freq >> 1 ) ) / gf1_playback_freq ) << 1;
    default: 
      return freq;
  }
}

int do_gus_command( void )
{
#define WNEW( x ) 	gv -> what_is_new |= NEW_##x
#define WNEW1( x )	gv -> what_is_new |= ( x )
#define GET_BYTE()	( *cmd++ )
#ifdef __i386__
#define GET_WORD()	( *((unsigned short *)cmd)++ )
#define GET_DWORD()     ( *((unsigned int *)cmd)++ )
#else
#error Byte order problem!!!
#endif
      
  struct gus_voice *gv = NULL;
  struct GUS_STRU_SAMPLE *gs;
  char *cmd = command;
  unsigned char cmd_byte = *cmd++;
  int tmp;

#if 0
  if ( cmd_byte < 0x50 )
    printk( "cmd=0x%x, size=%d\n", cmd_byte, cmd_size );
#endif

  if ( cmd_byte >= 0x20 && cmd_byte < 0x30 )
    {
      if ( gus_voice_select( GET_BYTE() ) ) return -EBADVOICE;
      cmd_byte -= 0x10;
    }
  if ( cmd_byte >= 0x10 && cmd_byte < 0x20 )
    gv = &(*gus_voices)[ voices_pos_tail ][ active_voice ];

  switch ( cmd_byte ) {
    case GUS_CMD_VOICE_SELECT:
      if ( gus_voice_select( GET_BYTE() ) ) return -EBADVOICE;
      break;
    case GUS_CMD_SAMPLE_SELECT:
      active_sample = GET_WORD();
      if ( active_sample < 0 || active_sample > gus_last_sample )
        {
          active_sample = GUS_MAX_SAMPLES - 1;
          return -EBADSAMPLE;
        }
      break;
    case GUS_CMD_VOICE_CONTROL:
      gv -> voice_cntrl = GET_BYTE();
      WNEW( VOICE_CNTRL );
      break;
    case GUS_CMD_FREQUENCY:
      gv -> frequency = translate_freq( GET_DWORD() ); 
      WNEW( FREQUENCY );
      break;
    case GUS_CMD_LOOP_START:
      if ( !( gv -> what_is_new & NEW_VOICE_CNTRL ) )
        gv -> voice_cntrl = samples[ active_sample ].voice_cntrl_reg;
      gv -> loop_start = GET_DWORD() + samples[ active_sample ].gstart; 
      WNEW( LOOP_START );
      break;     
    case GUS_CMD_LOOP_END:
      if ( !( gv -> what_is_new & NEW_VOICE_CNTRL ) )
        gv -> voice_cntrl = samples[ active_sample ].voice_cntrl_reg;
      gv -> loop_end = GET_DWORD() + samples[ active_sample ].gend; 
      WNEW( LOOP_END );
      break;
    case GUS_CMD_RAMP_RATE:
      gv -> ramp_rate = GET_BYTE();
      WNEW( RAMP_RATE );
      break;
    case GUS_CMD_RAMP_START:
      gv -> ramp_start = GET_BYTE();
      WNEW( RAMP_START );
      break;
    case GUS_CMD_RAMP_END:
      gv -> ramp_end = GET_BYTE();
      WNEW( RAMP_END );
      break;
    case GUS_CMD_CURRENT_VOL:
      gv -> current_volume = translate_volume( GET_WORD() );
      WNEW( CURRENT_VOLUME );
      break;
    case GUS_CMD_CURRENT_LOC:
      if ( !( gv -> what_is_new & NEW_VOICE_CNTRL ) )
        gv -> voice_cntrl = samples[ active_sample ].voice_cntrl_reg;
      gv -> current_address = GET_DWORD() + samples[ active_sample ].gbegin; 
      if ( gv -> current_address <= samples[ active_sample ].gend )
        WNEW( CURRENT_ADDRESS );
#if 0       
       else
        PRINTK( "gus: wrong sample position 0x%x\n", 
          gv -> current_address - samples[ active_sample ].gbegin );
#endif
      break;
    case GUS_CMD_CURRENT_PAN:
      gv -> pan_position = GET_BYTE(); WNEW( PAN_POSITION );
      break;
    case GUS_CMD_RAMP_CONTROL:
      gv -> ramp_cntrl = GET_BYTE(); WNEW( RAMP_CNTRL );
      break;
    case GUS_CMD_FREQ_AND_VOL:
      gv -> frequency = translate_freq( GET_DWORD() );
      gv -> current_volume = translate_volume( GET_WORD() );
      WNEW1( NEW_FREQUENCY | NEW_CURRENT_VOLUME );
      break;
    case GUS_CMD_LOOP_ALL:
      if ( !( gv -> what_is_new & NEW_VOICE_CNTRL ) )
        gv -> voice_cntrl = samples[ active_sample ].voice_cntrl_reg;
      gv -> voice_cntrl = GET_BYTE();
      gv -> loop_start = GET_DWORD() + samples[ active_sample ].gstart;
      gv -> loop_end = GET_DWORD() + samples[ active_sample ].gend;
      WNEW1( NEW_VOICE_CNTRL | NEW_LOOP_START | NEW_LOOP_END );
      break;
    case GUS_CMD_RAMP_ALL:
      gv -> ramp_cntrl = GET_BYTE();
      gv -> ramp_rate = GET_BYTE();
      gv -> ramp_start = GET_BYTE();
      gv -> ramp_end = GET_BYTE();
      WNEW1( NEW_RAMP_CNTRL | NEW_RAMP_RATE | NEW_RAMP_START | NEW_RAMP_END );
      break;
    case GUS_CMD_VOICE_STOP:
      gv -> voice_cntrl = 0x03;
      gv -> ramp_cntrl = 0x03;
      gv -> current_volume = lvol_to_gf1_vol( 0 );
      gv -> current_address = 0;
      WNEW1( NEW_VOICE_CNTRL | NEW_RAMP_CNTRL | NEW_CURRENT_VOLUME |
             NEW_CURRENT_ADDRESS );
      break;
    case GUS_CMD_SAMPLE_START:
      if ( gus_voice_select( GET_BYTE() ) ) return -EBADVOICE;
      gv = &(*gus_voices)[ voices_pos_tail ][ active_voice ];
      active_sample = GET_WORD();
      if ( active_sample < 0 || active_sample > gus_last_sample )
        return -EBADSAMPLE;
      gs = &samples[ active_sample ];
#if 0
      if ( active_voice != 3 ) break;
#endif
      gv -> voice_cntrl = gs -> voice_cntrl_reg;
      gv -> frequency = translate_freq( GET_DWORD() );
      gv -> loop_start = gs -> gstart;
      gv -> loop_end = gs -> gend;
      if ( gv -> voice_cntrl & GUS_WAVE_LOOP_BACK )
        gv -> current_address = gv -> loop_end - 1;
       else
        gv -> current_address = gs -> gbegin;
      gv -> current_volume = translate_volume( GET_WORD() );
#if 0
      printk( "sample start: smp=%i, f=0x%x, b=0x%x, s=0x%x, e=0x%x, c=0x%x\n",
      	        active_sample,
      		gv -> frequency,
      		gv -> current_address,
      		gv -> loop_start,
      		gv -> loop_end,
      		gv -> voice_cntrl );
#endif
      WNEW1( NEW_VOICE_CNTRL | NEW_FREQUENCY | NEW_LOOP_START | NEW_LOOP_END |
             NEW_CURRENT_VOLUME | NEW_CURRENT_ADDRESS | NEW_SAMPLE_START );
      break;
    case GUS_CMD_SAMPLE_START1:
      WNEW( SAMPLE_START );
      break;

    case GUS_CMD_SET_TIMER:
      tmp = GET_DWORD();
      if ( tmp < 0 || tmp > ( 320L * 256L ) + 39L ) return -EINVAL;
      new_timer_counts[ voices_pos_tail ] = ( tmp + 40L ) / 80L;
      break;      
    case GUS_CMD_SET_TIMER1:
      tmp = GET_WORD();
      if ( active_timer != 0 || ( tmp < 0 || tmp > 256 ) ) return -EINVAL;
      new_timer_counts[ voices_pos_tail ] = tmp;
      break;
    case GUS_CMD_SET_TIMER2:
      tmp = GET_WORD();
      if ( active_timer != 1 || ( tmp < 0 || tmp > 256 ) ) return -EINVAL;
      new_timer_counts[ voices_pos_tail ] = tmp << 2;
      break;
    case GUS_CMD_WAIT:
      if ( ( wait_count_next[ voices_pos_tail ] = GET_WORD() ) > 0 )
        {
          voices_pos_tail++;
          voices_pos_tail &= QUEUE_MASK;
          voices_used++;
          while ( !gf1_abort_flag && voices_used == QUEUE_LEN )
            {
              SLEEP( synth, HZ * 60 );
              if ( TABORT( synth ) )
                {
                  if ( !gf1_abort_flag )
                    gf1_do_abort( 0 );
                  return 0;
                }
              if ( TIMEOUT( synth ) )
                {
                  PRINTK( "gus: gf1 timeout\n" );
                  return -EIO;
                }
              if ( gus_irq_error < 0 )
                {
                  gf1_do_abort( 0 );
                  return gus_irq_error;
                }
            }
        }
      break;
    case GUS_CMD_STOP:
      break;
      
    default:
      PRINTK( "gus: Unknown command in queue - 0x%x\n", command[ 0 ] );
  }

  return 0;
}

static int gus_flush_gf1( void )
{
  voices_flush = 1;
  while ( !gf1_abort_flag && voices_used > 0 )
    {
      SLEEP( synth, 60 * HZ );
      if ( TABORT( synth ) )
        {
          voices_flush = 0;
          return 0;
        }
      if ( TIMEOUT( synth ) )
        {
          voices_flush = 0;
          return -EIO;
        }
    }
  voices_flush = 0;
  return 0;
}

int gus_ioctl_gf1( unsigned int cmd, unsigned long arg )
{  
  if ( ( cmd & 0xff00 ) == ( 'g' << 8 ) )
    {
      switch ( cmd ) {
        case GUS_IOCTL_VERSION:
          return GUS_SYNTH_VERSION;
        case GUS_IOCTL_RESET0:
        case GUS_IOCTL_RESET:
          gf1_abort_flag = 0;
          active_timer = -1;
          if ( cmd == GUS_IOCTL_RESET )
            if ( gus_reset( (short)arg ) ) return -EIO;
          gus_voices_init();
          return 0;
        case GUS_IOCTL_INFO:
          return gus_info( (struct GUS_STRU_INFO *)arg );
        case GUS_IOCTL_START_TIMER:
          return gus_timer_start( (short)arg );
        case GUS_IOCTL_STOP_TIMER:
          return gus_timer_stop();
        case GUS_IOCTL_START_TIMER1:
          return gus_timer1_start( (short)arg );
        case GUS_IOCTL_STOP_TIMER1:
          return gus_timer1_stop();
        case GUS_IOCTL_START_TIMER2:
          return gus_timer2_start( (short)arg ); 
        case GUS_IOCTL_STOP_TIMER2:
          return gus_timer2_stop();
        case GUS_IOCTL_DOWNLOAD:
          return gus_download( (struct GUS_STRU_DOWNLOAD *)arg );
        case GUS_IOCTL_VOL_TYPE:
          gf1_volume_mode = (short)arg;
          if ( gf1_volume_mode < GUS_VOL_TYPE_GF1 || 
               gf1_volume_mode > GUS_VOL_TYPE_LINEAR ) 
            {
              gf1_volume_mode = GUS_VOL_TYPE_GF1;
              return -EINVAL;
            }
          return 0;
        case GUS_IOCTL_FREQ_TYPE:
          gf1_freq_mode = (short)arg;
          if ( gf1_freq_mode < GUS_FREQ_TYPE_GF1 || 
               gf1_freq_mode > GUS_FREQ_TYPE_HZ )
            {
              gf1_freq_mode = GUS_FREQ_TYPE_GF1;
              return -EINVAL;
            }
          return 0;
        case GUS_IOCTL_FLUSH:
          return gus_flush_gf1();
        case GUS_IOCTL_ABORT:
        case GUS_IOCTL_QABORT:
          gf1_do_abort( cmd == GUS_IOCTL_QABORT );
          WAKEUP( synth );
          gf1_abs_tick = 0L;
          return 0;
        case GUS_IOCTL_SET_TICK:
          if ( (int)arg < 0 ) return -EINVAL;
          return gf1_abs_tick = arg;
        case GUS_IOCTL_GET_TICK:
          return gf1_abs_tick;
        case GUS_IOCTL_SET_AUTO_RAMP:
          if ( arg > 64 ) return -EINVAL;
          gf1_ramp_auto = arg;
          return 0;
        case GUS_IOCTL_GET_AUTO_RAMP:
          return ( GF1_RAMP_AUTO << 16 ) | gf1_ramp_auto;
        case GUS_IOCTL_SET_SPAN:
          if ( arg > 256 ) return -EINVAL;
          smooth_pan_value = arg;
          return 0;
        case GUS_IOCTL_GET_SPAN:
          return ( SMOOTH_PAN_VALUE << 16 ) | smooth_pan_value;
        default:
          PRINTK( "gus: unknown gus_ioctl_gf1 command 0x%x\n", cmd );
      }
    }
  return -EIO;
}

int gus_open_gf1()
{
  if ( gf1_mode != GF1_MODE_NONE ) return -EBUSY;
#ifndef STATIC_GF1_QUEUE
  if ( !( gus_voices = (gus_voices_type *)
  			kmalloc( sizeof( gus_voices_type ), GFP_KERNEL ) ) )
    return -ENOMEM;
#else
  {
    static gus_voices_type gus_voices_static;
    gus_voices = &gus_voices_static;
  }
#endif
  gf1_mode = GF1_MODE_SYNTH;
  gf1_volume_mode = GUS_VOL_TYPE_GF1;
  gf1_freq_mode = GUS_FREQ_TYPE_GF1;
  cmd_size = cmd_size_need = cmd_curr_pos = 0;
  gf1_abort_flag = 0;
  active_timer = -1;
  MOD_INC_USE_COUNT;
  return 0;
}
        
void gus_release_gf1()
{
  gus_stop();
#ifndef STATIC_GF1_QUEUE
  if ( gus_voices ) kfree_s( gus_voices, sizeof( gus_voices_type ) );
#endif
  gus_voices = NULL;
  gf1_mode = GF1_MODE_NONE;
  MOD_DEC_USE_COUNT;
}

int gus_read_gf1( char *buf, int count )
{
  return -EIO;
}

int gus_write_gf1( char *buf, int count )
{
  int copy_size, result;

  while ( count > 0 )
    { 
      if ( cmd_size == 0 )
        {
          cmd_size = get_fs_byte( buf++ );
	  if ( --cmd_size < 1 || cmd_size > sizeof( command ) ) 
	    {
	      PRINTK( "gus: Corrupted data!!!\n" );
	      return -EIO;	/* corrupted data */
	    }
          cmd_size_need = cmd_size;
          cmd_curr_pos = 0;
          count--;
        }
      if ( cmd_size_need > 0 )
        {
          copy_size = cmd_size_need;
          if ( copy_size > count ) copy_size = count;
          MEMCPY_FROMFS( command + cmd_curr_pos, buf, copy_size );
          cmd_size_need -= copy_size;
          count -= copy_size;
          buf += copy_size;
          cmd_curr_pos += copy_size;
        }
      if ( cmd_size > 0 && cmd_size_need == 0 )
        {
	  if ( !gf1_abort_flag )
	    {
	      if ( ( result = do_gus_command() ) < 0 ) 
                return result;
	    }
	   else
	  if ( gf1_abort_flag == 2 && command[ 0 ] == GUS_CMD_STOP )
            gf1_abort_flag = 0;
	  cmd_size = 0;
        }
    }
  return 0;
}
