/*
 *  Copyright (c) 1994/95 by Jaroslav Kysela (Perex soft)
 */

#include "gus_dev.h"

struct gus_mem_bank_stru {  
  int current;
  int begin;
  int end;
};

short ics_flag;			/* 5 = flipped!!! */
short codec_flag;
short max_flag;

int gus_port = 0x220;
int codec_port = -1;		/* MAX card is autodetected */
				/* !!code for daughter board doesn't tested!! */
int use_codec = 1;		/* 0 = don't use codec for PCM */
int gus_irq = 7;
int gus_dma1 = 6;
int gus_dma2 = 7;
int gus_version;
int gus_mem = 0;

int no_dma_download = 0;	/* set this, if you have trouble with 
				   download of samples */

short gus_active_voices = -1;
unsigned int gf1_playback_freq;
int gus_mem_last_bank;
struct gus_mem_bank_stru gus_mem_bank[ MAX_GUS_MEMORY_BANKS ];
struct GUS_STRU_SAMPLE samples[ GUS_MAX_SAMPLES ];
short gus_last_sample;
short max_cntrl_val;

SLEEP_PREPARE( dma1 )
SLEEP_PREPARE( dma2 )

unsigned char *dma1_buf = NULL;
unsigned char *dma2_buf = NULL;
int dma1_buf_size = 0;
int dma2_buf_size = 0;
short dma1_lock = 0;
short dma2_lock = 0;
char *dma1_owner = NULL;
char *dma2_owner = NULL;

static char gus_type_str[ 80 ];

static void gus_detect( void )
{
  int l, loc;

  gus_mem = 0;
  CLI();
  gus_write8( 0x4c, 0 );	/* reset GF1 */
  gus_delay();
  gus_delay();
  gus_write8( 0x4c, 1 );	/* release reset */
  gus_delay();
  gus_delay();
  gus_poke( 0L, 0xaa );
  MB();
  if ( gus_peek( 0L ) != 0xaa ) 
    {
      STI();
      return;
    }
  gus_poke( 0L, 0 );
  STI();
  for ( l = 1L; l < 1024L; l++ )
    {
      int n, failed;
    
      CLI();
      if ( gus_peek( 0L ) != 0 ) break;
      STI();
      loc = l << 10;
      for ( n = loc - 1, failed = 0; n <= loc; n++ )
        {
          CLI();
          gus_poke( loc, 0xaa );
          MB();
          if ( gus_peek( loc ) != 0xaa ) failed = 1;
          gus_poke( loc, 0x55 );
          MB();
          if ( gus_peek( loc ) != 0x55 ) failed = 1;
          STI();
        }
      if ( failed ) break;
    }
  gus_mem = l << 10;
}

int gus_init( void )
{
  short val, len;
  char *model;

  gus_detect();
  if ( gus_mem == 0 ) return 1;
  
  CLI();
  OUTB( 0x20, GUSP( REGCNTRLS ) );
  MB();
  val = INB( GUSP( REGCNTRLS ) );
  STI();
  
  gus_version = 0x24;
  ics_flag = 0;
  codec_flag = 0;
  max_flag = 0;
  model = "2.4";
  if ( val != 0xff && (val & 0x06) )
    {
      val = INB( GUSP( BOARDVERSION ) );
      if ( val == 255 || val < 5 )
        {
          model = "3.4";
          gus_version = 0x34;
        }
       else
        if ( val < 10 )
          {
            model = "3.7";
            gus_version = 0x37;
            ics_flag = val;		/* if 5 - some channels are flipped */
          }
         else
          {
            model = "MAX";
            gus_version = 0xa0;
            max_flag = codec_flag = 1;	/* heya - GUS MAX */
          }
    }

  gus_active_voices = -1;
  gus_mix_ctrl_reg = 0;
  if ( max_flag )	/* GUS MAX */
    codec_port = gus_port + ( 0x32c - 0x220 );
    
  if ( codec_flag && codec_port > 0x220 )
    {
      max_cntrl_val = ( gus_port >> 4 ) & 0x0f;
      if ( gus_dma1 > 3 ) max_cntrl_val |= 0x10;
      if ( gus_dma2 > 3 ) max_cntrl_val |= 0x20;
      max_cntrl_val |= 0x40;
      OUTB( max_cntrl_val, GUSP( MAXCNTRLPORT ) );
      MB();

      if ( !( val = codec_init() ) )
        {
          PRINTK( "gus: codec not found at port 0x%x\n", codec_port );
          max_flag = codec_flag = 0;		/* clear flags */
        }
       else
        if ( max_flag )
          switch ( val ) {
            case 0x00: model = "MAX (CS4231 rev A)"; break; /* maybe */
            case 0x01: model = "MAX (CS4231 rev B)"; break;
            case 0x0a: model = "MAX (CS4231 rev C)"; break;
            default:
              model = "MAX (unknown CS4231)";
          }
    }

  if ( !max_flag ) use_codec = 0;

  len = sprintf( gus_type_str, "Gravis UltraSound %s (%dk) at 0x%x, irq %d", 
                    model, (int)gus_mem / 1024, (int)gus_port, (int)gus_irq );
  if ( gus_dma2 != gus_dma1 )
    sprintf( gus_type_str + len, ", dmas %d %d", gus_dma1, gus_dma2 );
   else
    sprintf( gus_type_str + len, ", dma %d", gus_dma1 );
  PRINTK( "gus: %s\n", gus_type_str );

  mixer_init();
  return 0;
}

