
#define NO_PROTOS
#define NO_SAS_PRAGMAS
#include <iffp/8svx.h>
#undef NO_PROTOS
#undef NO_SAS_PRAGMAS

#include <exec/exec.h>
#include <devices/ahi.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <graphics/gfxbase.h>
#include <libraries/ahi_sub.h>
#include <libraries/asl.h>
#include <datatypes/datatypes.h>
#include <datatypes/soundclass.h>
#include <proto/asl.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/utility.h>
#include <proto/datatypes.h>
#include <proto/ahi_sub.h>
#include <stdlib.h>
#include "filesave.h"

#define dd ((struct filesave *) AudioCtrl->ahiac_DriverData)

#define SAVEBUFFERSIZE 100000   // in samples (min)
#define RECBUFFERSIZE  10000    // in samples

struct AIFCheader {
  ULONG                 FORMid;
  ULONG                 FORMsize;
  ULONG                 AIFCid;

  ULONG                 FVERid;
  ULONG                 FVERsize;
  FormatVersionHeader   FVERchunk;

  ULONG                 COMMid;
  ULONG                 COMMsize;
  ExtCommonChunk        COMMchunk;

  ULONG                 SSNDid;
  ULONG                 SSNDsize;
  SampledSoundHeader    SSNDchunk;
};

struct AIFFheader {
  ULONG                 FORMid;
  ULONG                 FORMsize;
  ULONG                 AIFFid;

  ULONG                 COMMid;
  ULONG                 COMMsize;
  CommonChunk           COMMchunk;

  ULONG                 SSNDid;
  ULONG                 SSNDsize;
  SampledSoundHeader    SSNDchunk;
};

struct EIGHTSVXheader {
  ULONG                 FORMid;
  ULONG                 FORMsize;
  ULONG                 EIGHTSVXid;

  ULONG                 VHDRid;
  ULONG                 VHDRsize;
  Voice8Header          VHDRchunk;

  ULONG                 BODYid;
  ULONG                 BODYsize;
};

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

extern void KPrintF(char *fmt,...);

#define FREQUENCIES 23
const ULONG  frequency[FREQUENCIES] =
{
  5513,
  6615,
  8000,     // µ- and A-Law
  9600,     // DAT/5
  11025,    // CD/4
  12000,    // DAT/4
  14700,    // CD/3
  16000,    // DAT/3
  17640,    // CD/2.5
  18900,
  19200,    // DAT/2.5
  22050,    // CD/2
  24000,    // DAT/2
  27429,
  29400,    // CD/1.5
  32000,    // DAT/1.5
  33075,
  37800,
  44056,    // Some kind of video rate
  44100,    // CD
  48000,    // DAT
  88200,    // CD*2
  96000     // DAT*2
};

extern void SlaveEntry(void);
extern void RecSlaveEntry(void);
void ulong2extended (ULONG in, extended *ex);

struct DosLibrary     *DOSBase        = NULL;
struct Library        *UtilityBase    = NULL;
struct Library        *AslBase        = NULL;
struct IntuitionBase  *IntuitionBase  = NULL;
struct Library        *AHIsubBase     = NULL;
struct Library        *DataTypesBase  = NULL;
struct GfxBase        *GfxBase        = NULL;

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

  if(!(DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", 37)))
  {
    Alert(AN_Unknown|AG_OpenLib|AO_DOSLib);
    return 1;
  }

  if(!(GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 37)))
  {
    Alert(AN_Unknown|AG_OpenLib|AO_GraphicsLib);
    return 1;
  }

  if(!(IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 37)))
  {
    Alert(AN_Unknown|AG_OpenLib|AO_Intuition);
    return 1;
  }

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

// Don't fail if these ones don't open!

  if(!(AslBase = OpenLibrary("asl.library", 37)))
  {
    struct EasyStruct req = {
        sizeof (struct EasyStruct), 0, _LibName,
        "Cannot open 'asl.library' v37", "OK"};

   	EasyRequest( NULL, &req, NULL, NULL );
  }

  DataTypesBase = OpenLibrary("datatypes.library",39);

  return 0;
}

void __saveds __asm __UserLibCleanup (register __a6 struct Library *libbase)
{
  if(AslBase)       { CloseLibrary(AslBase); AslBase = NULL; }
  if(DOSBase)       { CloseLibrary((struct Library *)DOSBase); DOSBase = NULL; }
  if(GfxBase)       { CloseLibrary((struct Library *)GfxBase); GfxBase = NULL; }
  if(IntuitionBase) { CloseLibrary((struct Library *)IntuitionBase); IntuitionBase = NULL; }
  if(UtilityBase)   { CloseLibrary(UtilityBase); UtilityBase = NULL; }
  if(DataTypesBase) { CloseLibrary(DataTypesBase); DataTypesBase = NULL; }
}

