/* Copyright 1990, 1992, 1995, 1996 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_sound[] =
	    "$Id: sound.c 97 2005-06-22 20:08:38Z ctm $";
#endif

/* Forward declarations in SoundMgr.h (DO NOT DELETE THIS LINE) */

#include "rsys/common.h"
#include "MemoryMgr.h"
#include "ResourceMgr.h"
#include "SoundDvr.h"
#include "SoundMgr.h"
#include "FileMgr.h"
#include "rsys/sounddriver.h"
#include "rsys/blockinterrupts.h"
#include "rsys/mman.h"


/* TRUE when we want to pretend this host has no sound support. */
boolean_t sound_disabled_p;

/*
 * TODO: The flag values below are just ctm guesses.  We need to run
 *	 tests on a Mac to see whether or not these flags are present
 *	 and if so, what values they take.
 */

#if defined(SOUNDGLUEROUTINES)
A3(PUBLIC, void, StartSound, Ptr, srec, LONGINT, nb, ProcPtr, comp)
{
}

A0(PUBLIC, void, StopSound)
{
}

A0(PUBLIC, BOOLEAN, SoundDone)
{
    return TRUE;
}

A1(PUBLIC, void, GetSoundVol, INTEGER *, volp)
{
}

A1(PUBLIC, void, SetSoundVol, INTEGER, vol)
{
}
#endif

PUBLIC int ROMlib_PretendSound = soundpretend;

static inline boolean_t
qfull_p (SndChannelPtr chanp)
{
  return ((CW (chanp->qTail) == CW (chanp->qHead) - 1 ||
	   (chanp->qHead == CWC (0)
	    && CW (chanp->qTail) == CW (chanp->qLength) - 1)));
}

static inline boolean_t
qempty_p (SndChannelPtr chanp)
{
  /* TetrisMax sometimes does something with a 0-length queue under
   * DOS when you quit, which causes a division by zero on a % in
   * `deq'; this is probably a bug somewhere else, but checking for a
   * spewy queue will make Executor more robust and may be adequate to
   * get 2.0 out the door.
   */
  return (chanp->qHead == chanp->qTail
	  || CW (chanp->qLength) <= 0);
}

static inline void
enq (SndChannelPtr chanp, SndCommand cmd)
{
  unsigned tail;

  tail = CW (chanp->qTail);
  chanp->queue[tail] = cmd;
  chanp->qTail = CW ((tail + 1) % CW (chanp->qLength));
}

static inline SndCommand
deq (SndChannelPtr chanp)
{
  SndCommand ret;
  unsigned head;

  head = CW (chanp->qHead);
  ret = chanp->queue[head];
  chanp->qHead = CW ((head + 1) % CW (chanp->qLength));

  return ret;
}

#if 0
static inline double
f2d (snd_time x)
{
  return x / (double) (1ULL << (4 * sizeof x));
}
#endif

static inline void
wait_on_channel (volatile SndChannelPtr vchanp)
{
  while (!qempty_p (vchanp) || SND_CHAN_CMDINPROG_P (vchanp))
    check_virtual_interrupt ();
}

static snd_time
snd_fixed_div (snd_time x, snd_time y)
{
  typeof(x) temp1, temp2;
  int bits;

  bits = 4 * sizeof x;

  temp1 = x / y;

  temp2 = (x - y * temp1) << bits;
  temp2 = temp2 / y;

  return (temp1 << bits) + temp2;
}

static snd_time
snd_fixed_mul (snd_time x, snd_time y)
{
  typeof(x) x1, x0, y1, y0;
  int bits;

  bits = 4 * sizeof x;
  x0 = x % (1ULL << bits);
  x1 = x >> bits;
  y0 = y % (1ULL << bits);
  y1 = y >> bits;

  return (((x0*y0) >> bits) + x0*y1 + x1*y0 + ((x1*y1) << bits));
}


/* This table is used to add two sound values together and pin
 * the value to avoid overflow.
 */
static const uint8 mix8[] =
{
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
  0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
  0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
  0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
  0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
  0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
  0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
  0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B,
  0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
  0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
  0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,
  0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
  0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92,
  0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
  0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
  0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
  0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
  0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
  0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
  0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
  0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
  0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
  0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};

