add: void _AHIsub_Update( ULONG                   flags, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase*      AHIsubBase ) { struct EMU10kxBase* EMU10kxBase = (struct EMU10kxBase*) AHIsubBase; struct EMU10kxData* dd = (struct EMU10kxData*) AudioCtrl->ahiac_DriverData;  dd->current_length = AudioCtrl->ahiac_BuffSamples;  if( AudioCtrl->ahiac_Flags & AHIACF_STEREO ) { dd->current_size = dd->current_length * 4; } else { dd->current_size = dd->current_length * 2; } }     void _AHIsub_Stop( ULONG                   flags, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase*      AHIsubBase ) { struct EMU10kxBase* EMU10kxBase = (struct EMU10kxBase*) AHIsubBase; struct EMU10kxData* dd = (struct EMU10kxData*) AudioCtrl->ahiac_DriverData;  if( flags & AHISF_PLAY ) { int i; dd->is_playing= FALSE;  if( dd->voices_started != 0 ) { emu10k1_irq_disable( &dd->card, INTE_INTERVALTIMERENB );   emu10k1_voices_stop( dd->voices, dd->voices_started ); dd->voices_started = 0; }  for( i = 0; i < dd->voices_allocated; ++i ) { emu10k1_voice_free( &dd->voices[i] ); }  dd->voices_allocated = 0;  for( i = 0; i < dd->voice_buffers_allocated; ++i ) { emu10k1_voice_free_buffer( &dd->card, &dd->voices[i].mem ); }  dd->voice_buffers_allocated = 0;  memset( &dd->voices, 0, sizeof( dd->voices ) );  dd->current_length     = 0; dd->current_size       = 0; dd->current_buffers[0] = NULL; dd->current_buffers[1] = NULL; dd->current_buffers[2] = NULL; dd->current_buffers[3] = NULL; dd->current_position   = 0;  FreeVec( dd->mix_buffer ); dd->mix_buffer = NULL; }  if( flags & AHISF_RECORD ) { emu10k1_irq_disable( &dd->card, INTE_ADCBUFENABLE );  sblive_writeptr( &dd->card, ADCCR, 0, 0 ); sblive_writeptr( &dd->card, ADCBS, 0, ADCBS_BUFSIZE_NONE );  if( dd->is_recording ) {  RestoreMixerState( dd ); }  if( dd->record_buffer != NULL ) { pci_free_consistent( dd->card.pci_dev, RECORD_BUFFER_SAMPLES * 4, dd->record_buffer, dd->record_dma_handle ); }  dd->record_buffer = NULL; dd->record_dma_handle = 0;  dd->is_recording = FALSE; } }     LONG _AHIsub_GetAttr( ULONG                   attribute, LONG                    argument, LONG                    def, struct TagItem*         taglist, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase*      AHIsubBase ) { struct EMU10kxBase* EMU10kxBase = (struct EMU10kxBase*) AHIsubBase; int i;  switch( attribute ) { case AHIDB_Bits: return 16;  case AHIDB_Frequencies: return FREQUENCIES;  case AHIDB_Frequency: return (LONG) Frequencies[ argument ];  case AHIDB_Index: if( argument <= (LONG) Frequencies[ 0 ] ) { return 0; }  if( argument >= (LONG) Frequencies[ FREQUENCIES - 1 ] ) { return FREQUENCIES-1; }  for( i = 1; i < FREQUENCIES; i++ ) { if( (LONG) Frequencies[ i ] > argument ) { if( ( argument - (LONG) Frequencies[ i - 1 ] ) < ( (LONG) Frequencies[ i ] - argument ) ) { return i-1; } else { return i; } } }  return 0;  case AHIDB_Author: return (LONG) "Martin 'Leviticus' Blom, Bertrand Lee et al.";  case AHIDB_Copyright: return (LONG) "GNU GPL";  case AHIDB_Version: return (LONG) LibIDString;  case AHIDB_Annotation: return (LONG) "Funded by Hyperion Entertainment. Based on the Linux driver.";  case AHIDB_Record: return TRUE;  case AHIDB_FullDuplex: return TRUE;  case AHIDB_Realtime: return TRUE;  case AHIDB_MaxRecordSamples: return RECORD_BUFFER_SAMPLES / 2;  case AHIDB_MinMonitorVolume: return 0x00000;  case AHIDB_MaxMonitorVolume: return 0x40000;  case AHIDB_MinInputGain: return 0x10000;  case AHIDB_MaxInputGain: return 0xD7450;  case AHIDB_MinOutputVolume: return 0x00000;  case AHIDB_MaxOutputVolume: return 0x40000;  case AHIDB_Inputs: return INPUTS;  case AHIDB_Input: return (LONG) Inputs[ argument ];  case AHIDB_Outputs: return OUTPUTS;  case AHIDB_Output: return (LONG) Outputs[ argument ];  default: return def; } }     ULONG _AHIsub_HardwareControl( ULONG                   attribute, LONG                    argument, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase*      AHIsubBase ) { struct EMU10kxBase* EMU10kxBase = (struct EMU10kxBase*) AHIsubBase; struct EMU10kxData* dd = (struct EMU10kxData*) AudioCtrl->ahiac_DriverData;  switch( attribute ) { case AHIC_MonitorVolume: dd->monitor_volume = Linear2MixerGain( (Fixed) argument, &dd->monitor_volume_bits ); if( dd->is_recording ) { UpdateMonitorMixer( dd ); } return TRUE;  case AHIC_MonitorVolume_Query: return dd->monitor_volume;  case AHIC_InputGain: dd->input_gain = Linear2RecordGain( (Fixed) argument, &dd->input_gain_bits ); emu10k1_writeac97( &dd->card, AC97_RECORD_GAIN, dd->input_gain_bits ); return TRUE;  case AHIC_InputGain_Query: return dd->input_gain;  case AHIC_OutputVolume: dd->output_volume = Linear2MixerGain( (Fixed) argument, &dd->output_volume_bits ); emu10k1_writeac97( &dd->card, AC97_PCMOUT_VOL, dd->output_volume_bits ); return TRUE;  case AHIC_OutputVolume_Query: return dd->output_volume;  case AHIC_Input: dd->input = argument; emu10k1_writeac97( &dd->card, AC97_RECORD_SELECT, InputBits[ dd->input ] );  if( dd->is_recording ) { UpdateMonitorMixer( dd ); }  return TRUE;  case AHIC_Input_Query: return dd->input;  case AHIC_Output: dd->output = argument;  if( dd->output == 0 ) { emu10k1_set_volume_gpr( &dd->card, VOL_FRONT_REAR_L, 0, VOL_5BIT); emu10k1_set_volume_gpr( &dd->card, VOL_FRONT_REAR_R, 0, VOL_5BIT); } else { emu10k1_set_volume_gpr( &dd->card, VOL_FRONT_REAR_L, 100, VOL_5BIT); emu10k1_set_volume_gpr( &dd->card, VOL_FRONT_REAR_R, 100, VOL_5BIT); } return TRUE;  case AHIC_Output_Query: return dd->output;  default: return FALSE; } }
context: _AHIsub_Start( ULONG                   flags,	       struct AHIAudioCtrlDrv* AudioCtrl,	       struct DriverBase*      AHIsubBase ){  struct EMU10kxBase* EMU10kxBase = (struct EMU10kxBase*) AHIsubBase;  struct EMU10kxData* dd = (struct EMU10kxData*) AudioCtrl->ahiac_DriverData;  /* Stop playback/recording, free old buffers (if any) */  AHIsub_Stop( flags, AudioCtrl );  if( flags & AHISF_PLAY )  {    int num_voices;    int i;    ULONG dma_buffer_size;    ULONG dma_sample_frame_size;    /* Update cached/syncronized variables */    AHIsub_Update( AHISF_PLAY, AudioCtrl );    /* Allocate a new mixing buffer. Note: The buffer must be cleared, since       it might not be filled by the mixer software interrupt because of       pretimer/posttimer! */    dd->mix_buffer = AllocVec( AudioCtrl->ahiac_BuffSize,			       MEMF_ANY | MEMF_PUBLIC | MEMF_CLEAR );    if( dd->mix_buffer == NULL )    {      Req( "Unable to allocate %ld bytes for mixing buffer.",	   AudioCtrl->ahiac_BuffSize );      return AHIE_NOMEM;    }    /* Allocate 1 or 4 voice buffers large enough for 16-bit double-buffered       playback (mono, stereo or 8 channels) */    if( AudioCtrl->ahiac_Flags & AHIACF_STEREO ||	AudioCtrl->ahiac_Flags & AHIACF_MULTICHANNEL)    {      dma_sample_frame_size = 4;      dma_buffer_size = AudioCtrl->ahiac_MaxBuffSamples * dma_sample_frame_size;    }    else    {      dma_sample_frame_size = 2;      dma_buffer_size = AudioCtrl->ahiac_MaxBuffSamples * dma_sample_frame_size;    }    if( AudioCtrl->ahiac_Flags & AHIACF_MULTICHANNEL )    {      num_voices = 4;      // Disable front-to-center/LFE routing      emu10k1_set_volume_gpr( &dd->card, VOL_FRONT_CENTER, 0, VOL_5BIT);      emu10k1_set_volume_gpr( &dd->card, VOL_FRONT_LFE,    0, VOL_5BIT);    }    else    {      num_voices = 1;      // Enable front-to-center/LFE routing      emu10k1_set_volume_gpr( &dd->card, VOL_FRONT_CENTER, 100, VOL_5BIT);      emu10k1_set_volume_gpr( &dd->card, VOL_FRONT_LFE,    100, VOL_5BIT);    }        for( i = 0; i < num_voices; ++i )    {      if( emu10k1_voice_alloc_buffer( &dd->card,				      &dd->voices[i].mem,				      ( dma_buffer_size * 2 + PAGE_SIZE - 1 )				      / PAGE_SIZE ) < 0 )      {	Req( "Unable to allocate voice buffer." );	return AHIE_NOMEM;      }      memset( dd->voices[i].mem.addr, 0, dma_buffer_size * 2 );      dd->voices[i].usage = VOICE_USAGE_PLAYBACK;      if( AudioCtrl->ahiac_Flags & AHIACF_STEREO ||	  AudioCtrl->ahiac_Flags & AHIACF_MULTICHANNEL )      {	dd->voices[i].flags = VOICE_FLAGS_STEREO | VOICE_FLAGS_16BIT;      }      else      {	dd->voices[i].flags = VOICE_FLAGS_16BIT;      }    }        dd->voice_buffers_allocated = num_voices;    for( i = 0; i < num_voices; ++i )    {      if( emu10k1_voice_alloc( &dd->card, &dd->voices[i] ) < 0 )      {	Req( "Unable to allocate voice." );	return AHIE_UNKNOWN;      }    }        dd->voices_allocated = num_voices;    for( i = 0; i < num_voices; ++i )    {      dd->voices[i].initial_pitch = (u16) ( srToPitch( AudioCtrl->ahiac_MixFreq ) >> 8 );      dd->voices[i].pitch_target  = SamplerateToLinearPitch( AudioCtrl->ahiac_MixFreq );      DPD(2, "Initial pitch --> %#x\n", dd->voice.initial_pitch);      /* start, startloop and endloop is unit sample frames, not bytes */      dd->voices[i].start     = ( ( dd->voices[i].mem.emupageindex << 12 )				  / dma_sample_frame_size );      dd->voices[i].endloop   = dd->voices[i].start + AudioCtrl->ahiac_MaxBuffSamples * 2;      dd->voices[i].startloop = dd->voices[i].start;      /* Make interrupt routine start at the correct location */      dd->current_position = dd->current_length;      dd->current_buffers[i] = ( dd->voices[i].mem.addr +				 dd->current_position * dma_sample_frame_size );      dd->voices[i].params[0].volume_target = 0xffff;      dd->voices[i].params[0].initial_fc = 0xff;      dd->voices[i].params[0].initial_attn = 0x00;      dd->voices[i].params[0].byampl_env_sustain = 0x7f;      dd->voices[i].params[0].byampl_env_decay = 0x7f;          if( dd->voices[i].flags & VOICE_FLAGS_STEREO )      {	if( dd->card.is_audigy )	{	  static int routes[] = {	    0x03020100, // Send FL/FR to ??/??, AHI_FRONT_L/MULTI_FRONT_R	    0x03020504, // Send RL/RR to ??/??, AHI_REAR_L/AHI_REAR_R	    0x03020706, // Send SL/SR to ??/??, AHI_SURROUND_L/AHI_SURROUND_R	    0x03020908, // Send C/LFE to ??/??, AHI_CENTER,AHI_LFE	  };	  dd->voices[i].params[0].send_dcba = 0x000000ff;	  dd->voices[i].params[0].send_hgfe = 0x00000000;	  dd->voices[i].params[1].send_dcba = 0x0000ff00;	  dd->voices[i].params[1].send_hgfe = 0x00000000;	  dd->voices[i].params[0].send_routing  = dd->voices[i].params[1].send_routing  = routes[i];	  dd->voices[i].params[0].send_routing2 = dd->voices[i].params[1].send_routing2 = 0x03020302;/* 	  dd->voices[i].params[0].send_dcba = 0x00ff00ff; *//* 	  dd->voices[i].params[0].send_hgfe = 0x00007f7f; *//* 	  dd->voices[i].params[1].send_dcba = 0xff00ff00; *//* 	  dd->voices[i].params[1].send_hgfe = 0x00007f7f; *//* 	  dd->voices[i].params[0].send_routing  = dd->voices[i].params[1].send_routing  = 0x03020100; *//* 	  dd->voices[i].params[0].send_routing2 = dd->voices[i].params[1].send_routing2 = 0x07060504; */	}	else	{	  static int routes[] = {	    0x3210, // Send FL/FR to ??/??, AHI_FRONT_L/MULTI_FRONT_R	    0x3254, // Send RL/RR to ??/??, AHI_REAR_L/AHI_REAR_R	    0x3276, // Send SL/SR to ??/??, AHI_SURROUND_L/AHI_SURROUND_R	    0x3298, // Send C/LFE to ??/??, AHI_CENTER,AHI_LFE	  };	    	  dd->voices[i].params[0].send_dcba = 0x000000ff;	  dd->voices[i].params[0].send_hgfe = 0;	  dd->voices[i].params[1].send_dcba = 0x0000ff00;	  dd->voices[i].params[1].send_hgfe = 0;	  dd->voices[i].params[0].send_routing  = dd->voices[i].params[1].send_routing  = routes[i];	  dd->voices[i].params[0].send_routing2 = dd->voices[i].params[1].send_routing2 = 0;	}	dd->voices[i].params[1].volume_target = 0xffff;	dd->voices[i].params[1].initial_fc = 0xff;	dd->voices[i].params[1].initial_attn = 0x00;	dd->voices[i].params[1].byampl_env_sustain = 0x7f;	dd->voices[i].params[1].byampl_env_decay = 0x7f;      }      else      {	if( dd->card.is_audigy )	{	  dd->voices[i].params[0].send_dcba = 0xffffffff;	  dd->voices[i].params[0].send_hgfe = 0x0000ffff; 	  dd->voices[i].params[0].send_routing  = 0x03020100;	  dd->voices[i].params[0].send_routing2 = 0x07060504;	}	else	{	  dd->voices[i].params[0].send_dcba = 0x000ffff;	  dd->voices[i].params[0].send_hgfe = 0;	  dd->voices[i].params[0].send_routing  = 0x3210;	  dd->voices[i].params[0].send_routing2 = 0;	}      }      DPD(2, "voice: startloop=%x, endloop=%x\n",	  dd->voice.startloop, dd->voice.endloop);      emu10k1_voice_playback_setup( &dd->voices[i] );    }        dd->playback_interrupt_enabled = TRUE;    /* Enable timer interrupts (TIMER_INTERRUPT_FREQUENCY Hz) */    emu10k1_timer_set( &dd->card, 48000 / TIMER_INTERRUPT_FREQUENCY );    emu10k1_irq_enable( &dd->card, INTE_INTERVALTIMERENB );    emu10k1_voices_start( dd->voices, num_voices, 0 );    dd->voices_started = num_voices;    dd->is_playing = TRUE;  }  if( flags & AHISF_RECORD )  {    int adcctl = 0;    /* Find out the recording frequency */    switch( AudioCtrl->ahiac_MixFreq )    {      case 48000:	adcctl = ADCCR_SAMPLERATE_48;	break;      case 44100:	adcctl = ADCCR_SAMPLERATE_44;	break;      case 32000:	adcctl = ADCCR_SAMPLERATE_32;	break;      case 24000:	adcctl = ADCCR_SAMPLERATE_24;	break;      case 22050:	adcctl = ADCCR_SAMPLERATE_22;	break;      case 16000:	adcctl = ADCCR_SAMPLERATE_16;	break;      case 11025:	adcctl = ADCCR_SAMPLERATE_11;	break;      case 8000:	adcctl = ADCCR_SAMPLERATE_8;	break;      default:	return AHIE_UNKNOWN;    }    adcctl |= ADCCR_LCHANENABLE | ADCCR_RCHANENABLE;    /* Allocate a new recording buffer (page aligned!) */    dd->record_buffer = pci_alloc_consistent( dd->card.pci_dev,					      RECORD_BUFFER_SAMPLES * 4,					      &dd->record_dma_handle );    if( dd->record_buffer == NULL )    {      Req( "Unable to allocate %ld bytes for the recording buffer.",	   RECORD_BUFFER_SAMPLES * 4 );      return AHIE_NOMEM;    }    SaveMixerState( dd );    UpdateMonitorMixer( dd );        #ifdef __AMIGAOS4__    // invalidate the whole record_buffer just to be on the safe side    CacheClearE( dd->record_buffer, RECORD_BUFFER_SAMPLES * 4, CACRF_InvalidateD );    #endif    sblive_writeptr( &dd->card, ADCBA, 0, dd->record_dma_handle );    sblive_writeptr( &dd->card, ADCBS, 0, RECORD_BUFFER_SIZE_VALUE );    sblive_writeptr( &dd->card, ADCCR, 0, adcctl );    dd->record_interrupt_enabled = TRUE;    /* Enable ADC interrupts  */    emu10k1_irq_enable( &dd->card, INTE_ADCBUFENABLE );    dd->is_recording = TRUE;  }  return AHIE_OK;}