ULONG __asm __saveds intAHIsub_AllocAudio(
    register __a1 struct TagItem *tagList,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  char *ext = "";

  if(AslBase == NULL)
  {
    return AHISF_ERROR;
  }

  if(AudioCtrl->ahiac_DriverData = AllocVec(sizeof(struct filesave),MEMF_CLEAR))
  {
    dd->fs_AHIsubBase       = AHIsubBase;
    dd->fs_DisableSignal    = -1;
    dd->fs_EnableSignal     = -1;
    dd->fs_SlaveSignal      = -1;
    dd->fs_MasterSignal     = AllocSignal(-1);
    dd->fs_MasterTask       = (struct Process *) FindTask(NULL);
    dd->fs_RecSlaveSignal   = -1;
    dd->fs_RecMasterSignal  = AllocSignal(-1);
  }
  else
  {
    return AHISF_ERROR;
  }

  if((dd->fs_MasterSignal == -1) || (dd->fs_RecMasterSignal == -1))
  {
    return AHISF_ERROR;
  }

  dd->fs_Format = GetTagData(AHIDB_FileSaveFormat, FORMAT_8SVX, tagList);

  switch(dd->fs_Format)
  {
    case FORMAT_8SVX:
      ext = ".8SVX";
      break;

    case FORMAT_AIFF:
      ext = ".AIFF";
      break;

    case FORMAT_AIFC:
      ext = ".AIFC";
      break;

    case FORMAT_S16:
      break;

    default:
      break;
  }

  if(!(dd->fs_FileReq = AllocAslRequestTags(ASL_FileRequest,
      ASLFR_InitialFile,  ext,
      ASLFR_DoSaveMode,   TRUE,
      ASLFR_RejectIcons,  TRUE,
      ASLFR_TitleText,    _LibID,
      TAG_DONE)))
  {
    return AHISF_ERROR;
  }

  if(!(dd->fs_RecFileReq = AllocAslRequestTags(ASL_FileRequest,
      ASLFR_RejectIcons,  TRUE,
      ASLFR_TitleText,    "Select a sound sample",
      TAG_DONE)))
  {
    return AHISF_ERROR;
  }

  return AHISF_KNOWHIFI|AHISF_KNOWSTEREO|AHISF_CANRECORD|AHISF_MIXING|AHISF_TIMING;
}

