/**
  audio: this file implements the audio system.
*/

#ifndef _LCR_AUDIO
#define _LCR_AUDIO

#define LCR_SOUND_NONE 0
#define LCR_SOUND_CLICK 1
#define LCR_SOUND_CRASH_SMALL 2
#define LCR_SOUND_CRASH_BIG 3
#define LCR_SOUND_ACCELERATOR 4

#define LCR_AUDIO_CRASH_LEN 2048

struct
{
  uint32_t frame;
  uint8_t on;
  uint8_t soundPlayed;
  uint16_t soundPlayedFrame;
  uint32_t noise;
  uint8_t crashSample;
  int engineIntensity;
  int engineOsc;
  int engineInc;
} LCR_audio;

void LCR_audioInit(void)
{
  LCR_LOG0("initializing audio");
  LCR_audio.frame = 0;
  LCR_audio.on = 1;
  LCR_audio.soundPlayed = LCR_SOUND_NONE;
  LCR_audio.soundPlayedFrame = 0;
  LCR_audio.noise = 0;
  LCR_audio.crashSample = 0;
  LCR_audio.engineOsc = 0;
  LCR_audio.engineInc = 1;
  LCR_audio.engineIntensity = 0;
}

void LCR_audioSetEngineIntensity(uint8_t value)
{
  LCR_audio.engineIntensity = value;
}

void LCR_audioPlaySound(uint8_t sound)
{
  LCR_LOG2("playing sound");
  LCR_audio.soundPlayed = sound;
  LCR_audio.soundPlayedFrame = 0;
}

uint8_t _LCR_audioNoise(void)
{
  LCR_audio.noise = LCR_audio.noise * 32310901 + 37;
  return LCR_audio.noise >> 16;
}

uint8_t LCR_audioGetNextSample(void)
{
  unsigned char result = 128;

  if (!LCR_audio.on)
    return result;

  switch (LCR_audio.soundPlayed)
  {
    case LCR_SOUND_CRASH_SMALL: 
    case LCR_SOUND_CRASH_BIG:
    {
      int limit = (LCR_audio.soundPlayed == LCR_SOUND_CRASH_BIG ? 256 : 128) - 
        (LCR_audio.soundPlayedFrame * 256) / LCR_AUDIO_CRASH_LEN;

      if (LCR_audio.frame % 2 || // lower frequency
        LCR_audio.soundPlayedFrame < LCR_AUDIO_CRASH_LEN / 4)
        LCR_audio.crashSample = _LCR_audioNoise();

      if (LCR_audio.crashSample > limit)
        LCR_audio.crashSample = (_LCR_audioNoise() % 2) ? 0 : limit;

      result += LCR_audio.crashSample - limit / 2;

      if (limit == 0)
        LCR_audio.soundPlayed = LCR_SOUND_NONE;

      break;
    }

    case LCR_SOUND_CLICK:
    {
      int v = ((LCR_audio.soundPlayedFrame >> 6) *
        (LCR_audio.soundPlayedFrame >> 1)
        + LCR_audio.soundPlayedFrame * 35) & 0x1c;

      if (LCR_audio.soundPlayedFrame < 250)
        result = v;
      else if (LCR_audio.soundPlayedFrame < 400)
        result = (v + 128) / 2;
      else if (LCR_audio.soundPlayedFrame < 600)
        result = (v + 3 * 128) / 4;
      else
        LCR_audio.soundPlayed = LCR_SOUND_NONE;

      break;
    }

    default:
      break;
  }

  if (LCR_audio.soundPlayed != LCR_SOUND_NONE)
    LCR_audio.soundPlayedFrame++;
  else if (LCR_audio.engineIntensity)
  {
    LCR_audio.engineOsc += LCR_audio.engineInc ? (((_LCR_audioNoise() % 256) <
        (10 + LCR_audio.engineIntensity))) : -31;

    if (LCR_audio.engineInc && LCR_audio.engineOsc >
      (90 + (LCR_audio.engineIntensity / 8)))
      LCR_audio.engineInc = 0;
    else if ((!LCR_audio.engineInc) && LCR_audio.engineOsc < 10)
      LCR_audio.engineInc = 1;

    result += LCR_audio.engineIntensity < 20 ?
      LCR_audio.engineOsc / 2 : LCR_audio.engineOsc;
  }

  LCR_audio.frame++;

  return result;
}

#endif // guard