/* Return TRUE if the buffer was used up.  I hate value-result parms,
   but oh well.  A NULL orig_outbuf is OK; the resampled waveform
   will simply be lost. */

static boolean_t
resample (uint8 *inbuf, uint8 *orig_outbuf, unsigned int insize,
	  unsigned int outsize, uint32 infreq, uint32 outfreq,
	  snd_time *current_start, uint8 *prev_samp,
	  snd_time *chan_time, snd_time until)

{
  snd_time step;
  snd_time in_t, out_t;
  uint8 *outbuf;

  /* FIXME - this does lots of pointless work when we're going to
   * ignore the resampled waveform anyway, but it makes the "real"
   * sound and "dummy" sound cases more similar.  Is it worth it?
   */
  outbuf = orig_outbuf ?: alloca (outsize);

  step = snd_fixed_div (infreq, outfreq);

  while (SND_PROMOTE (*chan_time) < *current_start)
    {
      outbuf[*chan_time % outsize] =
	mix8[((unsigned int) *prev_samp
	      + (unsigned int) outbuf[*chan_time % outsize])];
      (*chan_time)++;
    }

  in_t = snd_fixed_mul (SND_PROMOTE (*chan_time) - *current_start, step);
  out_t = *chan_time;

#if 0
  warning_sound_log ("step %g in_t %.9g until %.9g", f2d (step),
		     f2d (in_t),
		     (double) until);
  warning_sound_log (" cur_start %.9g insize %d", f2d (*current_start),
		     insize);
  warning_sound_log (" chan_time %.9g out_t %.9g outsize %d",
		     (double) *chan_time, (double) out_t, outsize);
#endif

  while (out_t < until && SND_DEMOTE (in_t) < insize - 1)
    {
      outbuf[out_t % outsize] =
	mix8[((unsigned int) inbuf[SND_DEMOTE (in_t)]
	      + (unsigned int) outbuf[out_t % outsize])];
      in_t += step;
      out_t += 1;
    }

  *chan_time = out_t;
  if (SND_DEMOTE (in_t) < insize - 1)
    return FALSE;
  else
    {
      *current_start = (SND_PROMOTE (out_t)
			+ snd_fixed_div (SND_PROMOTE (insize), step)
			- snd_fixed_div (in_t, step));
      *prev_samp = inbuf[insize - 1];

      return TRUE;
    }
}

PUBLIC int ROMlib_get_snd_cmds (Handle sndh, SndCommand **cmdsp)
{
  void *p;
  int format;
  int num_formats;
  int retval;

  p = STARH (sndh);
  format = CW (*(INTEGER *)p);
  switch (format)
    {
    case 1:
      p += 2;
      num_formats = CW (*(INTEGER *)p);
      switch (num_formats)
	{
	case 0:
	  p += 2;
	  break;
	case 1:
	  p += 8;
	  break;
	default:
	  warning_sound_log (" WARNING: num_formats = %d", num_formats);
	  p += (2 + 6*num_formats);
	  break;
	}
      break;
      
    case 2:
      p += 4;
      break;
      
    default:
      warning_sound_log ("Unrecognized sound format %d", format);
      return 0;  /* FIXME */
      break;
    }
  
  /* Now p points to "Number of sound commands" field of resource */

  retval = CW (*(INTEGER *)p);
  *cmdsp = (SndCommand *)(p+2);

  return retval;
}