void __asm __saveds intAHIsub_FreeAudio(
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  if(AudioCtrl->ahiac_DriverData)
  {
    FreeAslRequest(dd->fs_FileReq);
    FreeAslRequest(dd->fs_RecFileReq);
    FreeSignal(dd->fs_MasterSignal);
    FreeSignal(dd->fs_RecMasterSignal);
    FreeVec(AudioCtrl->ahiac_DriverData);
    AudioCtrl->ahiac_DriverData = NULL;
  }
}

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

  AHIsub_Stop(Flags, AudioCtrl);

  if(Flags & AHISF_PLAY)
  {
    ULONG savebufferlength;

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

    dd->fs_SaveBufferSize = AudioCtrl->ahiac_MaxBuffSamples;

    // S16 has two buffers (L/R) instead
    if((AudioCtrl->ahiac_Flags & AHIACF_STEREO) && dd->fs_Format != FORMAT_S16)
    {
      dd->fs_SaveBufferSize <<=1;
    }

    if(dd->fs_SaveBufferSize < SAVEBUFFERSIZE)
    {
      dd->fs_SaveBufferSize = SAVEBUFFERSIZE;
    }

    savebufferlength = dd->fs_SaveBufferSize;


    switch(dd->fs_Format)
    {
      case FORMAT_8SVX:
        break;

      case FORMAT_AIFF:
        savebufferlength <<= 1;
        break;

      case FORMAT_AIFC:
        savebufferlength <<= 1;
        break;

      case FORMAT_S16:
        savebufferlength <<= 1;
        break;

      default:
        break;
    }


    if(!(dd->fs_SaveBuffer = AllocVec(savebufferlength, MEMF_ANY)))
    {
      return AHIE_NOMEM;
    }

    if ((AudioCtrl->ahiac_Flags & AHIACF_STEREO) && dd->fs_Format == FORMAT_S16)
    {
      if(!(dd->fs_SaveBuffer2 = AllocVec(savebufferlength, MEMF_ANY)))
      {
        return AHIE_NOMEM;
      }
    }

    if(AslRequestTags(dd->fs_FileReq,TAG_DONE))
    {

      Forbid();

      if(dd->fs_SlaveTask = CreateNewProcTags(
          NP_Entry,     SlaveEntry,
          NP_Name,      _LibName,
          NP_Priority,  -1,               // It's a number cruncher...
          NP_StackSize, 10000,
          TAG_DONE))
      {
        dd->fs_SlaveTask->pr_Task.tc_UserData = AudioCtrl;
      }

      Permit();

      if(dd->fs_SlaveTask)
      {
        Wait(1L<<dd->fs_MasterSignal);  // Wait for slave to come alive
        if(dd->fs_SlaveTask == NULL)    // Is slave alive or dead?
        {
          return AHIE_UNKNOWN;
        }
      }
      else
      {
        return AHIE_NOMEM;
      }
    }
    else
    {
      if(IoErr())
      {
        return AHIE_NOMEM;    //error occured
      }
      else
      {
        return AHIE_ABORTED;  //requester cancelled
      }
    }
  }

  if(Flags & AHISF_RECORD)
  {
    if(!(dd->fs_RecBuffer = AllocVec(RECBUFFERSIZE*4,MEMF_ANY)))
    {
      return AHIE_NOMEM;
    }

    if(AslRequestTags(dd->fs_RecFileReq,TAG_DONE))
    {
      Delay(TICKS_PER_SECOND);         // Wait for window to close etc...

      Forbid();

      if(dd->fs_RecSlaveTask = CreateNewProcTags(
          NP_Entry,     RecSlaveEntry,
          NP_Name,      _LibName,
          NP_Priority,  1,               // Make it steady...
          TAG_DONE))
      {
        dd->fs_RecSlaveTask->pr_Task.tc_UserData = AudioCtrl;
      }

      Permit();

      if(dd->fs_RecSlaveTask)
      {
        Wait(1L<<dd->fs_RecMasterSignal);  // Wait for slave to come alive
        if(dd->fs_RecSlaveTask == NULL)    // Is slave alive or dead?
        {
          return AHIE_UNKNOWN;
        }
      }
      else
      {
        return AHIE_NOMEM;
      }
    }
    else
    {
      if(IoErr())
      {
        return AHIE_NOMEM;    //error occured
      }
      else
      {
        return AHIE_ABORTED;  //requester cancelled
      }
    }
  }

  return AHIE_OK;
}


void __asm __saveds 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)
  {
    if(dd->fs_SlaveTask)
    {
      if(dd->fs_SlaveSignal != -1)
      {
        Signal((struct Task *)dd->fs_SlaveTask,1L<<dd->fs_SlaveSignal); // Kill him!
      }
      Wait(1L<<dd->fs_MasterSignal);  // Wait for slave to die
    }
    FreeVec(dd->fs_MixBuffer);
    dd->fs_MixBuffer = NULL;
    FreeVec(dd->fs_SaveBuffer);
    FreeVec(dd->fs_SaveBuffer2);
    dd->fs_SaveBuffer = NULL;
    dd->fs_SaveBuffer2 = NULL;
  }

  if(Flags & AHISF_RECORD)
  {
    if(dd->fs_RecSlaveTask)
    {
      if(dd->fs_RecSlaveSignal != -1)
      {
        Signal((struct Task *)dd->fs_RecSlaveTask,1L<<dd->fs_RecSlaveSignal); // Kill him!
      }
      Wait(1L<<dd->fs_RecMasterSignal);  // Wait for slave to die
    }
    FreeVec(dd->fs_RecBuffer);
    dd->fs_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)
{
  int i;

  switch(Attribute)
  {
    case AHIDB_Bits:
      switch (GetTagData(AHIDB_FileSaveFormat,FORMAT_8SVX,tagList))
      {
        case FORMAT_8SVX:
          return 8;

        case FORMAT_AIFF:
          return 16;

        case FORMAT_AIFC:
          return 16;

        case FORMAT_S16:
          return 16;

        default:
          return Default;
      }

    case AHIDB_Frequencies:
      return FREQUENCIES;

    case AHIDB_Frequency: // Index->Frequency
      return (LONG) frequency[Argument];

    case AHIDB_Index: // Frequency->Index
      if(Argument <= frequency[0])
      {
        return 0;
      }
      if(Argument >= frequency[FREQUENCIES-1])
      {
        return FREQUENCIES-1;
      }
      for(i = 1;i<FREQUENCIES;i++)
      {
        if(frequency[i]>Argument)
        {
          if( (Argument-frequency[i-1]) < (frequency[i]-Argument) )
          {
            return i-1;
          }
          else
          {
            return i;
          }
        }
      }
      return 0;  // Will not happen

    case AHIDB_Author:
      return (LONG) "Martin 'Leviticus' Blom";

    case AHIDB_Copyright:
      return (LONG) "Public Domain";

    case AHIDB_Version:
      return (LONG) _LibID;

    case AHIDB_Record:
      return TRUE;

    case AHIDB_FullDuplex:
      return TRUE;

    case AHIDB_MaxRecordSamples:
      return RECBUFFERSIZE;

    case AHIDB_Realtime:
      return FALSE;

    case AHIDB_Inputs:
      return 1;

    case AHIDB_Input:
      return (LONG) "File";    // We have only one input!

    case AHIDB_Outputs:
      return 1;

    case AHIDB_Output:
      return (LONG) "File";    // We have only one output!

    default:
      return Default;
  }
}