int gus_reset( short voices )
{
  static irqs[ 16 ] = { 0, 0, 1, 3, 0, 2, 0, 4, 0, 0, 0, 5, 6, 0, 0, 7 };
  static dmas[ 16 ] = { 0, 1, 0, 2, 0, 3, 4, 5 };
  short i;
  unsigned char irq, dma;
  short again_reset = 0;

#if 0
  PRINTK( "gus_reset: voices = %d\n", voices );
#endif

again:

  if ( codec_mode == CODEC_MODE_NONE )
    {
      CLI();
      gus_write8( 0x4c, 0 );	/* reset GF1 */
      gus_delay();
      gus_write8( 0x4c, 1 );	/* disable IRQ & DAC */
      gus_delay();
      STI();
    }

  gus_delay();
  gus_delay();
  CLI();
  INB( GUSP( IRQSTAT ) );
  gus_write8( 0x41, 0 );	/* DRAM DMA Control Register */
  gus_write8( 0x45, 0 );	/* Timer Control */
  gus_write8( 0x49, 0 );	/* Sampling Control Register */
  gus_select_active_voices( voices );
  STI();
  gus_delay();
  CLI();
  gus_poke( 0L, 0 );
  gus_poke( 1L, 0 );
  INB( GUSP( IRQSTAT ) );
  gus_look8( 0x41 );		/* DRAM DMA Control Register */
  gus_look8( 0x49 );		/* Sampling Control Register */
  gus_read8( 0x0f );		/* IRQ Source Register */
  STI();

  for ( i = 0; i < 32; i++ )
    {
      CLI();
      OUTB( i, GUSP( GF1PAGE ) );
      gus_write8( 0, 3 );	/* Voice Control Register = voice stop */
      gus_write8( 0x0d, 3 );	/* Volume Ramp Control Register = ramp off */
      gus_write16( 1, 0x100 );
      gus_write_addr( 2, 0, 0 );
      gus_write_addr( 4, 0, 0 );
      gus_write8( 6, 0 );
      gus_write8( 7, 0 );
      gus_write8( 8, 0 );
      gus_write16( 9, 0 );
      gus_write_addr( 0x0a, 0, 0 );
      gus_write8( 0x0c, 7 );
      STI();
    }

  CLI();
  gus_look8( 0x41 );		/* DRAM DMA Control Register */
  gus_look8( 0x49 );		/* Sampling Control Register */
  gus_read8( 0x0f );		/* IRQ Source Register */
  gus_delay();
  gus_write8( 0x4c, 7 );	/* Reset Register = IRQ enable, DAC enable */
  gus_delay();
  gus_write8( 0x4c, 7 );	/* Reset Register = IRQ enable, DAC enable */
  STI();

  if ( codec_mode == CODEC_MODE_NONE )
    {
      if ( gus_dma1 < 0 || gus_dma1 > 15 ) gus_dma1 = 0;
      gus_dma1 &= 7;
      dma = dmas[ gus_dma1 ];
      if ( gus_dma2 < 0 || gus_dma2 > 15 ) gus_dma2 = gus_dma1;
      if ( gus_dma1 == gus_dma2 || ( gus_dma2 >= 0 && dmas[ gus_dma2 ] == 0 ) )
        {
          if ( dmas[ gus_dma2 ] == 0 )
            PRINTK( "gus: Warning! DMA2 isn't defined.\n" );
          dma |= 0x40;
        }
       else
        dma |= dmas[ gus_dma2 ] << 3;
    
      if ( ( dma & 7 ) == 0 )
        {
          PRINTK( "gus: Error! DMA isn't defined.\n" );
          return 1;
        }

      if ( gus_irq < 0 || gus_irq > 15 ) gus_irq = 0;
      gus_irq &= 0x0f;
      irq = irqs[ gus_irq ];
      if ( !irq )
        {
          PRINTK( "gus: Error! IRQ isn't defined.\n" );
          return 1;
        }
      irq |= 0x40;

      CLI();

      OUTB( 5, GUSP( REGCNTRLS ) );
      OUTB( gus_mix_ctrl_reg | 0x02, GUSP( MIXCNTRLREG ) );
      OUTB( 0x00, GUSP( IRQDMACNTRLREG ) );
      OUTB( 0, GUSP( REGCNTRLS ) );

      gus_delay();

      OUTB( 0x00 | gus_mix_ctrl_reg | 0x02, GUSP( MIXCNTRLREG ) );
      OUTB( dma, GUSP( IRQDMACNTRLREG ) );
      OUTB( 0x40 | gus_mix_ctrl_reg | 0x02, GUSP( MIXCNTRLREG ) );
      OUTB( irq, GUSP( IRQDMACNTRLREG ) );

      gus_delay();
  
      OUTB( 0x00 | gus_mix_ctrl_reg | 0x02, GUSP( MIXCNTRLREG ) );
      OUTB( dma, GUSP( IRQDMACNTRLREG ) );
      OUTB( 0x40 | gus_mix_ctrl_reg | 0x02, GUSP( MIXCNTRLREG ) );
      OUTB( irq, GUSP( IRQDMACNTRLREG ) );

      gus_delay();
    }
  
  OUTB( 0, GUSP( GF1PAGE ) );
  OUTB( gus_mix_ctrl_reg, GUSP( MIXCNTRLREG ) );

  STI();
  
  do {
    CLI();
    i = gus_read8( 0x0f ) & 0xc0;
    STI();
  } while( i != 0xc0 );

  while ( INB( GUSP( IRQSTAT ) ) );
  
  CLI();
  OUTB( 0, GUSP( GF1PAGE ) );
  gus_write8( 6, 0x3f );
  gus_write8( 7, 0x00 );
  gus_write8( 8, 0x7f );
  gus_write8( 0x0d, 0x20 );
  gus_write8( 0, 0x08 );
  gus_delay();
  gus_write8( 0x0d, 0x20 );
  gus_write8( 0, 0x08 );
  gf1_synth_irqs = 0;
  STI();
  SLEEP( dma1, 4 );
  CLI();
  gus_write8( 0, 0x03 );
  gus_write8( 0x0d, 0x03 );
  STI();
  if ( gf1_synth_irqs == 0 )
    {
      gf1_synth_irqs = -1;
      if ( ++again_reset > 3 )
        {
#if 1
          PRINTK( "gus_reset: ramp (and maybe IRQ) test failed\n" );
#endif
          return 1;
        }
      goto again;
    }
  gf1_synth_irqs = -1;

  CLI();
  INB( GUSP( IRQSTAT ) );	/* touch the IRQ reg */
  gus_look8( 0x41 );		/* DRAM DMA Control Register */
  gus_look8( 0x49 );		/* Sampling Control Register */
  gus_read8( 0x0f );		/* IRQ Source Register */
  STI();

  recompute_gf1_ramp_ranges();
  gus_reset_samples();
  return 0;
}

