/*  File:         sound.c
 *  Created:      20-10-95
 *  Updated:      30-12-95
 *  Version:      1.0
 *  Project:      Clicker
 *  Owner:        Jeroen Vermeulen
 *  Requirements: KickStart V39+
 *  Legal:        PD
 *  Status:       Release
 */

#include <math.h>

#include <proto/exec.h>
#include <exec/devices.h>
#include <exec/memory.h>
#include <devices/audio.h>
#include <proto/alib.h>

#include "main.h"
#include "sound.h"
#include "prefs.h"


static STRPTR
        AllocFailChipMem = "Not enough CHIP memory!\n",
        AllocFailPubMem  = "Not enough PUBLIC memory!\n",
        OpenFailAudioDev = "Can't open audio.device\n";

/* Here's our basic built-in sample.  It's a very crude sine wave but it should
 * do for those very short intervals.
 * NOTE:  SAMPLELENGTH must be defined to the length of this array!
 */
static signed char Sample[SAMPLELENGTH] = { 0, 60, 100, 127, 100, 60,
                                            0, -60, -100, -127, -100, -60 };

/* Allocation priority array for sound channels.  Don't see the point really, as
 * we're only asking for a single lousy sound channel.  Oh well, audio.device
 * seems to insist that we provide this.
 */
static unsigned char SoundChannels[] = { 2,1,4,8 };


/* CreateSample():
 * Open audio device and sample.  An IOAudio structure is returned unless either
 * an error occurs or the error string was already non-NULL before the call.
 */
struct IOAudio *CreateSample(STRPTR *const error)
{
  struct IOAudio   *soundrequest = NULL;
  struct IORequest *plainrequest;
  struct MsgPort   *Reply;

  if ((Reply = CreateMsgPort()))
  {
    plainrequest = CreateIORequest(Reply,sizeof(struct IOAudio));
    soundrequest = (struct IOAudio *)plainrequest;

    if (soundrequest)
    {
      soundrequest->ioa_Data = SoundChannels;
      soundrequest->ioa_Length = 4;
      plainrequest->io_Command = ADCMD_ALLOCATE;
      plainrequest->io_Flags = ADIOF_NOWAIT;

      if (OpenDevice("audio.device",0,plainrequest,0) == 0)
      {
        UBYTE *SampleMem;
        /* --- */
        SampleMem = (UBYTE *)AllocMem(SAMPLELENGTH,MEMF_PUBLIC|MEMF_CHIP);

        if (SampleMem)
        {
          CopyMem(Sample,SampleMem,SAMPLELENGTH);
          plainrequest->io_Flags = ADIOF_PERVOL|IOF_QUICK;
          plainrequest->io_Command = CMD_WRITE;
          soundrequest->ioa_Data = SampleMem;
          soundrequest->ioa_Length = SAMPLELENGTH;
        }
        else *error = AllocFailChipMem;
      }
      else *error = OpenFailAudioDev;
    }
    else *error = AllocFailPubMem;
  }
  else *error = AllocFailMsgPort;

  if (*error)
  {
    DeleteSample(soundrequest);
    soundrequest = NULL;
  }

  return soundrequest;
}


/* Destroy sample and free all resources allocated with it.  This function is
 * overly robust so it can be used from within CreateSample() in case of an
 * error.
 */
void DeleteSample(struct IOAudio *const soundrequest)
{
  if (soundrequest)
  {
    struct IORequest *const plainrequest = &soundrequest->ioa_Request;
    if (plainrequest->io_Message.mn_ReplyPort)
    {
      CloseDevice(plainrequest);
      DeleteMsgPort(plainrequest->io_Message.mn_ReplyPort);
    }
    if (soundrequest->ioa_Data) FreeMem(soundrequest->ioa_Data,SAMPLELENGTH);
    DeleteIORequest(plainrequest);
  }
}


/* KeyClick():
 * Make key-click noise.  The soundrequest pointer is assumed to be valid and
 * non-NULL, and point at a properly initialized IOAudio structure.
 */