ULONG __asm __saveds intAHIsub_HardwareControl(
    register __d0 ULONG attribute,
    register __d1 LONG argument,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  return NULL;
}

/*
** Unused LVOs follows...
*/

ULONG __asm __saveds intAHIsub_SetVol(
    register __d0 UWORD channel,
    register __d1 Fixed volume,
    register __d2 sposition pan,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl,
    register __d3 ULONG Flags)
{
  return AHIS_UNKNOWN;
}

ULONG __asm __saveds intAHIsub_SetFreq(
    register __d0 UWORD channel,
    register __d1 ULONG freq,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl,
    register __d2 ULONG flags )
{
  return AHIS_UNKNOWN;
}

ULONG __asm __saveds intAHIsub_SetSound(
    register __d0 UWORD channel,
    register __d1 UWORD sound,
    register __d2 ULONG offset,
    register __d3 LONG length,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl,
    register __d4 ULONG flags )
{
  return AHIS_UNKNOWN;
}


ULONG __asm __saveds intAHIsub_SetEffect (
    register __a0 APTR effect,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  return AHIS_UNKNOWN;
}

ULONG __asm __saveds intAHIsub_LoadSound(
    register __d0 UWORD sound,
    register __d1 ULONG type,
    register __a0 APTR info,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  return AHIS_UNKNOWN;
}

ULONG __asm __saveds intAHIsub_UnloadSound(
    register __d0 UWORD sound,
    register __a2 struct AHIAudioCtrlDrv *AudioCtrl )
{
  return AHIS_UNKNOWN;
}



/*
** The slave process
*/