P3(PUBLIC, pascal trap OSErr, SndPlay, SndChannelPtr, chanp, Handle, sndh,
								BOOLEAN, async)
{
  OSErr retval;
  void *resp;
  SndCommand *cmds;
  int num_commands, i, format;
  boolean_t need_allocate;

  if (sndh)
    resp = STARH (sndh);
  else
    resp = NULL;

  if (resp)
    format = CW (*(INTEGER *)resp);
  else
    format = 0;

  warning_sound_log ("chanp %p fmt %d num_fmts %d async %d",
		     chanp, format, resp ? CW (*((INTEGER *)(resp+2))) : 0,
		     async);

  warning_sound_log (" sndh %s", (HGetState(sndh) & LOCKBIT)
		     ? "Locked" : "Unlocked");

  switch (ROMlib_PretendSound) {
  default:
  case soundoff:
    retval = notEnoughHardware;
    break;
  case soundpretend:
    retval = noErr;
    break;
  case soundon:
    num_commands = ROMlib_get_snd_cmds (sndh, &cmds);

    warning_sound_log (" num_commands = %d", num_commands);

    need_allocate = (chanp == NULL);

    if (need_allocate)
      {
	HIDDEN_SndChannelPtr foo;

	foo.p = CLC (0);
	SndNewChannel (&foo, sampledSynth, 0, 0);
	chanp = MR (foo.p);
      }

    for (i=0 ; i < num_commands ; i++)
      {
	SndCommand cmd;

	cmd = cmds[i];

	/* If high bit of cmd is set, then param2 is actually an
           offset */
	if (cmd.cmd & CWC (0x8000))
	  {
	    cmd.param2 = (LONGINT) RM ((resp + CL (cmd.param2)));
	    cmd.cmd &= CWC (~0x8000);
	  }

	/* Need to figure this out properly; FIXME */
	if (format == 2 && i == 0 && cmd.cmd == CWC (soundCmd))
	  {
	    cmd.cmd = CWC (bufferCmd);
	  }

	C_SndDoCommand (chanp, &cmd, 0);
      }

    if (need_allocate || !async)
      wait_on_channel (chanp);

    if (need_allocate)
      SndDisposeChannel (chanp, 0);

    retval = noErr;
    break;
  }
  return retval;
}

HIDDEN_SndChannelPtr allchans;

P4(PUBLIC pascal trap, OSErr, SndNewChannel, HIDDEN_SndChannelPtr *, chanpp,
   INTEGER, synth, LONGINT, init, ProcPtr, userroutinep)
{
  SndChannelPtr chanp;
  OSErr retval;

  warning_sound_log
    ("chanp %p synth %d init 0x%x userroutine %p",
     chanpp ? STARH (chanpp) : NULL, synth, init, userroutinep);

  switch (ROMlib_PretendSound) {
  default:
  case soundoff:
    retval = notEnoughHardware;
    break;
  case soundpretend:
    retval = noErr;
    break;

  case soundon:
    if (STARH (chanpp) == NULL) {
      chanpp->p = RM ((SndChannelPtr) NewPtr (sizeof (SndChannel)));
      chanp = STARH (chanpp);
      chanp->flags = CWC (CHAN_ALLOC_FLAG);
    } else {
      chanp = STARH (chanpp);
      chanp->flags = CWC (0);
    }
    chanp->nextChan = allchans.p;
    allchans.p = RM (chanp);
    chanp->firstMod = RM ((Ptr) NewPtr (sizeof (ModifierStub)));
    SND_CHAN_TIME (chanp) = 0;
    SND_CHAN_CURRENT_START (chanp) = 0;
    chanp->callBack = RM (userroutinep);
    /*chanp->userInfo = 0;*/
    chanp->wait = 0;
    chanp->cmdInProg.cmd = 0;
    chanp->qLength = CWC (stdQLength);
    chanp->qHead = 0;
    chanp->qTail = 0;
    retval = noErr;
  }
    return retval;
}

#define MOD_SYNTH_FLAG	1