void gus_stop( void )
{
  short i, ramp_ok;
  unsigned char ramp_end;
  
  gus_active_voices = -1;

  if ( max_flag && codec_mode == CODEC_MODE_NONE )
  					/* clear out MAX DMA latches */
    {
      CLI();
      OUTB( max_cntrl_val & ~0x30, GUSP( MAXCNTRLPORT ) );
      OUTB( max_cntrl_val, GUSP( MAXCNTRLPORT ) );
      STI();
    }  
    
  if ( ( gus_read8( 0x4c ) & 7 ) == 1 ) return;

  gus_write8( 0x45, 0 );	/* stop all timers */
  
  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();
    }
  gus_write8( 0x4c, 1 );	/* disable IRQ & DAC */
}

void gus_reset_samples( void )
{
  short i;

  gus_mem_last_bank = gus_mem >> GUS_MEM_BANK_SHIFT;
  gus_mem_bank[ 0 ].current =
  gus_mem_bank[ 0 ].begin = 0x20;
  gus_mem_bank[ 0 ].end = GUS_MEM_BANK_SIZE;
  for ( i = 1; i < gus_mem_last_bank; i++ )
    {
      gus_mem_bank[ i ].current =
      gus_mem_bank[ i ].begin = i << GUS_MEM_BANK_SHIFT;
      gus_mem_bank[ i ].end = ( i + 1 ) << GUS_MEM_BANK_SHIFT;
    }
  for ( i = gus_mem_last_bank; i < MAX_GUS_MEMORY_BANKS; i++ )
    {
      gus_mem_bank[ i ].current =
      gus_mem_bank[ i ].begin =
      gus_mem_bank[ i ].end = i << GUS_MEM_BANK_SHIFT;
    }
#if 0
  for ( i = 0; i < GUS_MAX_SAMPLES; i++ )
    {
      samples[ i ].gbegin = 0;
      samples[ i ].gstart = 0;
      samples[ i ].gend = 0;
      samples[ i ].length = 0;
      samples[ i ].voice_cntrl_reg = 0;
    }
#endif
  gus_last_sample = -1;
}