void __asm __saveds SlaveTask(register __a2 struct AHIAudioCtrlDrv *AudioCtrl)
// SlaveEntry() will set up register a2 and a6 for us.
{
  struct EIGHTSVXheader EIGHTSVXheader = // All NULLs will be filled later.
  { 
    ID_FORM, NULL, ID_8SVX,
    ID_VHDR, sizeof(Voice8Header),
    {
      NULL,
      0,
      0,
      NULL,
      1,
      sCmpNone,
      0x10000
    },
    ID_BODY, NULL
  };

  struct AIFFheader AIFFheader = // All NULLs will be filled later.
  { 
    ID_FORM, NULL, ID_AIFF,
    ID_COMM, sizeof(CommonChunk),
    {
      NULL,
      NULL,
      16,
      {
        NULL
      }
    },
    ID_SSND, NULL,
    {
      0,
      0
    }
  };

  struct AIFCheader AIFCheader = // All NULLs will be filled later.
  { 
    ID_FORM, NULL, ID_AIFC,
    ID_FVER, sizeof(FormatVersionHeader), 
    {
      AIFCVersion1
    },
    ID_COMM, sizeof(ExtCommonChunk),
    {
      NULL,
      NULL,
      16,
      {
        NULL
      },
      NO_COMPRESSION,
      sizeof("not compressed") - 1,
      'n','o','t',' ','c','o','m','p','r','e','s','s','e','d'
    },
    ID_SSND, NULL,
    {
      0,
      0
    }
  };

  struct STUDIO16FILE S16header = // All NULLs will be filled later.
  {
    S16FID,
    NULL,
    S16FINIT,
    S16_VOL_0,
    0,
    0,
    NULL,
    0,
    0,
    NULL,
    NULL,
    0,
    NULL,
    0,
    {
      0
    }
  };

  struct EasyStruct req =
  {
    sizeof (struct EasyStruct),
    0,
    _LibID,
    "Rendering finished.\nTo futher improve the quality of the sample,\n"
    "you can raise the volume to %ld%%%sand render again.",
    "OK",
  };

  BPTR lock = NULL,cd = NULL,file = NULL, file2 = NULL;
  ULONG signals, i, maxVolume = 0, samples, length;
  ULONG offset = 0, bytesInBuffer = 0, samplesWritten = 0, bytesWritten = 0;

// We cannot handle stereo 8SVXs!
  if( (dd->fs_Format == FORMAT_8SVX) &&
      (AudioCtrl->ahiac_Flags & AHIACF_STEREO) )
  {
    goto quit;
  }

  if((dd->fs_DisableSignal = AllocSignal(-1)) == -1)
  {
    goto quit;
  }
  if((dd->fs_EnableSignal = AllocSignal(-1)) == -1)
  {
    goto quit;
  }
  if((dd->fs_SlaveSignal = AllocSignal(-1)) == -1)
  {
    goto quit;
  }

  if(!(lock = Lock(dd->fs_FileReq->fr_Drawer, ACCESS_READ)))
  {
    goto quit;
  }

  cd = CurrentDir(lock);

  switch(dd->fs_Format)
  {
    case FORMAT_8SVX:
      if(!(file = Open(dd->fs_FileReq->fr_File, MODE_NEWFILE))) goto quit;
      Write(file, &EIGHTSVXheader, sizeof EIGHTSVXheader);
      break;

    case FORMAT_AIFF:
      if(!(file = Open(dd->fs_FileReq->fr_File, MODE_NEWFILE))) goto quit;
      Write(file, &AIFFheader, sizeof AIFFheader);
      break;

    case FORMAT_AIFC:
      if(!(file = Open(dd->fs_FileReq->fr_File, MODE_NEWFILE))) goto quit;
      Write(file, &AIFCheader, sizeof AIFCheader);
      break;

    case FORMAT_S16:
      if (AudioCtrl->ahiac_Flags & AHIACF_STEREO)
      {
        char filename[256];
        int len;

        strncpy (filename, dd->fs_FileReq->fr_File, sizeof(filename) - 3);
        len = strlen(filename);

        if(len >= 2 && filename[len - 2] == '_'
           && (filename[len - 1] == 'L' || filename[len - 1] == 'R'))
        {
          filename[len - 1] = 'L';
        }
        else
        {
          strcat (filename, "_L");
        }

        if(!(file = Open(filename, MODE_NEWFILE))) goto quit;

        filename[strlen(filename) - 1] = 'R';
        if(!(file2 = Open(filename, MODE_NEWFILE))) goto quit;

        Write(file, &S16header, sizeof S16header);
        Write(file2, &S16header, sizeof S16header);
      }
      else
      {
        if(!(file = Open(dd->fs_FileReq->fr_File, MODE_NEWFILE))) goto quit;
        Write(file, &S16header, sizeof S16header);
      }
      break;
  }

  // Everything set up. Tell Master we're alive and healthy.
  Signal((struct Task *)dd->fs_MasterTask,1L<<dd->fs_MasterSignal);

  for(;;)
  {
    signals = SetSignal(0L,0L);
    if(signals & (SIGBREAKF_CTRL_C | 1L<<dd->fs_SlaveSignal))
    {
      break;
    }

    if(signals & (1L<<dd->fs_EnableSignal | 1L<<dd->fs_DisableSignal) == 1L<<dd->fs_DisableSignal)
    {
      Wait(1L<<dd->fs_EnableSignal);
    }

    CallHookPkt(AudioCtrl->ahiac_PlayerFunc, AudioCtrl, NULL);
    CallHookPkt(AudioCtrl->ahiac_MixerFunc, AudioCtrl, dd->fs_MixBuffer);

    samples = AudioCtrl->ahiac_BuffSamples;

    if(AudioCtrl->ahiac_Flags & AHIACF_STEREO)
    {
      samples <<= 1;
    }

// Search for loudest part in sample
    if(AudioCtrl->ahiac_Flags & AHIACF_HIFI)
    {
      for(i = 0; i < samples; i++)
        if(abs(((LONG *)dd->fs_MixBuffer)[i]) > maxVolume)
          maxVolume = abs(((LONG *)dd->fs_MixBuffer)[i]);
    }
    else
    {
      for(i = 0; i< samples; i++)
        if(abs(((WORD *)dd->fs_MixBuffer)[i]) > maxVolume)
          maxVolume = abs(((WORD *)dd->fs_MixBuffer)[i]);
    }

    if((AudioCtrl->ahiac_Flags & AHIACF_STEREO) && dd->fs_Format == FORMAT_S16)
    {
      samples >>= 1;  // Two buffers instead
    }

    if(offset+samples >= dd->fs_SaveBufferSize)
    {
      if(Write(file, dd->fs_SaveBuffer, bytesInBuffer) != bytesInBuffer)
      {
        break;
      }
      if(file2 != NULL) {
        if(Write(file2, dd->fs_SaveBuffer2, bytesInBuffer) != bytesInBuffer)
        {
          break;
        }
      }
      offset = 0;
      bytesInBuffer = 0;
    }

    switch(dd->fs_Format)
    {
      case FORMAT_8SVX:
        if(AudioCtrl->ahiac_Flags & AHIACF_HIFI)
        {
          BYTE *dest = &((BYTE *) dd->fs_SaveBuffer)[offset];
          LONG *source = dd->fs_MixBuffer;

          for(i = 0; i < samples; i++)
            *dest++ = *source++ >> 24;
        }
        else
        {
          BYTE *dest = &((BYTE *) dd->fs_SaveBuffer)[offset];
          WORD *source = dd->fs_MixBuffer;

          for(i = 0; i < samples; i++)
            *dest++ = *source++ >> 8;
        }
        length = samples;
        break;

      case FORMAT_AIFF:
      case FORMAT_AIFC:
        if(AudioCtrl->ahiac_Flags & AHIACF_HIFI)
        {
          WORD *dest = &((WORD *) dd->fs_SaveBuffer)[offset];
          LONG *source = dd->fs_MixBuffer;

          for(i = 0; i < samples; i++)
          {
            *dest++ = *source++ >> 16;
          }
        }
        else
        {
          WORD *dest = &((WORD *) dd->fs_SaveBuffer)[offset];
          WORD *source = dd->fs_MixBuffer;

          for(i = 0; i < samples; i++)
          {
            *dest++ = *source++;
          }
        }
        length = samples*2;
        break;

      case FORMAT_S16:
        switch(AudioCtrl->ahiac_Flags & (AHIACF_HIFI | AHIACF_STEREO))
        {
          case 0:
          {
            WORD *dest = &((WORD *) dd->fs_SaveBuffer)[offset];
            WORD *source = dd->fs_MixBuffer;

            for(i = 0; i < samples; i++)
            {
              *dest++ = *source++;
            }

            length = i*2;
            break;
          }

          case AHIACF_STEREO:
          {
            WORD *dest1 = &((WORD *) dd->fs_SaveBuffer)[offset];
            WORD *dest2 = &((WORD *) dd->fs_SaveBuffer2)[offset];
            WORD *source = dd->fs_MixBuffer;

            for(i = 0; i < samples; i++)
            {
              *dest1++ = *source++;
              *dest2++ = *source++;
            }

            length = i*2;
            break;
          }

          case AHIACF_HIFI:
          {
            WORD *dest = &((WORD *) dd->fs_SaveBuffer)[offset];
            LONG *source = dd->fs_MixBuffer;

            for(i = 0; i < samples; i++)
            {
              *dest++ = *source++ >> 16;
            }

            length = i*2;
            break;
          }

          case (AHIACF_HIFI | AHIACF_STEREO):
          {
            WORD *dest1 = &((WORD *) dd->fs_SaveBuffer)[offset];
            WORD *dest2 = &((WORD *) dd->fs_SaveBuffer2)[offset];
            LONG *source = dd->fs_MixBuffer;

            for(i = 0; i < samples; i++)
            {
              *dest1++ = *source++ >> 16;
              *dest2++ = *source++ >> 16;
            }

            break;
          }
        }

        length = samples*2;
        break;
    }

    offset          += samples;
    samplesWritten  += AudioCtrl->ahiac_BuffSamples;
    bytesWritten    += length;
    bytesInBuffer   += length;

  }

  Write(file, dd->fs_SaveBuffer, bytesInBuffer);
  if(file2 != NULL)
  {
    Write(file2, dd->fs_SaveBuffer2, bytesInBuffer);
  }

  switch(dd->fs_Format)
  {
    case FORMAT_8SVX:
      EIGHTSVXheader.FORMsize = sizeof(EIGHTSVXheader)-8+bytesWritten;
      EIGHTSVXheader.VHDRchunk.oneShotHiSamples = samplesWritten;
      EIGHTSVXheader.VHDRchunk.samplesPerSec = AudioCtrl->ahiac_MixFreq;
      EIGHTSVXheader.BODYsize = bytesWritten;
      if(bytesWritten & 1)
        FPutC(file,'\0');   // Pad to even
      Seek(file,0,OFFSET_BEGINNING);
      Write(file,&EIGHTSVXheader,sizeof EIGHTSVXheader);
      break;

    case FORMAT_AIFF:
      AIFFheader.FORMsize = sizeof(AIFFheader)-8+bytesWritten;
      AIFFheader.COMMchunk.numChannels = (AudioCtrl->ahiac_Flags & AHIACF_STEREO ? 2 : 1);
      AIFFheader.COMMchunk.numSampleFrames = samplesWritten;
      ulong2extended(AudioCtrl->ahiac_MixFreq,&AIFFheader.COMMchunk.sampleRate);
      AIFFheader.SSNDsize = sizeof(SampledSoundHeader)+bytesWritten;
      Seek(file,0,OFFSET_BEGINNING);
      Write(file,&AIFFheader,sizeof AIFFheader);
      break;

    case FORMAT_AIFC:
      AIFCheader.FORMsize = sizeof(AIFCheader)-8+bytesWritten;
      AIFCheader.COMMchunk.numChannels = (AudioCtrl->ahiac_Flags & AHIACF_STEREO ? 2 : 1);
      AIFCheader.COMMchunk.numSampleFrames = samplesWritten;
      ulong2extended(AudioCtrl->ahiac_MixFreq,&AIFCheader.COMMchunk.sampleRate);
      AIFCheader.SSNDsize = sizeof(SampledSoundHeader)+bytesWritten;
      Seek(file,0,OFFSET_BEGINNING);
      Write(file,&AIFCheader,sizeof AIFCheader);
      break;

    case FORMAT_S16:
      S16header.S16F_RATE = AudioCtrl->ahiac_MixFreq;
      S16header.S16F_SAMPLES0 =
      S16header.S16F_SAMPLES1 = samplesWritten;
      S16header.S16F_SAMPLES2 = samplesWritten - 1;
      if (file2 == NULL)
      {
        S16header.S16F_PAN = S16_PAN_MID;
      }
      else
      {
        S16header.S16F_PAN = S16_PAN_LEFT;
      }

      Seek(file, 0, OFFSET_BEGINNING);
      Write(file, &S16header, sizeof S16header);
      if(file2 != NULL)
      {
        S16header.S16F_PAN = S16_PAN_RIGHT;
        Seek(file2,0,OFFSET_BEGINNING);
        Write(file2, &S16header, sizeof S16header);
      }
      break;   
  }

  if(AudioCtrl->ahiac_Flags & AHIACF_HIFI)
    maxVolume >>=16;

  if(maxVolume != 0)
    EasyRequest(NULL, &req, NULL, 3276800/maxVolume,
      AudioCtrl->ahiac_MixFreq<frequency[FREQUENCIES-1] ? ",\nincrease the mixing frequency " : "\n");

quit:
  if(file)
  {
    Close(file);
  }
  if(file2)
  {
    Close(file2);
  }
  if(lock)
  {
    CurrentDir(cd);
    UnLock(lock);
  }
  Forbid();
  dd->fs_SlaveTask = NULL;
  FreeSignal(dd->fs_DisableSignal);
  FreeSignal(dd->fs_EnableSignal);
  FreeSignal(dd->fs_SlaveSignal);
  dd->fs_DisableSignal  = -1;
  dd->fs_EnableSignal   = -1;
  dd->fs_SlaveSignal    = -1;
  // Tell the Master we're dying
  Signal((struct Task *)dd->fs_MasterTask,1L<<dd->fs_MasterSignal);
  // Multitaking will resume when we are dead.
}

