
#include <exec/exec.h>

#include <devices/ahi.h>
#include <libraries/ahi_sub.h>
#include <libraries/toccata.h>

#include <dos/dos.h>

#include <proto/exec.h>
#include <proto/utility.h>
#include <proto/ahi_sub.h>
#include <clib/toccata_protos.h>
#include <pragmas/toccata_pragmas.h>

#include "toccata.h"

#define EQ ==
#define dd ((struct toccata *) AudioCtrl->ahiac_DriverData)

#define PLAYBUFFERSIZE 128*4      // in samples
#define PLAYIRQSIZE    1<<9       // 5=32, 6=64, ... 9=512
#define RECBUFFERSIZE  128*50     // in samples

extern char __far _LibID[];
extern char __far _LibName[];

extern void __asm RecordFunc(void);
extern void __asm PlayFuncMono(void);
extern void __asm PlayFuncStereo(void);

struct Library        *UtilityBase=NULL;
struct Library        *AHIsubBase=NULL;
struct ToccataBase    *ToccataBase=NULL;

#define INPUTS 5

const static STRPTR Inputs[] =
{ "Line",
  "Aux1",
  "Mic",
  "Mic as Line",
  "Mixer"
};

const static ULONG inputmap[] =
{ TINPUT_Line,
  TINPUT_Aux1,
  TINPUT_Mic,
  TINPUT_Mic,
  TINPUT_Mix
};

const static ULONG micgainmap[] =
{ FALSE,
  FALSE,
  FALSE,
  TRUE,
  FALSE
};


int  __saveds __asm __UserLibInit (register __a6 struct Library *libbase)
{
  AHIsubBase=libbase;

  if(!(UtilityBase=OpenLibrary("utility.library",37)))
  {
    Alert(AN_Unknown|AG_OpenLib|AO_UtilityLib);
    return 1;
  }

  if(!(ToccataBase=(struct ToccataBase *)OpenLibrary("toccata.library",TOCCATA_LIB_VERSION)))
  {
    Alert(AN_Unknown|AG_OpenLib|AO_Unknown);
    return 1;
  }

  return 0;
}

void __saveds __asm __UserLibCleanup (register __a6 struct Library *libbase)
{
  if(UtilityBase)   { CloseLibrary(UtilityBase); UtilityBase=NULL; }
  if(ToccataBase)   { CloseLibrary((struct Library *)ToccataBase); ToccataBase=NULL; }
}

ULONG __asm __saveds intAHIsub_AllocAudio(
    register __a1 struct TagItem *tagList,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  if(AudioCtrl->ahiac_DriverData=AllocVec(sizeof(struct toccata),MEMF_PUBLIC|MEMF_ANY|MEMF_CLEAR))
  {
    dd->t_AHIsubBase=AHIsubBase;
    dd->t_TocSamples=PLAYBUFFERSIZE;

/*
** This one is just to keep the $#£@$£#"% T_RawPlayback() happy.
** Thank you so very much, MacroSystem, for documenting that TT_ErrorTask and
** TT_ErrorMask must be specified for T_RawPlayback() but not T_Capture().
** After all, spending four days trying to figure out how to get T_RawPlayback()
** work is exactly my idea of fun. Or maybe not.
** Funny it should have to take a disassembly of 'toccata.library' to figure out
** how to use it.
*/

    dd->t_ErrorTask=FindTask(NULL);
    dd->t_ErrorSignal=AllocSignal(-1);
    if(dd->t_ErrorSignal != -1)
    {
      AudioCtrl->ahiac_MixFreq=T_FindFrequency(AudioCtrl->ahiac_MixFreq);

      if(ToccataBase->tb_HardInfo)         // Check if hardware is present...
      {
        T_SaveSettings(0);                 // Save state
        T_Stop(TSF_DONTSAVECACHE);
        T_SetPartTags(                     // Reset
            PAT_InputVolumeLeft,0,
            PAT_InputVolumeRight,0,
            PAT_OutputVolumeLeft,0,
            PAT_OutputVolumeRight,0,
            PAT_LoopbackVolume,-64,
            PAT_Input,TINPUT_Line,
            PAT_Mode,TMODE_LINEAR_16_S,
            PAT_Frequency,ToccataBase->tb_HardInfo->hi_MaxFrequency,
            TAG_DONE);
        return AHISF_KNOWSTEREO|AHISF_CANRECORD|AHISF_MIXING|AHISF_TIMING;
      }
    }
  }
  return AHISF_ERROR;
}

void __asm __saveds intAHIsub_FreeAudio(
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  if(AudioCtrl->ahiac_DriverData)
  {
    FreeSignal(dd->t_ErrorSignal);
    FreeVec(AudioCtrl->ahiac_DriverData);
    AudioCtrl->ahiac_DriverData=NULL;
    T_LoadSettings(0);                 // Restore state
  }
}



