/**
   \file auooset.c
*/

#include <vo_stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <audio.h>
#include <timers.h>
#include <vsos.h>
#include <devAudio.h>
#include <sysmemory.h>
#include <ringbuf.h>
#include <clockspeed.h>
#include <imem.h>
#include <saturate.h>
#include "auooset.h"

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

void DaosetInterruptVector(void);
extern u_int32 auxi;

struct AudioOutOset audioOutOset;


auto u_int16 AudioOutFree(void) {
  s_int16 fi = audioOutOset.ao.wr - audioOutOset.ao.rd;
  s_int16 fr;
  if (fi < 0)
    fi += audioOutOset.ao.bufSize;
  fr = audioOutOset.ao.bufSize - fi - 1;
  if (fr < 0)
    return 0;
  return fr & ~3;
}


auto ioresult AudioSetRateAndBits(register s_int32 sampleRate,
				  register u_int16 hiBits) {
  u_int16 rateBits = 0xFFF;
  sampleRate &= ~0x7F000000; /* Mask away fractional sample rate bits 30:24 */
  if (sampleRate < 1000) {
    sampleRate = 1000;
  }
  {
    u_int32 rate32;
    rate32 = (u_int32)(clockSpeed.peripClkHz/(2*sampleRate)-0.5);
    if (rate32 < 0xFFF) {
      rateBits = (u_int16)rate32;
    }
  }

  memsetY(audioOutOset.ao.buf, 0, audioOutOset.ao.bufSize);
  Disable();
  audioOutOset.ao.wr = audioOutOset.ao.rd = audioOutOset.ao.buf;
  audioOutOset.ao.sampleRate = clockSpeed.peripClkHz/(2*(rateBits+1));
  audioOutOset.ao.flags &= ~AIN_32BIT;
  if (hiBits) {
    audioOutOset.ao.flags |= AIN_32BIT;
  }
  PERIP(DAOSET_CF) = rateBits | DAOSET_CF_ENA;
  Enable();

  return 0;
}



ioresult AudioClose(void) {
  PERIP(DAOSET_CF) = 0;
  PERIP(INT_ENABLE1_HP) &= ~INTF1_DAOSET;

  if (audioOutOset.ao.bufSize) {
    FreeMemY(audioOutOset.ao.buf, audioOutOset.ao.bufSize);
    audioOutOset.ao.bufSize = NULL;
  }
}


ioresult AllocateAudioBuffer(register struct AudioOut *a,
			     register u_int16 bufSize) {
  ioresult res = S_OK;
  Disable();
  if (a->bufSize) {
    FreeMemY(a->buf, a->bufSize);
  }
  if (!(a->buf = AllocMemY(bufSize, bufSize))) {
    static u_int16 __mem_y panicBuffer[4];
    a->buf = panicBuffer;
    a->bufSize = 0;
    a->modifier = MAKEMODF(4);
    res = S_ERROR;
  } else {
    memsetY(a->buf, 0, bufSize);
    a->bufSize = bufSize;
    a->modifier = MAKEMODF(bufSize);
  }
  a->wr = a->rd = (__mem_y s_int16 *)(a->buf);
  Enable();
  return res;
}


auto ioresult AudioSetVolume(register u_int16 volume) {
  ioresult res = S_OK;

  audioOutOset.volVal = volume;
  if (volume == 256) {
    audioOutOset.volMult[0] = audioOutOset.volMult[1] = 4096;
  } else {
    if (volume <= 256-48) {
      audioOutOset.volMult[0] = audioOutOset.volMult[1] = 65535;
    } else if (volume >= 511) {
      audioOutOset.volMult[0] = audioOutOset.volMult[1] = 0;
    } else {
      static const u_int16 __mem_y volTab[12] =
	{61858, 58386, 55109, 52016, 49097, 46341,
	 43740, 41285, 38968, 36781, 34716, 32768};
      u_int16 tmpVol = volume-(256-48+1);
      audioOutOset.volMult[0] = audioOutOset.volMult[1] =
	volTab[tmpVol%12] >> (tmpVol/12);
    }
  }

  return res;
}