/*
** Apple's 80-bit SANE extended has the following format:

 1       15      1            63
+-+-------------+-+-----------------------------+
|s|       e     |i|            f                |
+-+-------------+-+-----------------------------+
  msb        lsb   msb                       lsb

The value v of the number is determined by these fields as follows:
If 0 <= e < 32767,              then v = (-1)^s * 2^(e-16383) * (i.f).
If e == 32767 and f == 0,       then v = (-1)^s * (infinity), regardless of i.
If e == 32767 and f != 0,       then v is a NaN, regardless of i.
*/

void ulong2extended (ULONG in, extended *ex)
{
  ex->exponent = 31+16383;
  ex->mantissa[1] = 0;
  while(!(in & 0x80000000))
  {
    ex->exponent--;
    in <<= 1;
  }
  ex->mantissa[0] = in;
}



/*
** The record slave process
*/

void __asm __saveds RecSlaveTask(register __a2 struct AHIAudioCtrlDrv *AudioCtrl)
// RecSlaveEntry() will set up register a2 and a6 for us.
{
  ULONG   signals;
  BPTR    lock = NULL,cd,file = NULL;
  Object *o = NULL;
  BYTE   *samples = NULL;
  ULONG   length = NULL;
  ULONG   count = 0,offs = 0,i;

  struct AHIRecordMessage RecordMessage = 
  {
    AHIST_S16S,
    NULL,
    RECBUFFERSIZE
  };

  RecordMessage.ahirm_Buffer = dd->fs_RecBuffer;

  if(!(lock = Lock(dd->fs_RecFileReq->fr_Drawer,ACCESS_READ)))
    goto quit;
  cd = CurrentDir(lock);

  if(DataTypesBase)
  {
    if (!(o = NewDTObject (dd->fs_RecFileReq->fr_File,
        DTA_GroupID,GID_SOUND,
        TAG_DONE)))
      goto quit;

    GetDTAttrs(o,
      SDTA_Sample,&samples,
      SDTA_SampleLength,&length,
      TAG_DONE);
  }
  else // datatypes.library not open. Open the selected file as raw 8 bit signed instead.
  {
    if(!(file = Open(dd->fs_RecFileReq->fr_File,MODE_OLDFILE)))
      goto quit;
    Seek(file,0,OFFSET_END);
    length = Seek(file,0,OFFSET_BEGINNING);
    if(!(samples = AllocVec(length,MEMF_ANY)))
      goto quit;
    if(length != Read(file,samples,length))
      goto quit;
  }

  if(!samples || !length )
    goto quit;

  if((dd->fs_RecSlaveSignal = AllocSignal(-1)) == -1)
    goto quit;

// Everything set up. Tell Master we're alive and healthy.
    Signal((struct Task *)dd->fs_MasterTask,1L<<dd->fs_RecMasterSignal);

    for(;;)
    {
      signals = SetSignal(0L,0L);
      if(signals & (SIGBREAKF_CTRL_C | 1L<<dd->fs_RecSlaveSignal))
        break;

      for(;;)
      {
        if(count+RECBUFFERSIZE-offs < length)
        {
// End of sample will not be reached; just fill to the end of dd->fs_RecBuffer.
          for(i = RECBUFFERSIZE-offs;i>0;i--)
          {
            dd->fs_RecBuffer[(offs)<<1] = 
            dd->fs_RecBuffer[((offs++)<<1)+1] = 
            samples[count++]<<8;
          }
          offs = 0;
          break;
        }
        else
        {
// End of sample will be reached. Fill part of buffer, and iterate (== don't break).
          for(i = length-count;i>0;i--)
          {
            dd->fs_RecBuffer[(offs)<<1] = 
            dd->fs_RecBuffer[((offs++)<<1)+1] = 
            samples[count++]<<8;
          }
          count = 0;
        }

      }

      CallHookPkt(AudioCtrl->ahiac_SamplerFunc,AudioCtrl,&RecordMessage);
      Delay(50*RECBUFFERSIZE/AudioCtrl->ahiac_MixFreq);
    }

quit:
// Get rid of object
  if(DataTypesBase)
  {
    if(o)
      DisposeDTObject (o);
  }
  else // datatypes.library not open.
  {
    if(samples)
      FreeVec(samples);
    if(file)
      Close(file);
  }
  CurrentDir(cd);
  if(lock)
    UnLock(lock);

  Forbid();
  dd->fs_RecSlaveTask = NULL;
  FreeSignal(dd->fs_RecSlaveSignal);
  dd->fs_RecSlaveSignal = -1;
  // Tell the Master we're dying
  Signal((struct Task *)dd->fs_MasterTask,1L<<dd->fs_RecMasterSignal);
  // Multitaking will resume when we are dead.
}