P4(PUBLIC, pascal trap OSErr, SndAddModifier, SndChannelPtr, chanp,
				      ProcPtr, mod, INTEGER, id, LONGINT, init)
{
#if defined(OLD_BROKEN_NEXTSTEP_SOUND)
    Handle h;
    SndCommand cmd;
    ModifierStubPtr modp;
#endif /* defined(OLD_BROKEN_NEXTSTEP_SOUND) */
    OSErr retval;

    warning_sound_log ("chanp %p", chanp);

    switch (ROMlib_PretendSound) {
    default:
    case soundoff:
	retval = notEnoughHardware;
	break;
    case soundpretend:
	retval = noErr;
	break;
#if defined(OLD_BROKEN_NEXTSTEP_SOUND)
    case soundon:
	if ((unsigned short) chanp->qLength != (unsigned short) CWC(stdQLength))
	    retval = badChannel;
	else {
	    modp = (ModifierStubPtr) NewPtr(sizeof(ModifierStub));
	    modp->nextStub = chanp->firstMod;
	    chanp->firstMod = CL(modp);
	    modp->flags = 0;
	    if (mod) {
		modp->code = CL(mod);
		modp->hState = 0;
	    } else {
		h = GetResource(TICK("snth"), id);
		if (STARH(h) != (Ptr) P_snth5) {	/* ACK; phone handle stuff */
		    LoadResource(h);
		    modp->flags = MOD_SYNTH_FLAG;
		    modp->hState = HGetState(h);
		    HLock(h);
		}
		modp->code = (ProcPtr) h->p;
	    }
	    modp->userInfo = 0;
	    modp->count = 0;
	    modp->every = 0;
	    if (modp->code) {
		cmd.cmd    = CWC(initCmd);
		cmd.param1 = CWC(0);
		cmd.param2 = CL(init);
		retval = SndDoImmediate(chanp, &cmd);
	    } else
		retval = resProblem;
	}
	break;
#endif
    }
    return retval;
}

#if defined(OLD_BROKEN_NEXTSTEP_SOUND)
static void dumpcmd(SndCommand *cmdp)
{
    printf("#%x,1-%x,2-%x.", (LONGINT) CW(cmdp->cmd), (LONGINT) CW(cmdp->param1), (LONGINT) CL(cmdp->param2));
}
#endif


typedef pascal BOOLEAN (*snthfp)(SndChannelPtr, SndCommand *, ModifierStubPtr);

BOOLEAN callasynth(SndChannelPtr chanp, SndCommand *cmdp, ModifierStubPtr mp)
{
/*
 * NOTE: when we support sound, we'll have to check for known P_routines
 *	 to avoid invoking syn68k on our own stuff.
 */
    return CToPascalCall(MR(mp->code), CTOP_SectRect, chanp, cmdp, mp);
}

PRIVATE void recsndcmd(SndChannelPtr chanp, SndCommand *cmdp,
							    ModifierStubPtr mp)
{
    INTEGER i;
    BOOLEAN doanother;

    if (mp) {
	i = 0;
	do {
	    doanother = callasynth(chanp, cmdp, mp);
	    recsndcmd(chanp, cmdp, MR(mp->nextStub));
	    if (doanother) {
		cmdp->cmd = CWC(requestNextCmd);
		cmdp->param1 = CW(++i);
		cmdp->param2 = 0;
	    }
	} while (doanother);
    }
}


static inline boolean_t
earlier_p (snd_time t1, snd_time t2)
{
  return ((t1 - t2) >> ((8 * sizeof t1) - 1)) != 0;
}

#if 0
static inline snd_time
earlier (snd_time t1, snd_time t2)
{
  return earlier_p (t1, t2) ? t1 : t2;
}
#endif

static inline unsigned long
snd_duration (SoundHeaderPtr hp)
{
  return CL (hp->length);
}

#define CMD_DONE(c) (SND_CHAN_FLAGS_X (c) &= CWC (~CHAN_CMDINPROG_FLAG))