ULONG __asm __saveds intAHIsub_Start(
    register __d0 ULONG Flags,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{

  AHIsub_Stop(AHISF_PLAY|AHISF_RECORD,AudioCtrl);       // Only half duplex!

  if(Flags & AHISF_PLAY)
  {
    ULONG toccata_mode;
    ULONG toccata_size;

    if(!(dd->t_PlaySoftInt=AllocVec(sizeof(struct Interrupt),MEMF_PUBLIC|MEMF_ANY|MEMF_CLEAR)))
      return AHIE_NOMEM;

    switch(AudioCtrl->ahiac_BuffType)
    {
      case AHIST_M16S:
        dd->t_PlaySoftInt->is_Code=(void (* )())PlayFuncMono;
        toccata_mode=TMODE_LINEAR_16;
        toccata_size=dd->t_TocSamples<<1;
        break;
      case AHIST_S16S:
        dd->t_PlaySoftInt->is_Code=(void (* )())PlayFuncStereo;
        toccata_mode=TMODE_LINEAR_16_S;
        toccata_size=dd->t_TocSamples<<2;
        break;
      default:
        return AHIE_BADSAMPLETYPE;
    }

    if(!(dd->t_SampBuffer1=AllocVec(toccata_size,MEMF_PUBLIC|MEMF_ANY)))
      return AHIE_NOMEM;

    if(!(dd->t_SampBuffer2=AllocVec(toccata_size,MEMF_PUBLIC|MEMF_ANY)))
      return AHIE_NOMEM;

    if(!(dd->t_MixBuffer=AllocVec(AudioCtrl->ahiac_BuffSize,MEMF_PUBLIC|MEMF_ANY)))
      return AHIE_NOMEM;

    dd->t_PlaySoftInt->is_Node.ln_Type=NT_INTERRUPT;
    dd->t_PlaySoftInt->is_Node.ln_Name=_LibName;
    dd->t_PlaySoftInt->is_Data=AudioCtrl;

    if(!T_RawPlaybackTags(
          TT_ErrorTask, dd->t_ErrorTask,
          TT_ErrorMask, (1L << dd->t_ErrorSignal),
          TT_Mode, toccata_mode,
          TT_Frequency, AudioCtrl->ahiac_MixFreq,
          TT_RawBuffer1, dd->t_SampBuffer1,
          TT_RawBuffer2, dd->t_SampBuffer2,
          TT_BufferSize, toccata_size,
          TT_RawIrqSize, PLAYIRQSIZE,
          TT_RawInt, dd->t_PlaySoftInt,
          TAG_DONE))
    {
      T_Stop(TSF_DONTSAVECACHE);
      return AHIE_UNKNOWN;
    }
  }

  if(Flags & AHISF_RECORD)
  {
    if(!(dd->t_RecBuffer=AllocVec(RECBUFFERSIZE<<2,MEMF_PUBLIC|MEMF_ANY)))
      return AHIE_NOMEM;
    if(!(dd->t_RecMessage=AllocVec(sizeof(struct AHIRecordMessage),MEMF_PUBLIC|MEMF_ANY|MEMF_CLEAR)))
      return AHIE_NOMEM;

    dd->t_RecMessage->ahirm_Type=AHIST_S16S;
    dd->t_RecMessage->ahirm_Buffer=dd->t_RecBuffer;

    if(!T_CaptureTags(
        TT_BufferSize,RECBUFFERSIZE<<2,
        TT_Save, RecordFunc,
        TT_CBParamA1, AudioCtrl,
        TT_Mode, TMODE_LINEAR_16_S,
        TT_Frequency, AudioCtrl->ahiac_MixFreq,
        TT_Flags, TTF_READYRETURN,
        TAG_DONE))
    {
      T_Stop(TSF_DONTSAVECACHE);
      return AHIE_UNKNOWN;
    }
  }

  return AHIE_OK;
}

/*
void __asm __saveds __interrupt intAHIsub_Update(
    register __d0 ULONG Flags,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
}
*/

void __asm __saveds intAHIsub_Stop(
    register __d0 ULONG Flags,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  if(Flags & AHISF_PLAY)
  {
    T_Stop(TSF_DONTSAVECACHE);

    FreeVec(dd->t_MixBuffer);
    dd->t_MixBuffer=NULL;
    FreeVec(dd->t_SampBuffer1);
    dd->t_SampBuffer1=NULL;
    FreeVec(dd->t_SampBuffer2);
    dd->t_SampBuffer2=NULL;
    FreeVec(dd->t_PlaySoftInt);
    dd->t_PlaySoftInt=NULL;
  }

  if(Flags & AHISF_RECORD)
  {
    T_Stop(TSF_DONTSAVECACHE);

    FreeVec(dd->t_RecBuffer);
    dd->t_RecBuffer=NULL;
  }
}



LONG __asm __saveds intAHIsub_GetAttr(
    register __d0 ULONG Attribute,
    register __d1 LONG Argument,
    register __d2 LONG Default,
    register __a1 struct TagItem *tagList,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl)
{
  switch(Attribute)
  {
    case AHIDB_Bits:
      return 16;
    case AHIDB_Frequencies:
    {
      ULONG freq=NULL;
      LONG  freqs=0;
      while(freq=T_NextFrequency(freq))
        freqs++;
      return freqs;
    }
    case AHIDB_Frequency: // Index->Frequency
    {
      ULONG freq=NULL;
      LONG  i;
      for(i=0; i<=Argument ; i++)
        freq=T_NextFrequency(freq);
      return (LONG) freq;
    }    
    case AHIDB_Index: // Frequency->Index
    {  
      ULONG freq=NULL,realfreq=T_FindFrequency(Argument);
      LONG  index=0;
      while((freq=T_NextFrequency(freq)) != realfreq)
        index++;
      return index;
    }
    case AHIDB_Author:
      return (LONG) "Martin 'Leviticus' Blom";
    case AHIDB_Copyright:
      return (LONG) "Public Domain";
    case AHIDB_Version:
      return (LONG) _LibID;
    case AHIDB_Annotation:
      return (LONG) "Based on code by Pauli Porkka, Peter Kunath and Frank Riffel.";
    case AHIDB_Record:
      return TRUE;
    case AHIDB_FullDuplex:
      return FALSE;
    case AHIDB_Realtime:
      return TRUE;
    case AHIDB_MaxPlaySamples:
      return Default+PLAYBUFFERSIZE;
    case AHIDB_MaxRecordSamples:
      return RECBUFFERSIZE;
    case AHIDB_MinMonitorVolume:
      return 0x00000;
    case AHIDB_MaxMonitorVolume:
      return 0x10000;
    case AHIDB_MinInputGain:
      return 0x10000;
    case AHIDB_MaxInputGain:
      return 0xd55d0;           // 13.335<<16 == +22.5 dB
    case AHIDB_MinOutputVolume:
      return 0x00000;
    case AHIDB_MaxOutputVolume:
      return 0x10000;
    case AHIDB_Inputs:
      return INPUTS;
    case AHIDB_Input:
    {
      return (LONG) Inputs[Argument];
    }
    case AHIDB_Outputs:
      return 1;
    case AHIDB_Output:
      return (LONG) "Line";     // We have only one output!
    default:
      return Default;
  }
}

const static LONG negboundaries[] =
{
  65536,55141,46395,39037,32845,27636,23253,19565,16461,13850,11654,9805,8250,
  6941,5840,4914,4135,3479,2927,2463,2072,1743,1467,1234,1038,873,735,618,520,
  438,368,310,260,219,184,155,130,110,92,77,65,55,46,39,32,27,23,19,16,13,11,9,
  8,6,5,4,4,3,2,2,2,1,1,1,0
};

LONG fixed2negdbvalue( LONG volume)
{
  LONG i=0;

  while(volume >= negboundaries[i])
    i++;
  return(-i);
}

const static LONG posboundaries[] =
{
  65536,77889,92572,110022,130761,155410,184705,219522,260903,
  310084,368536,438005,520570,618699,735326,873936
};

LONG fixed2posdbvalue( LONG volume)
{
  LONG i=0;

  while((volume >= posboundaries[i+1]) && i<15)
    i++;
  return(i);
}

LONG __asm __saveds __interrupt intAHIsub_HardwareControl(
    register __d0 ULONG attribute,
    register __d1 LONG argument,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  LONG rc=TRUE;

  if(ToccataBase->tb_HardInfo)         // Check if hardware is present...
  {
    switch (attribute)
    {
      case AHIC_MonitorVolume:
        T_SetPartTags(PAT_LoopbackVolume, fixed2negdbvalue(argument), TAG_DONE);
        break;
      case AHIC_MonitorVolume_Query:
        T_GetPartTags(PAT_LoopbackVolume, &rc, TAG_DONE);
        rc=negboundaries[-rc];
        break;
      case AHIC_InputGain:
        T_SetPartTags(PAT_InputVolumeLeft, fixed2posdbvalue(argument),
                      PAT_InputVolumeRight, fixed2posdbvalue(argument), TAG_DONE);
        break;
      case AHIC_InputGain_Query:
        T_GetPartTags(PAT_InputVolumeLeft, &rc, TAG_DONE);
        rc=negboundaries[rc];
        break;
      case AHIC_OutputVolume:
        T_SetPartTags(PAT_OutputVolumeLeft, fixed2negdbvalue(argument),
                      PAT_OutputVolumeRight, fixed2negdbvalue(argument), TAG_DONE);
        break;
      case AHIC_OutputVolume_Query:
        T_GetPartTags(PAT_OutputVolumeLeft, &rc, TAG_DONE);
        rc=negboundaries[-rc];
        break;
      case AHIC_Input:
        dd->t_Input=argument;
        T_SetPartTags(PAT_Input,   inputmap[argument],
                      PAT_MicGain, micgainmap[argument], TAG_DONE);
        break;
      case AHIC_Input_Query:
        rc=dd->t_Input;
        break;
      case AHIC_Output_Query:
        rc=0;                           // There is only one output
        break;
      default:
        rc=FALSE;
        break;
    }
  }
  else
    rc=FALSE;
  return rc;
}