void KeyClick(struct IOAudio *const soundrequest)
{
  if (ClickPrefs.newsettings)
  {
    ClickPrefs.newsettings = FALSE;
    soundrequest->ioa_Period = ClickPrefs.period;
    soundrequest->ioa_Volume = ClickPrefs.volume;
    soundrequest->ioa_Cycles = ClickPrefs.cycles;
    soundrequest->ioa_Request.io_Flags = ADIOF_PERVOL | IOF_QUICK;
  }
  BeginIO(&soundrequest->ioa_Request);
  WaitIO(&soundrequest->ioa_Request);
}


/* SliderToHertz():
 * Converts a prefs window slider position (between -5*12 and 4*12) to a
 * frequency in Hertz, based on a twelve-tone octave centered at the 440 Hz A.
 */
LONG SliderToHertz(const struct Gadget *const dum, const WORD sliderpos)
{
  /* Frequency is 440 * 2^(n/12).  Note that left-shift cannot be used here.
   */
  return (LONG)(440 * pow((double)2,sliderpos/(double)12));
}


/* HertzToPeriod():
 * Converts human-readable pitch in Hertz to period length suitable for use by
 * audio.device.  As a rule, HertzToPeriod(SliderToHertz(S)) is equivalent to
 * SliderToPeriod(S).
 */
UWORD HertzToPeriod(const LONG Hertz)
{
  /* Period should be computed from frequency f as 10^9 / (279.365 * s * f),
   * where s is the sample length.  With proper constant folding, this should
   * compile to (C/Hertz) where C is a constant.
   */
  return (UWORD)((1000000000/(279.365*SAMPLELENGTH))/Hertz);
}


/* PeriodToHertz():
 * Converts audio.device period length to human-readable pitch in Hertz.  This
 * is the inverse of HertzToPeriod().
 */
LONG PeriodToHertz(const UWORD period)
{
  /* Pitch in Hertz is computed from period f as 279.365 * s * p / 10^9, where s
   * is sample length.
   */
  return (LONG)((279.365 * SAMPLELENGTH / 1000000000) * period);
}


/* SliderToPeriod():
 * Converts a prefs window slider position (between 0 and 9*12) to a period
 * length (in units of 279.365 nanoseconds) suitable for use by audio.device.
 */
UWORD SliderToPeriod(const WORD sliderpos)
{
  /* For a frequency f, the period length is 10^9 / (279.365 * s * f).  Here s
   * is the length of the waveform sample (SAMPLELENGTH).
   * So we need to compute 10^9 / (279.365 * s * (440 * 2^(n/12)))
   * == 2^(-n/12) * 10^9 / (279.365 * 440 * s)
   * ==  2^(-n/12) * 10^9 / (122920.6 * s)
   */
  return (UWORD)(pow((double)2,-sliderpos/(double)12) *
                ((double)10000000 / (1229.206 * SAMPLELENGTH)));
}


/* PeriodToSlider():
 * Inverse of SliderToPeriod().  Takes a period length as an argument and
 * computes the appropriate slider position (between -5*12 and 4*12).  This
 * function can afford to be slow because it's only ever called when the prefs
 * window pops up.
 */
WORD PeriodToSlider(const UWORD period)
{
  /* Since the period number p can be computed from slider position n by
   *  p = 10^9 / (279.365 * 440 * s * 2^(n/12))  <==>
   *  2^(n/12) = 10^9 / (279.365 * 440 * s * p)  <==>
   *  n/12 = 2log(10^9 / (279.365 * 440 * s * p)) <==>
   *  n = 12 * 2log(10^9 / (279.365 * 440 * s * p))  ==
   *      12 * 2log(10^9 / (122920.6 * s * p))  ==
   *      12 * ln(10^9 /  (122920.6 * s * p)) / ln(2)
   */
  return (WORD)(12 * log(
                (double)1000000000 / (122920.6*SAMPLELENGTH*period)
               )/log((double)2));
}