static void
do_current_command (SndChannelPtr chanp, struct hunger_info info)
{
  unsigned long duration;
  unsigned char *sp;
  SoundHeaderPtr hp;
  SndCommand cmd;

  switch (CW (chanp->cmdInProg.cmd))
    {
    case bufferCmd:
      hp = MR ((SoundHeaderPtr) chanp->cmdInProg.param2);
      
      if (hp->encode != stdSH)
	{
	  warning_unimplemented ("Ignoring unsupported SoundHeader "
				 "encoding: %s",
				 ((hp->encode == cmpSH)
				  ? "compressed"
				  : ((hp->encode == extSH)
				     ? "extended"
				     : "<unknown>")));
	  CMD_DONE (chanp);
	}
      else
	{
	  duration = snd_duration (hp);
	  
	  sp = (hp->samplePtr ? MR ((unsigned char *) hp->samplePtr)
		: hp->sampleArea);
	  
	  warning_sound_log ("bufferCmd dur %d", (int) duration);

	  if (resample (sp, info.buf, CL (hp->length),
			info.bufsize, CL (hp->sampleRate),
			SND_RATE << 16,
			&SND_CHAN_CURRENT_START (chanp),
			&SND_CHAN_PREV_SAMP (chanp),
			&SND_CHAN_TIME (chanp),
			info.t3))
	    {
	      CMD_DONE (chanp);
	    }
	}
      break;

    case callBackCmd:
      warning_sound_log ("callBackCmd");
      cmd = chanp->cmdInProg;
      CToPascalCall (MR (chanp->callBack), CTOP_SetCTitle,
		     chanp, &cmd);
      CMD_DONE (chanp);
      break;

    case ampCmd:
      warning_sound_log ("ampCmd (ignored)");
      CMD_DONE (chanp);
      break;

    default:
      warning_sound_log ("UNKNOWN CMD %d\n",
			 chanp ? CW (chanp->cmdInProg.cmd) : 0);
      CMD_DONE (chanp);
    }
}

#define SND_DB_DONE(c) (SND_CHAN_FLAGS_X(c) &= CWC (~CHAN_DBINPROG_FLAG))

static void
do_current_db (SndChannelPtr chanp, struct hunger_info info)
{
  SndDoubleBufferHeader *dbhp;
  SndDoubleBuffer *dbp;
  unsigned char *sp;

  dbhp = SND_CHAN_DBHP (chanp);
  dbp = MR (dbhp->dbhBufferPtr[SND_CHAN_CURRENT_DB (chanp)]);

  if ((dbp->dbFlags & CLC (dbBufferReady)) == 0)
    {
      /* This buffer isn't ready */
      warning_sound_log ("notready");
      SND_CHAN_TIME (chanp) = info.t3;
      SND_CHAN_CURRENT_START (chanp) = SND_PROMOTE (info.t3);
      return;
    }
  sp = dbp->dbSoundData;

#if 0
  warning_sound_log ("curs %g t %d t3 %d dur %d rate %g",
		     f2d (SND_CHAN_CURRENT_START (chanp)),
		     (int) SND_CHAN_TIME (chanp),
		     (int) info.t3,
		     CL (dbp->dbNumFrames),
		     CL (dbhp->dbhSampleRate) / (double) (1 << 16));
#endif
  if (resample (sp, info.buf, CL (dbp->dbNumFrames),
		info.bufsize, CL (dbhp->dbhSampleRate),
		SND_RATE << 16,
		&SND_CHAN_CURRENT_START (chanp),
		&SND_CHAN_PREV_SAMP (chanp),
		&SND_CHAN_TIME (chanp),
		info.t3))
    {
      /* We are done with this buffer */
      dbp->dbFlags &= CLC (~dbBufferReady);
      if (dbp->dbFlags & CLC (dbLastBuffer))
	/* We are completely done */
	SND_DB_DONE (chanp);
      else
	{
	  /* Start the next buffer */
	  SND_CHAN_CURRENT_DB (chanp) ^= 1;
	}
#if 0
      warning_sound_log ("dblback %p ch %p bp %p",
			 CL (dbhp->dbhDoubleBack),
			 chanp, dbp);
      warning_sound_log (" frs %d flgs %d ui1 %x ui2 %x",
			 CL (dbp->dbNumFrames),
			 CL (dbp->dbFlags),
			 CL (dbp->dbUserInfo[0]),
			 CL (dbp->dbUserInfo[1]));
#endif
      CToPascalCall (MR (dbhp->dbhDoubleBack), CTOP_SetCTitle,
		     chanp, dbp);
    }
}