static int gus_mem_get_ptr( int needed_size, short w_16 )
{
  short bank;
  int current, free;
  
  if ( w_16 && needed_size > GUS_MEM_BANK_SIZE ) 
    return -1;
  for ( bank = 0; bank < gus_mem_last_bank; bank++ )
    {
      current = gus_mem_bank[ bank ].current;
      free = gus_mem_bank[ bank ].end - current;
      if ( !free ) continue;
      if ( free >= needed_size )
        {
          gus_mem_bank[ bank ].current += needed_size + 0x1f;
          gus_mem_bank[ bank ].current &= ~0x1f;
          return current;
        }
      if ( !w_16 && bank + 1 < gus_mem_last_bank )
        {
          short tbank;
          int tfree;
        
          tbank = bank + 1;
          tfree = free;
          while ( tfree < needed_size && tbank < gus_mem_last_bank )
            if ( gus_mem_bank[ tbank ].current == gus_mem_bank[ tbank ].begin )
              {
                tfree += GUS_MEM_BANK_SIZE;
                tbank++;
              }
             else
              break;
          
          if ( tfree >= needed_size )
            {
              needed_size -= free;
              gus_mem_bank[ bank++ ].current += free;
              while ( bank < tbank )
                {
                  gus_mem_bank[ bank ].current +=
                	needed_size > GUS_MEM_BANK_SIZE ? 
                  		GUS_MEM_BANK_SIZE : needed_size + 0x1f;
                  gus_mem_bank[ bank++ ].current &= ~0x1f;
                }
              return current;
            }
        }
    }
  return -1;
}

int gus_mem_free( void )
{
  short bank;
  int free;
  
  free = 0;
  for ( bank = 0; bank < gus_mem_last_bank; bank++ )
    free += gus_mem_bank[ bank ].end - gus_mem_bank[ bank ].current;
  return free;
}

int gus_download( struct GUS_STRU_DOWNLOAD *sample )
{
  struct GUS_STRU_DOWNLOAD smp;
  int result;

  if ( VERIFY_AREA( VERIFY_READ, sample, sizeof( *sample ) ) ||
       VERIFY_AREA( VERIFY_WRITE, sample, sizeof( *sample ) ) )
    return -EIO;
  MEMCPY_FROMFS( &smp, sample, sizeof( smp ) );
  if ( ( result = gus_download1( &smp ) ) >= 0 )
    put_fs_int( samples[ gus_last_sample ].gbegin, &sample -> gus_addr );
  return result;
}

