text: // MIT License
  
  // Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com
  
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to deal
  // in the Software without restriction, including without limitation the rights
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  
  // The above copyright notice and this permission notice shall be included in all
  // copies or substantial portions of the Software.
  
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  // SOFTWARE.
  
  
  #include "api.h"
  #include "core.h"
  
  #include <string.h>
  #include <assert.h>
  
  #if defined(DINGUX) && !defined(static_assert)
  #define static_assert _Static_assert
  #endif
  
  #define ENVELOPE_FREQ_SCALE 2
  #define SECONDS_PER_MINUTE 60
  #define NOTES_PER_MUNUTE (TIC80_FRAMERATE / NOTES_PER_BEAT * SECONDS_PER_MINUTE)
  #define PIANO_START 8
  
  static const u16 NoteFreqs[] = { 0x10, 0x11, 0x12, 0x13, 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1c, 0x1d, 0x1f, 0x21, 0x23, 0x25, 0x27, 0x29, 0x2c, 0x2e, 0x31, 0x34, 0x37, 0x3a, 0x3e, 0x41, 0x45, 0x49, 0x4e, 0x52, 0x57, 0x5c, 0x62, 0x68, 0x6e, 0x75, 0x7b, 0x83, 0x8b, 0x93, 0x9c, 0xa5, 0xaf, 0xb9, 0xc4, 0xd0, 0xdc, 0xe9, 0xf7, 0x106, 0x115, 0x126, 0x137, 0x14a, 0x15d, 0x172, 0x188, 0x19f, 0x1b8, 0x1d2, 0x1ee, 0x20b, 0x22a, 0x24b, 0x26e, 0x293, 0x2ba, 0x2e4, 0x310, 0x33f, 0x370, 0x3a4, 0x3dc, 0x417, 0x455, 0x497, 0x4dd, 0x527, 0x575, 0x5c8, 0x620, 0x67d, 0x6e0, 0x749, 0x7b8, 0x82d, 0x8a9, 0x92d, 0x9b9, 0xa4d, 0xaea, 0xb90, 0xc40, 0xcfa, 0xdc0, 0xe91, 0xf6f, 0x105a, 0x1153, 0x125b, 0x1372, 0x149a, 0x15d4, 0x1720, 0x1880 };
  static_assert(COUNT_OF(NoteFreqs) == NOTES * OCTAVES + PIANO_START, "count_of_freqs");
  static_assert(sizeof(tic_sound_register) == 16 + 2,                 "tic_sound_register");
  static_assert(sizeof(tic_sample) == 66,                             "tic_sample");
  static_assert(sizeof(tic_track_pattern) == 3 * MUSIC_PATTERN_ROWS,  "tic_track_pattern");
  static_assert(sizeof(tic_track) == 3 * MUSIC_FRAMES + 3,            "tic_track");
  static_assert(tic_music_cmd_count == 1 << MUSIC_CMD_BITS,           "tic_music_cmd_count");
  static_assert(sizeof(tic_music_state) == 4,                         "tic_music_state_size");
  
  static s32 getTempo(tic_core* core, const tic_track* track)
  {
      return core->state.music.tempo < 0 
          ? track->tempo + DEFAULT_TEMPO 
          : core->state.music.tempo;
  }
  
  static s32 getSpeed(tic_core* core, const tic_track* track)
  {
      return core->state.music.speed < 0 
          ? track->speed + DEFAULT_SPEED 
          : core->state.music.speed;
  }
  
  static s32 tick2row(tic_core* core, const tic_track* track, s32 tick)
  {
      // BPM = tempo * 6 / speed
      s32 speed = getSpeed(core, track);
      return speed 
          ? tick * getTempo(core, track) * DEFAULT_SPEED / speed / NOTES_PER_MUNUTE
          : 0;
  }
  
  static s32 row2tick(tic_core* core, const tic_track* track, s32 row)
  {
      s32 tempo = getTempo(core, track);
      return tempo
          ? row * getSpeed(core, track) * NOTES_PER_MUNUTE / tempo / DEFAULT_SPEED
          : 0;
  }
  
  static inline s32 param2val(const tic_track_row* row)
  {
      return (row->param1 << 4) | row->param2;
  }
  
  static void update_amp(blip_buffer_t* blip, tic_sound_register_data* data, s32 new_amp)
  {
      s32 delta = new_amp - data->amp;
      data->amp += delta;
      blip_add_delta(blip, data->time, delta);
  }
  
  static inline s32 freq2period(s32 freq)
  {
      enum
      {
          MinPeriodValue = 10,
          MaxPeriodValue = 4096,
          Rate = CLOCKRATE * ENVELOPE_FREQ_SCALE / WAVE_VALUES
      };
  
      if (freq == 0) return MaxPeriodValue;
  
      return CLAMP(Rate / freq - 1, MinPeriodValue, MaxPeriodValue);
  }
  
  static inline s32 getAmp(const tic_sound_register* reg, s32 amp)
  {
      enum { AmpMax = (u16)-1 / 2 };
      return (amp * AmpMax / MAX_VOLUME) * reg->volume / MAX_VOLUME / TIC_SOUND_CHANNELS;
  }
  
  static void runEnvelope(blip_buffer_t* blip, const tic_sound_register* reg, tic_sound_register_data* data, s32 end_time, u8 volume)
  {
      s32 period = freq2period(reg->freq * ENVELOPE_FREQ_SCALE);
  
      for (; data->time < end_time; data->time += period)
      {
          data->phase = (data->phase + 1) % WAVE_VALUES;
  
          update_amp(blip, data, getAmp(reg, tic_tool_peek4(reg->waveform.data, data->phase) * volume / MAX_VOLUME));
      }
  }
  
  static void runNoise(blip_buffer_t* blip, const tic_sound_register* reg, tic_sound_register_data* data, s32 end_time, u8 volume)
  {
      // phase is noise LFSR, which must never be zero 
      if (data->phase == 0)
          data->phase = 1;
  
      s32 period = freq2period(reg->freq);
  
      for (; data->time < end_time; data->time += period)
      {
          data->phase = ((data->phase & 1) * (0b11 << 13)) ^ (data->phase >> 1);
          update_amp(blip, data, getAmp(reg, (data->phase & 1) ? volume : 0));
      }
  }
  
  static s32 calcLoopPos(const tic_sound_loop* loop, s32 pos)
  {
      s32 offset = 0;
  
      if (loop->size > 0)
      {
          for (s32 i = 0; i < pos; i++)
          {
              if (offset < (loop->start + loop->size - 1))
                  offset++;
              else offset = loop->start;
          }
      }
      else offset = pos >= SFX_TICKS ? SFX_TICKS - 1 : pos;
  
      return offset;
  }
  
  static void resetSfxPos(tic_channel_data* channel)
  {
      memset(channel->pos->data, -1, sizeof(tic_sfx_pos));
      channel->tick = -1;
  }
  
  static void sfx(tic_mem* memory, s32 index, s32 note, s32 pitch, tic_channel_data* channel, tic_sound_register* reg, s32 channelIndex)
  {
      tic_core* core = (tic_core*)memory;
  
      if (channel->duration > 0)
          channel->duration--;
  
      if (index < 0 || channel->duration == 0)
      {
          resetSfxPos(channel);
          return;
      }
  
      const tic_sample* effect = &memory->ram.sfx.samples.data[index];
      s32 pos = tic_tool_sfx_pos(channel->speed, ++channel->tick);
  
      for (s32 i = 0; i < sizeof(tic_sfx_pos); i++)
          *(channel->pos->data + i) = calcLoopPos(effect->loops + i, pos);
  
      u8 volume = MAX_VOLUME - effect->data[channel->pos->volume].volume;
  
      if (volume > 0)
      {
          s8 arp = effect->data[channel->pos->chord].chord * (effect->reverse ? -1 : 1);
          if (arp) note += arp;
  
          note = CLAMP(note, 0, COUNT_OF(NoteFreqs) - 1);
  
          reg->freq = NoteFreqs[note] + effect->data[channel->pos->pitch].pitch * (effect->pitch16x ? 16 : 1) + pitch;
          reg->volume = volume;
  
          u8 wave = effect->data[channel->pos->wave].wave;
          const tic_waveform* waveform = &memory->ram.sfx.waveforms.items[wave];
          memcpy(reg->waveform.data, waveform->data, sizeof(tic_waveform));
  
          tic_tool_poke4(&memory->ram.stereo.data, channelIndex * 2, channel->volume.left * !effect->stereo_left);
          tic_tool_poke4(&memory->ram.stereo.data, channelIndex * 2 + 1, channel->volume.right * !effect->stereo_right);
      }
  }
  
  static void setChannelData(tic_mem* memory, s32 index, s32 note, s32 octave, s32 duration, tic_channel_data* channel, s32 volumeLeft, s32 volumeRight, s32 speed)
  {
      tic_core* core = (tic_core*)memory;
  
      channel->volume.left = volumeLeft;
      channel->volume.right = volumeRight;
  
      if (index >= 0)
      {
          struct { s8 speed : SFX_SPEED_BITS; } temp = { speed };
          channel->speed = speed == temp.speed ? speed : memory->ram.sfx.samples.data[index].speed;
      }
  
      channel->note = note + octave * NOTES;
      channel->duration = duration;
      channel->index = index;
  
      resetSfxPos(channel);
  }
  
  
  static void setMusicChannelData(tic_mem* memory, s32 index, s32 note, s32 octave, s32 left, s32 right, s32 channel)
  {
      tic_core* core = (tic_core*)memory;
      setChannelData(memory, index, note, octave, -1, &core->state.music.channels[channel], left, right, SFX_DEF_SPEED);
  }
  
  static void resetMusicChannels(tic_mem* memory)
  {
      for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
          setMusicChannelData(memory, -1, 0, 0, 0, 0, c);
  
      tic_core* core = (tic_core*)memory;
      memset(core->state.music.commands, 0, sizeof core->state.music.commands);
      memset(&core->state.music.jump, 0, sizeof(tic_jump_command));
  }
  
  static void stopMusic(tic_mem* memory)
  {
      tic_api_music(memory, -1, 0, 0, false, false, -1, -1);
  }
  
  static void processMusic(tic_mem* memory)
  {
      tic_core* core = (tic_core*)memory;
      tic_music_state* music_state = &memory->ram.music_state;
  
      if (music_state->flag.music_status == tic_music_stop) return;
  
      const tic_track* track = &memory->ram.music.tracks.data[music_state->music.track];
      s32 row = tick2row(core, track, core->state.music.ticks);
      tic_jump_command* jumpCmd = &core->state.music.jump;
  
      if (row != music_state->music.row
          && jumpCmd->active)
      {
          music_state->music.frame = jumpCmd->frame;
          row = jumpCmd->beat * NOTES_PER_BEAT;
          core->state.music.ticks = row2tick(core, track, row);
          memset(jumpCmd, 0, sizeof(tic_jump_command));
      }
  
      s32 rows = MUSIC_PATTERN_ROWS - track->rows;
      if (row >= rows)
      {
          row = 0;
          core->state.music.ticks = 0;
  
          // If music is in sustain mode, we only reset the channels if the music stopped.
          // Otherwise, we reset it on every new frame.
          if (music_state->flag.music_status == tic_music_stop || !music_state->flag.music_sustain)
          {
              resetMusicChannels(memory);
  
              for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
                  setMusicChannelData(memory, -1, 0, 0, MAX_VOLUME, MAX_VOLUME, c);
          }
  
          if (music_state->flag.music_status == tic_music_play)
          {
              music_state->music.frame++;
  
              if (music_state->music.frame >= MUSIC_FRAMES)
              {
                  if (music_state->flag.music_loop)
                      music_state->music.frame = 0;
                  else
                  {
                      stopMusic(memory);
                      return;
                  }
              }
              else
              {
                  s32 val = 0;
                  for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
                      val += tic_tool_get_pattern_id(track, music_state->music.frame, c);
  
                  // empty frame detected
                  if (!val)
                  {
                      if (music_state->flag.music_loop)
                          music_state->music.frame = 0;
                      else
                      {
                          stopMusic(memory);
                          return;
                      }
                  }
              }
          }
          else if (music_state->flag.music_status == tic_music_play_frame)
          {
              if (!music_state->flag.music_loop)
              {
                  stopMusic(memory);
                  return;
              }
          }
      }
  
      if (row != music_state->music.row)
      {
          music_state->music.row = row;
  
          for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
          {
              s32 patternId = tic_tool_get_pattern_id(track, music_state->music.frame, c);
              if (!patternId) continue;
  
              const tic_track_pattern* pattern = &memory->ram.music.patterns.data[patternId - PATTERN_START];
              const tic_track_row* trackRow = &pattern->rows[music_state->music.row];
              tic_channel_data* channel = &core->state.music.channels[c];
              tic_command_data* cmdData = &core->state.music.commands[c];
  
              if (trackRow->command == tic_music_cmd_delay)
              {
                  cmdData->delay.row = trackRow;
                  cmdData->delay.ticks = param2val(trackRow);
                  trackRow = NULL;
              }
  
              if (cmdData->delay.row && cmdData->delay.ticks == 0)
              {
                  trackRow = cmdData->delay.row;
                  cmdData->delay.row = NULL;
              }
  
              if (trackRow)
              {
                  // reset commands data
                  if (trackRow->note)
                  {
                      cmdData->slide.tick = 0;
                      cmdData->slide.note = channel->note;
                  }
  
                  if (trackRow->note == NoteStop)
                      setMusicChannelData(memory, -1, 0, 0, channel->volume.left, channel->volume.right, c);
                  else if (trackRow->note >= NoteStart)
                      setMusicChannelData(memory, tic_tool_get_track_row_sfx(trackRow), trackRow->note - NoteStart, trackRow->octave,
                          channel->volume.left, channel->volume.right, c);
  
                  switch (trackRow->command)
                  {
                  case tic_music_cmd_volume:
                      channel->volume.left = trackRow->param1;
                      channel->volume.right = trackRow->param2;
                      break;
  
                  case tic_music_cmd_chord:
                      cmdData->chord.tick = 0;
                      cmdData->chord.note1 = trackRow->param1;
                      cmdData->chord.note2 = trackRow->param2;
                      break;
  
                  case tic_music_cmd_jump:
                      core->state.music.jump.active = true;
                      core->state.music.jump.frame = trackRow->param1;
                      core->state.music.jump.beat = trackRow->param2;
                      break;
  
                  case tic_music_cmd_vibrato:
                      cmdData->vibrato.tick = 0;
                      cmdData->vibrato.period = trackRow->param1;
                      cmdData->vibrato.depth = trackRow->param2;
                      break;
  
                  case tic_music_cmd_slide:
                      cmdData->slide.duration = param2val(trackRow);
                      break;
  
                  case tic_music_cmd_pitch:
                      cmdData->finepitch.value = param2val(trackRow) - PITCH_DELTA;
                      break;
  
                  default: break;
                  }
              }
          }
      }
  
      for (s32 i = 0; i < TIC_SOUND_CHANNELS; ++i)
      {
          tic_channel_data* channel = &core->state.music.channels[i];
          tic_command_data* cmdData = &core->state.music.commands[i];
  
          if (channel->index >= 0)
          {
              s32 note = channel->note;
              s32 pitch = 0;
  
              // process chord commmand
              {
                  s32 chord[] =
                  {
                      0,
                      cmdData->chord.note1,
                      cmdData->chord.note2
                  };
  
                  note += chord[cmdData->chord.tick % (cmdData->chord.note2 == 0 ? 2 : 3)];
              }
  
              // process vibrato commmand
              if (cmdData->vibrato.period && cmdData->vibrato.depth)
              {
                  static const s32 VibData[] = { 0x0, 0x31f1, 0x61f8, 0x8e3a, 0xb505, 0xd4db, 0xec83, 0xfb15, 0x10000, 0xfb15, 0xec83, 0xd4db, 0xb505, 0x8e3a, 0x61f8, 0x31f1, 0x0, 0xffffce0f, 0xffff9e08, 0xffff71c6, 0xffff4afb, 0xffff2b25, 0xffff137d, 0xffff04eb, 0xffff0000, 0xffff04eb, 0xffff137d, 0xffff2b25, 0xffff4afb, 0xffff71c6, 0xffff9e08, 0xffffce0f };
                  static_assert(COUNT_OF(VibData) == 32, "VibData");
  
                  s32 p = cmdData->vibrato.period << 1;
                  pitch += (VibData[(cmdData->vibrato.tick % p) * COUNT_OF(VibData) / p] * cmdData->vibrato.depth) >> 16;
              }
  
              // process slide command
              if (cmdData->slide.tick < cmdData->slide.duration)
                  pitch += (NoteFreqs[channel->note] - NoteFreqs[note = cmdData->slide.note]) * cmdData->slide.tick / cmdData->slide.duration;
  
              pitch += cmdData->finepitch.value;
  
              sfx(memory, channel->index, note, pitch, channel, &memory->ram.registers[i], i);
          }
  
          ++cmdData->chord.tick;
          ++cmdData->vibrato.tick;
          ++cmdData->slide.tick;
  
          if (cmdData->delay.ticks)
              cmdData->delay.ticks--;
      }
  
      core->state.music.ticks++;
  }
  
  static void setSfxChannelData(tic_mem* memory, s32 index, s32 note, s32 octave, s32 duration, s32 channel, s32 left, s32 right, s32 speed)
  {
      tic_core* core = (tic_core*)memory;
      setChannelData(memory, index, note, octave, duration, &core->state.sfx.channels[channel], left, right, speed);
  }
  
  static void setMusic(tic_core* core, s32 index, s32 frame, s32 row, bool loop, bool sustain, s32 tempo, s32 speed)
  {
      tic_mem* memory = (tic_mem*)core;
      tic_ram* ram = &memory->ram;
  
      ram->music_state.music.track = index;
  
      if (index < 0)
      {
          ram->music_state.flag.music_status = tic_music_stop;
          resetMusicChannels(memory);
      }
      else
      {
          for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
              setMusicChannelData(memory, -1, 0, 0, MAX_VOLUME, MAX_VOLUME, c);
  
          ram->music_state.music.row = -1;
          ram->music_state.music.frame = frame < 0 ? 0 : frame;
          ram->music_state.flag.music_loop = loop;
          ram->music_state.flag.music_sustain = sustain;
          ram->music_state.flag.music_status = tic_music_play;
  
          const tic_track* track = &ram->music.tracks.data[index];
          core->state.music.tempo = tempo;
          core->state.music.speed = speed;
          core->state.music.ticks = row >= 0 ? row2tick(core, track, row) : 0;
      }
  }
  
  void tic_api_music(tic_mem* memory, s32 index, s32 frame, s32 row, bool loop, bool sustain, s32 tempo, s32 speed)
  {
      tic_core* core = (tic_core*)memory;
  
      setMusic(core, index, frame, row, loop, sustain, tempo, speed);
  
      if (index >= 0)
          memory->ram.music_state.flag.music_status = tic_music_play;
  }
  
  void tic_api_sfx(tic_mem* memory, s32 index, s32 note, s32 octave, s32 duration, s32 channel, s32 left, s32 right, s32 speed)
  {
      tic_core* core = (tic_core*)memory;
      setSfxChannelData(memory, index, note, octave, duration, channel, left, right, speed);
  }
  
  static void stereo_tick_end(tic_mem* memory, tic_sound_register_data* registers, blip_buffer_t* blip, u8 stereoRight)
  {
      enum { EndTime = CLOCKRATE / TIC80_FRAMERATE };
      for (s32 i = 0; i < TIC_SOUND_CHANNELS; ++i)
      {
          u8 volume = tic_tool_peek4(&memory->ram.stereo.data, stereoRight + i * 2);
  
          const tic_sound_register* reg = &memory->ram.registers[i];
          tic_sound_register_data* data = registers + i;
  
          EMPTY(reg->waveform.data)
              ? runNoise(blip, reg, data, EndTime, volume)
              : runEnvelope(blip, reg, data, EndTime, volume);
  
          data->time -= EndTime;
      }
  
      blip_end_frame(blip, EndTime);
  }
  
  void tic_core_sound_tick_start(tic_mem* memory)
  {
      tic_core* core = (tic_core*)memory;
  
      for (s32 i = 0; i < TIC_SOUND_CHANNELS; ++i)
          memset(&memory->ram.registers[i], 0, sizeof(tic_sound_register));
  
      memory->ram.stereo.data = -1;
  
      processMusic(memory);
  
      for (s32 i = 0; i < TIC_SOUND_CHANNELS; ++i)
      {
          tic_channel_data* c = &core->state.sfx.channels[i];
  
          if (c->index >= 0)
              sfx(memory, c->index, c->note, 0, c, &memory->ram.registers[i], i);
      }
  }
  
  void tic_core_sound_tick_end(tic_mem* memory)
  {
      tic_core* core = (tic_core*)memory;
  
      stereo_tick_end(memory, core->state.registers.left, core->blip.left, 0);
      stereo_tick_end(memory, core->state.registers.right, core->blip.right, 1);
  
      blip_read_samples(core->blip.left, core->memory.samples.buffer, core->samplerate / TIC80_FRAMERATE, TIC_STEREO_CHANNELS);
      blip_read_samples(core->blip.right, core->memory.samples.buffer + 1, core->samplerate / TIC80_FRAMERATE, TIC_STEREO_CHANNELS);
  }
  