syn68k_addr_t
sound_callback (syn68k_addr_t interrupt_addr, void *unused)
{
  struct hunger_info info;
  SndChannelPtr chanp;
  int did_something = 0;
  M68kReg saved_regs[16];
  CCRElement saved_ccnz, saved_ccn, saved_ccc, saved_ccv, saved_ccx;

  //  fprintf (stderr, "in sound_callback\n");

  /* Save the 68k registers and cc bits away. */
  memcpy (saved_regs, &cpu_state.regs, sizeof saved_regs);
  saved_ccnz = cpu_state.ccnz;
  saved_ccn  = cpu_state.ccn;
  saved_ccc  = cpu_state.ccc;
  saved_ccv  = cpu_state.ccv;
  saved_ccx  = cpu_state.ccx;

#if defined (SYN68K)

  /* There's no reason to think we need to decrement A7 by 32;
   * it's just a paranoid thing to do.
   */
  EM_A7 = (EM_A7 - 32) & ~3;  /* Might as well long-align it. */

#else /* !SYN68K */

  /* Since we don't know which stack we were on when interrupted,
   * switch stacks as appropriate.  If all stacks are busy,
   * there's not much we can do other than return.  
   */
  if (!m68k_use_interrupt_stacks ())
    return 0;

#endif /* !SYN68K */

  SOUND_HUNGER_START ();
  info = SOUND_GET_HUNGER_INFO ();

  /* For each channel, grab some samples and mix them in */
  for (chanp = MR (allchans.p) ; chanp != NULL ; chanp = MR (chanp->nextChan))
    {
      if (earlier_p (SND_CHAN_TIME (chanp), info.t2))
	{
	  snd_time diff = info.t2 - SND_CHAN_TIME (chanp);

	  SND_CHAN_TIME (chanp) += diff;
	  SND_CHAN_CURRENT_START (chanp) += SND_PROMOTE (diff);
	}

      while (earlier_p (SND_CHAN_TIME (chanp), info.t3))
	{
	  if (SND_CHAN_CMDINPROG_P (chanp))
	    {
	      do_current_command (chanp, info);
	      did_something = 1;
	    }
	  else if (SND_CHAN_DBINPROG_P (chanp))
	    {
	      do_current_db (chanp, info);
	      did_something = 1;
	    }
	  else if (!qempty_p (chanp))
	    {
	      chanp->cmdInProg = deq (chanp);
	      SND_CHAN_FLAGS_X (chanp) |= CWC (CHAN_CMDINPROG_FLAG);
	      did_something = 1;
	    }
	  else
	    {
	      break;
	    }
	}
    }

  //  fprintf (stderr, "about to call sound_hunger_finish\n");

  SOUND_HUNGER_FINISH ();

  if (!did_something)
    SOUND_STOP ();

#if !defined (SYN68K)
  m68k_restore_stacks ();
#endif

  memcpy (&cpu_state.regs, saved_regs, sizeof saved_regs);
  cpu_state.ccnz = saved_ccnz;
  cpu_state.ccn  = saved_ccn;
  cpu_state.ccc  = saved_ccc;
  cpu_state.ccv  = saved_ccv;
  cpu_state.ccx  = saved_ccx;

#if defined (SYN68K)
  return MAGIC_RTE_ADDRESS;
#else
  return 0;
#endif
}

P3(PUBLIC, pascal trap OSErr, SndDoCommand, SndChannelPtr, chanp,
					   SndCommand *, cmdp, BOOLEAN, nowait)
{
  OSErr retval;
  
#if 1
  if (!cmdp)
    warning_sound_log ("cmdp = NULL");
  else
    warning_sound_log
      ("cmd %d param1 0x%x param2 0x%x nowait %d", CW (cmdp->cmd),
       CW (cmdp->param1), CL (cmdp->param2), CW (nowait));
#endif

#if ERROR_SUPPORTED_P (ERROR_SOUND_LOG)
  if (cmdp->cmd == CWC (bufferCmd))
    {
      SoundHeaderPtr hp;

      hp = MR ((SoundHeaderPtr) cmdp->param2);
      if (!hp)
	warning_sound_log ("hp = NULL");
      else
	warning_sound_log
	  (" len %d rate 0x%x encode %d freq %d",
	   CL (hp->length), CL (hp->sampleRate), hp->encode,
	   hp->baseFrequency);
    }
#endif

  switch (ROMlib_PretendSound)
    {
    default:
    case soundoff:
      retval = notEnoughHardware;
      break;
    case soundpretend:
      retval = noErr;
      break;

    case soundon:
      retval = noErr;
      SOUND_GO ();

      if (qfull_p (chanp))
	{
	  if (nowait)
	    retval = queueFull;
	  else
	    {
	      volatile SndChannelPtr vchanp = chanp;
	    
	      while (qfull_p (vchanp))
		check_virtual_interrupt ();
	      retval = noErr;
	    }
	}
      if (retval == noErr) {
	enq (chanp, *cmdp);
	chanp->flags |= CWC(CHAN_BUSY_FLAG);
      }
      break;
    }

  return retval;
}