int gus_download1( struct GUS_STRU_DOWNLOAD *sample )
{
  unsigned char *ptr;
  int gus_mem_ptr;
  int length;
  int transfer_size;
  int count, count1;
  unsigned short invert;
  unsigned long timer;

  if ( GUS_MAX_SAMPLES <= gus_last_sample + 1 ) return -ENOMEM;
  gus_last_sample++;
#if 0
  if ( gus_last_sample != 100 )
    {
      PRINTK( "smp = %d, start = 0x%x, end = 0x%x, length = 0x%x\n", 
      	        gus_last_sample,
      		sample -> loop_start,
      		sample -> loop_end,
      		sample -> length );
    }
#endif
  ptr = sample -> sample_ptr;
  length = sample -> length;
  samples[ gus_last_sample ].gstart = sample -> loop_start;
  if ( !( sample -> voice_cntrl_reg & GUS_WAVE_LOOP ) || !sample -> loop_end )
    samples[ gus_last_sample ].gend = length - 1;
   else
    samples[ gus_last_sample ].gend = sample -> loop_end;
  samples[ gus_last_sample ].length = length;
  samples[ gus_last_sample ].voice_cntrl_reg =
  	sample -> voice_cntrl_reg & 
  	( GUS_WAVE_LOOP_BACK | GUS_WAVE_BIDIRECTIONAL | 
  	  GUS_WAVE_LOOP | GUS_WAVE_16BIT );

  if ( ( gus_mem_ptr = gus_mem_get_ptr( length, sample -> voice_cntrl_reg & GUS_WAVE_16BIT ) ) < 0 )
    {
      gus_last_sample--;
      return -ENOMEM;
    }

  samples[ gus_last_sample ].gbegin = gus_mem_ptr;
  samples[ gus_last_sample ].gstart += gus_mem_ptr;
  samples[ gus_last_sample ].gend += gus_mem_ptr;

#if 0
  printk( "DWNLD: beg=0x%x start=0x%x end=0x%x cntrl=0x%x\n",
	samples[ gus_last_sample ].gbegin,
	samples[ gus_last_sample ].gstart,
	samples[ gus_last_sample ].gend,
	samples[ gus_last_sample ].voice_cntrl_reg );
#endif

  if ( !no_dma_download && dma1_buf_size > 0 && codec_mode == CODEC_MODE_NONE )
    {
      CLI();
      while ( dma1_lock == 0 && length >= 512 )
        {
          dma1_lock = WK_LOCK;
          dma1_owner = "gf1 synth";
          STI();
          if ( length > dma1_buf_size ) 
            transfer_size = dma1_buf_size; 
           else
            transfer_size = length;
          if ( gus_mem_ptr >> GUS_MEM_BANK_SHIFT != ( gus_mem_ptr + transfer_size - 1 ) >> 18 )
            transfer_size = ( ( gus_mem_ptr + ( 1 << GUS_MEM_BANK_SHIFT ) ) &
            		    ~( GUS_MEM_BANK_SIZE - 1 ) ) - gus_mem_ptr;
          MEMCPY_FROMFS( dma1_buf, ptr, transfer_size );
          gf1_init_dma_transfer( gus_mem_ptr, dma1_buf, transfer_size,
                                 sample -> voice_cntrl_reg & GUS_WAVE_INVERT,
                                 sample -> voice_cntrl_reg & GUS_WAVE_16BIT );
	  timer = jiffies + ( HZ * 2 );
          do {
            SLEEP( dma1, HZ * 2 );
          } while ( ( dma1_lock & WK_WAKEUP ) == 0 && timer > jiffies );
          if ( timer <= jiffies )
            PRINTK( "gus_download: DMA transfer time out\n" );
          gf1_done_dma_transfer();
          dma1_owner = NULL;
          dma1_lock = 0;
          ptr += transfer_size;
          length -= transfer_size;
          gus_mem_ptr += transfer_size;
        }
      STI();
    }

  invert = ( sample -> voice_cntrl_reg & GUS_WAVE_INVERT ) != 0 ? 0x80 : 0;
  if ( sample -> voice_cntrl_reg & GUS_WAVE_16BIT ) 
    {
      if ( length & 1 )
         PRINTK( "gus_download: warning! unaligned poke size\n" );
      length >>= 1;
      for ( count = count1 = 0; count < length; count++ )
        {
          CLI();
          gus_poke( gus_mem_ptr++, get_fs_byte( ptr++ ) );
          gus_poke( gus_mem_ptr++, get_fs_byte( ptr++ ) ^ invert );
          STI();
        }
    }
   else
    {
      for ( count = 0; count < length; count++ )
        {
          CLI();
          gus_poke( gus_mem_ptr++, get_fs_byte( ptr++ ) ^ invert );
          STI();
        }
    }

  return 0;
}