IOCTL_RESULT AudioIoctl(register __i0 VO_FILE *self, s_int16 request,
			IOCTL_ARGUMENT argp) {
  ioresult res = S_OK;

#if 0
  fprintf(stderr, "Ioctl(%p, %d, %p)\n", self, request, argp);
#endif

  switch (request) {
#if 0
  case IOCTL_START_FRAME:			
    break;
		
  case IOCTL_END_FRAME:
    break;
#endif

  case IOCTL_RESTART:
    PERIP(INT_ENABLE1_LP) &= ~INTF1_DAOSET;
    PERIP(INT_ENABLE1_HP) &= ~INTF1_DAOSET;
    WriteIMem(0x20 + INTV_DAOSET, ReadIMem((u_int16)DaosetInterruptVector));  
    AudioSetRateAndBits(48000, 0);
    AudioSetVolume(0x100);
    if (AllocateAudioBuffer(&audioOutOset.ao, 512)) {
      res = S_ERROR;
    }
    break;

  case IOCTL_AUDIO_GET_ORATE:
    *((u_int32 *)argp) = audioOutOset.ao.sampleRate;
    break;

  case IOCTL_AUDIO_SET_ORATE:
    res = AudioSetRateAndBits(*((u_int32 *)argp), audioOutOset.ao.flags & AIN_32BIT);
    break;

  case IOCTL_AUDIO_SET_RATE_AND_BITS:
    {
      s_int32 rate = *((u_int32 *)argp);
      s_int16 hiBits = (rate < 0);
      rate = labs(rate);
      res = AudioSetRateAndBits(rate, hiBits);
    }
    break;

  case IOCTL_AUDIO_GET_BITS:
    return (audioOutOset.ao.flags & AIN_32BIT) ? 32 : 16;
    break;

  case IOCTL_AUDIO_SET_BITS:
    res = AudioSetRateAndBits(audioOutOset.ao.sampleRate, (u_int16)argp > 16);
    break;

  case IOCTL_AUDIO_GET_VOLUME:
    res = audioOutOset.volVal;
    break;

  case IOCTL_AUDIO_SET_VOLUME:
    res = AudioSetVolume((int)argp);
    break;

  case IOCTL_AUDIO_GET_OUTPUT_BUFFER_SIZE:
    return audioOutOset.ao.bufSize;
    break;

  case IOCTL_AUDIO_SET_OUTPUT_BUFFER_SIZE:
    if (AllocateAudioBuffer(&audioOutOset.ao, (u_int16)argp)) {
      res = S_ERROR;
    }
    break;

  case IOCTL_AUDIO_GET_OUTPUT_BUFFER_FREE:
    return AudioOutFree();
    break;

  case IOCTL_AUDIO_GET_SAMPLE_COUNTER:
    Disable();
    *((u_int32 *)argp) = audioOutOset.ao.sampleCounter;
    Enable();
    break;

  case IOCTL_AUDIO_GET_UNDERFLOWS:
    Disable();
    *((u_int32 *)argp) = audioOutOset.ao.underflow;
    Enable();
    break;

  default:
    res = S_ERROR;
    break;
  }

  if (audioOutOset.ao.bufSize) {
    audioFile.flags = __MASK_PRESENT | __MASK_OPEN | __MASK_CHARACTER_DEVICE |
      __MASK_FILE |
      /*__MASK_READABLE |*/ __MASK_WRITABLE;
    PERIP(INT_ENABLE1_HP) |= INTF1_DAOSET;
  } else {
    audioFile.flags = __MASK_PRESENT | __MASK_OPEN | __MASK_CHARACTER_DEVICE |
      __MASK_FILE;
    PERIP(INT_ENABLE1_HP) &= ~INTF1_DAOSET;
  }

  return res;
}


void ApplyVolume(register __i0 s_int16 *d, register __d0 u_int16 vol,
		 register __d1 s_int16 n);

u_int16 AudioWrite(register __i0 VO_FILE *self, u_int16 *buf,
		   u_int16 sourceIndex, u_int16 bytes) {
  u_int16 left = bytes>>1;
  if (sourceIndex || (bytes&((audioOutOset.ao.flags & AIN_32BIT) ? 7 : 3))) {
    return 0;
  }
  while (left) {
    u_int16 toWrite = MIN(left, 16);
    u_int16 *wp = buf;
    if (audioOutOset.volMult[0] != 4096) {
      static u_int16 t[16];
      int i;
      wp = t;
      memcpy(t, buf, toWrite);
      ApplyVolume(t, audioOutOset.volMult[0], toWrite);
    }
    while (AudioOutFree() < toWrite) {
      Delay(1);
    }
    audioOutOset.ao.wr =
      RING_XY_D(ringcpyXY(audioOutOset.ao.wr, audioOutOset.ao.modifier, wp, 1, toWrite));
    buf += toWrite;
    left -= toWrite;
  }
  return bytes;
}

char *Identify(register __i0 void *self, char *buf, u_int16 bufSize) {
  strncpy(buf, "AUOOSET", bufSize);
  buf[bufSize-1] = '\0';
  return buf;
}

FILEOPS audioFileOps = {
  NULL, /* Open() */
  NULL, /* Close() */
  AudioIoctl, /* Ioctl() */
  CommonOkResultFunction, /* Read() */
  AudioWrite /* Write() */
};

SIMPLE_FILE audioFile = {
  __MASK_PRESENT|__MASK_OPEN|__MASK_WRITABLE|/*__MASK_READABLE|*/__MASK_FILE, /* Flags */
  Identify, /* Identify() */
  &audioFileOps,
  0, /* pos */
  0, /* ungetc_buffer */
  NULL, /* dev */
};