P2 (PUBLIC, pascal trap OSErr, SndDoImmediate, SndChannelPtr, chanp,
    SndCommand *, cmdp)
{
  OSErr retval;
  SndCommand cmd;

  switch (ROMlib_PretendSound)
    {
    case soundoff:
    default:
      retval = notEnoughHardware;
      break;
    case soundpretend:
      retval = noErr;
      break;
    case soundon:
      SOUND_GO ();
#if 0  /* This is not a good check for badChannel */
      if ((unsigned short) chanp->qLength
	  != (unsigned short) CWC (stdQLength))
	retval = badChannel;
      else
#endif
	{
	  cmd = *cmdp;
	  switch (CW (cmd.cmd))
	    {
	    case flushCmd:
	      warning_sound_log ("flushCmd");
	      while (!qempty_p (chanp))
		(void) deq (chanp);
	      /* We need to flush the current channel as well as the Q */
              CMD_DONE (chanp);
              SND_DB_DONE (chanp);
              SND_CHAN_TIME (chanp) = 0;
              SND_CHAN_CURRENT_START (chanp) = 0;
	      retval = noErr;
	      break;
	      
	    case quietCmd:
	      warning_sound_log ("quietCmd");
	      CMD_DONE (chanp);
	      SND_DB_DONE (chanp);
	      retval = noErr;
	      break;

	    case bufferCmd:
	      warning_sound_log ("bufferCmd");
	      chanp->cmdInProg = cmd;
	      SND_CHAN_FLAGS_X (chanp) |= CWC (CHAN_CMDINPROG_FLAG);
	      SND_CHAN_CURRENT_START (chanp) =
		SND_PROMOTE (SND_CHAN_TIME (chanp));
	      retval = noErr;
	      break;

	    default:
	      warning_sound_log ("UNKNOWN CMD %d", CW (cmd.cmd));
	      retval = noErr;
	    }
	}
      break;
    }
  return retval;
}

P3(PUBLIC, pascal trap OSErr, SndChannelStatus, SndChannelPtr, chanp,
					INTEGER, length, SCStatusPtr, statusp)
{
  OSErr ret;

  warning_sound_log ("chanp %p", chanp);

  switch (ROMlib_PretendSound)
    {
    default:
    case soundoff:
      ret = notEnoughHardware;
      break;
    case soundpretend:
      ret = noErr;
      break;
    case soundon:
      if (length < (int) sizeof (*statusp))
	ret = paramErr;
      else
	{
	  statusp->scStartTime = CLC (0);
	  statusp->scEndTime = CLC (0);
	  statusp->scCurrentTime = CLC (0);
	  statusp->scChannelBusy = (SND_CHAN_CMDINPROG_P (chanp)
				    || !qempty_p (chanp));
	  statusp->scChannelPaused = 0;  /* FIXME */
	  statusp->scChannelAttributes = CLC(0x80);  /* FIXME */
	  statusp->scCPULoad = CLC (0);
	  ret = noErr;
	}
      break;
    }

  return ret;
}