int gus_info( struct GUS_STRU_INFO *info )
{
  if ( VERIFY_AREA( VERIFY_WRITE, info, sizeof( *info ) ) ) return 1;
  put_fs_word( gus_port, (short *)&info -> port );
  put_fs_word( gus_irq, (short *)&info -> irq );
  put_fs_word( gus_dma1, (short *)&info -> dma1 );
  put_fs_word( gus_dma2, (short *)&info -> dma2 );
  put_fs_int( gus_mem, (int *)&info -> memory_size );
  put_fs_int( gus_mem_free(), (int *)&info -> memory_free );
  put_fs_word( gus_version, (short *)&info -> version );
  return 0;
}

int get_info_init( char *buffer )
{
  int i;

  strcpy( buffer, VERSION_AND_COPYRIGHT "\n" );

  strcat( buffer, gus_type_str );
  strcat( buffer, "\n\n" );
  strcat( buffer, "DMA" );
  if ( gus_dma1 != gus_dma2 ) 
    strcat( buffer, "1:\n" ); else strcat( buffer, ":\n" );
  sprintf( buffer + strlen( buffer ), "  buffer ptr    : 0x%x\n", (int)dma1_buf );
  sprintf( buffer + strlen( buffer ), "  buffer size   : %i\n", dma1_buf_size );
  sprintf( buffer + strlen( buffer ), "  lock status   : %-6s %-6s %-6s\n",
	dma1_lock & WK_LOCK   ? "lock  " : "",
	dma1_lock & WK_WAKEUP ? "wakeup" : "",
	dma1_lock & WK_SLEEP  ? "sleep " : "" );
  sprintf( buffer + strlen( buffer ), "  owner         : %-20s\n", dma1_owner );
  if ( gus_dma1 != gus_dma2 )
    { 
      strcat( buffer, "DMA2:\n" );
      sprintf( buffer + strlen( buffer ), "  buffer ptr    : 0x%x\n", (int)dma2_buf );
      sprintf( buffer + strlen( buffer ), "  buffer size   : %i\n", dma2_buf_size );
      sprintf( buffer + strlen( buffer ), "  lock status   : %-6s %-6s %-6s\n",
	dma2_lock & WK_LOCK   ? "lock  " : "",
	dma2_lock & WK_WAKEUP ? "wakeup" : "",
	dma2_lock & WK_SLEEP  ? "sleep " : "" );
      sprintf( buffer + strlen( buffer ), "  owner         : %-20s\n", dma2_owner );
    }
  if ( gus_active_voices > 0 )
    {
      strcat( buffer, "GF1:\n" );
      strcat( buffer, "  owner         : " );
      switch ( gf1_mode ) {
        case GF1_MODE_SYNTH: strcat( buffer, "synth\n" ); break;
        case GF1_MODE_PCM:   strcat( buffer, "pcm\n" ); break;
        default:             strcat( buffer, "<NULL>\n" ); break;
      }
      sprintf( buffer + strlen( buffer ), "  active voices : %i\n", gus_active_voices );
      sprintf( buffer + strlen( buffer ), "  mixing freq.  : %iHz\n", gf1_playback_freq );
      if ( gf1_mode == GF1_MODE_SYNTH )
        sprintf( buffer + strlen( buffer ), "  free memory   : %i\n", gus_mem_free() );
      for ( i = 0; i < gus_mem_last_bank && gf1_mode == GF1_MODE_SYNTH; i++ )
        {
          sprintf( buffer + strlen( buffer ), "  MEMORY BANK #%i:\n", i );
          sprintf( buffer + strlen( buffer ), "    begin         : 0x%05x (%07i)\n", 
          	gus_mem_bank[ i ].begin, gus_mem_bank[ i ].begin );
          sprintf( buffer + strlen( buffer ), "    end           : 0x%05x (%07i)\n", 
          	gus_mem_bank[ i ].end - 1, gus_mem_bank[ i ].end - 1 );
          sprintf( buffer + strlen( buffer ), "    free          : 0x%05x (%07i)\n",
          	gus_mem_bank[ i ].end - gus_mem_bank[ i ].current,
          	gus_mem_bank[ i ].end - gus_mem_bank[ i ].current );
        }
    }
  return strlen( buffer );
}