P2(PUBLIC, pascal trap OSErr, SndControl, INTEGER, id, SndCommand *, cmdp)
{
    OSErr retval;
#if defined(OLD_BROKEN_NEXTSTEP_SOUND)
    Handle h;
    SignedByte state;
#endif /* defined(OLD_BROKEN_NEXTSTEP_SOUND) */

    warning_sound_log (NULL_STRING);

    switch (ROMlib_PretendSound) {
    default:
    case soundoff:
	retval = notEnoughHardware;
	break;
    case soundpretend:
	retval = noErr;
	break;
#if defined(OLD_BROKEN_NEXTSTEP_SOUND)
    case soundon:
	h = GetResource(TICK("snth"), id);
	if (!h)
	    retval = resProblem;
	else {
	    LoadResource(h);
	    if (STARH(h) != (Ptr) P_snth5)
		state = HGetState(h);
#if !defined(LETGCCWAIL)
	    else
		state = 0;
#endif
	    callasynth((SndChannelPtr) 0, cmdp, (ModifierStubPtr) 0);
	    if (STARH(h) != (Ptr) P_snth5)
		HSetState(h, state);
	    retval = noErr;
	}
	break;
#endif
    }
    return retval;
}

P2(PUBLIC, pascal trap OSErr, SndDisposeChannel, SndChannelPtr, chanp,
							      BOOLEAN, quitnow)
{
    OSErr retval;
    SndCommand cmd;
    HIDDEN_SndChannelPtr *pp;

    warning_sound_log ("chanp %p quitnow %d", chanp, quitnow);

    if (!chanp)
      return badChannel;

    switch (ROMlib_PretendSound) {
    default:
    case soundoff:
	retval = notEnoughHardware;
	break;
    case soundpretend:
	retval = noErr;
	break;

    case soundon:
	retval = noErr;
	if (quitnow) {
	    cmd.cmd    = CWC(flushCmd);
	    cmd.param1 = CWC(0);
	    cmd.param2 = CLC(0);
	    retval = SndDoImmediate(chanp, &cmd);
	}
	if (retval == noErr) {
	    cmd.cmd    = CWC(quietCmd);
	    retval = SndDoImmediate(chanp, &cmd);
	}
#if 0
	block = block_virtual_ints ();
	vchanp = chanp;
	while (vchanp->flags & CWC(CHAN_BUSY_FLAG)) {
	    restore_virtual_ints (block);
	    block = block_virtual_ints ();
	}
	restore_virtual_ints (block);
	nextmp = CL(chanp->firstMod);
	while ((mp = nextmp)) {
	    nextmp = CL(mp->nextStub);
	    cmd.cmd = CWC(freeCmd);
	    callasynth(chanp, &cmd, mp);
	    if (mp->flags & MOD_SYNTH_FLAG) {
		h = RecoverHandle((Ptr) CL(mp->code));
		HSetState(h, mp->hState);
	    }
	    DisposPtr((Ptr) mp);
	}
#endif

	for (pp = &allchans;
	     pp->p && MR(pp->p) != chanp;
	     pp = (HIDDEN_SndChannelPtr *) &MR(pp->p)->nextChan)
	    ;
	if (pp->p)
	  {
	    pp->p = chanp->nextChan;
	    DisposPtr (MR ((Ptr) chanp->firstMod));
	    if (chanp->flags & CWC(CHAN_ALLOC_FLAG))
	      DisposPtr((Ptr) chanp);
	  }
	else
	  {
	    warning_sound_log ("NON-EXISTENT CHAN");
	    retval = badChannel;
	  }


	break;
    }
    return retval;
}

#if defined(OLD_BROKEN_NEXTSTEP_SOUND)
void ROMlib_soundcomplete( void *vp )
{
    SndChannelPtr chanp;
    virtual_int_state_t block;

    chanp = vp;
    block = block_virtual_ints ();
    if (chanp->flags & CWC(CHAN_IMMEDIATE_FLAG))
	chanp->flags &= CWC(~CHAN_IMMEDIATE_FLAG);
    else {
	chanp->qHead = CW(CW(chanp->qHead) + 1);
	if ((unsigned short) chanp->qHead == (unsigned short) CWC(stdQLength))
	    chanp->qHead = CWC(0);
	if (chanp->qHead == chanp->qTail)
	    chanp->flags &= CWC(~CHAN_BUSY_FLAG);
	else
	    recsndcmd(chanp, &chanp->queue[CW(chanp->qHead)], CL(chanp->firstMod));
    }
    restore_virtual_ints (block);
}
#endif
