/*
 waveforms.c
 -----------
 Waveform editor
 19/11/24
*/

#include <math.h>
#include "common.h"
#include "wimp.h"
#include "lib.h"
#include "synth.h"
#include "main.h"
#include "editor.h"

#define NPT (1<<SIN_BITS) // fft bins (= samples in single cycle waveforms)
typedef struct fft_data_s // fft data
{
  int xr[NPT]; // real component
  int xi[NPT]; // imaginary component
} fft_data_t;

// fft.s
#define INVERSE 0
#define FORWARD 1
static fft_data_t fd;
void fft(fft_data_t *fd, int inv);

//line.s
void line(void *start, int x1, int y1, int x2, int y2, int colour);

static harm_t edit_harm; // copy of the waveform harmonics data
static int cur_harm; // the displayed harmonic
static unsigned int cur_use; // a bit set for each instrument oscillator being tested
static int x0; // mouse x position when first clicked inside a harmonic bar
static int p0; // cur harm phase when mouse first clicked inside a harmonic bar

// waveform icon size in pixels
#define PLOT_HEIGHT 128
#define PLOT_WIDTH  512

// desktop 256 colour palette colours
#define BLACK   0
#define GREY    3
#define GREEN   99
#define BLUE    136
#define LT_BLUE 139
#define MAGENTA 156

#define m40dB ((1<<14)/100) // -40db


/*
 * Integer square root
 * -------------------
 * Calculates the integer square root and remainder.
 * Returns the square root.
 */
static unsigned int isqrt(unsigned int n)
{
  unsigned int q, r, s;
  int i;
  q = r = 0;
  for(i=30; i>=0; i-=2)
  {
    r = (r << 2) + ((n >> i) & 3); // shift 2 bits in
    s = (q << 2) + 1; // 4*q + 1
    q <<= 1;
    if(r >= s)
    {
      r -= s;
      q++;
    }
  }
  // q = square root of n, r = remainder
  return q;
}


/*
 * draw_grid
 * ---------
 * zero is the colour of the central horizontal grid line
 */
static void draw_grid(int colour, int zero)
{
  int i;

  // vertical axis
  line(ro.wave_plot, 0, PLOT_HEIGHT/4, PLOT_WIDTH-1, PLOT_HEIGHT/4, colour);
  // centre zero dashed
  for(i=0; i<PLOT_WIDTH; i += 8)
    line(ro.wave_plot, i, PLOT_HEIGHT/2-1, i + 4, PLOT_HEIGHT/2-1, zero);
  line(ro.wave_plot, 0, 3*PLOT_HEIGHT/4, PLOT_WIDTH-1, 3*PLOT_HEIGHT/4, colour);
  // horizontal axis
  for(i=0; i<PLOT_WIDTH; i+=PLOT_WIDTH/16)
    line(ro.wave_plot, i, 0, i, PLOT_HEIGHT-1, colour);
}


/*
 * plot_waveform
 * -------------
 */
static void plot_waveform(harm_t *h)
{
  int i, j;

  // create waveform from harmonic array
  memset(&fd, 0, sizeof(fd));
  for(i=1; i<=NUM_HARMS; i++)
  {
    fd.xr[i] = h->hrm[i-1].re;
    fd.xi[i] = h->hrm[i-1].im;
    j = (NPT - i) & (NPT - 1);
    fd.xr[j] = fd.xr[i];
    fd.xi[j] = -fd.xi[i];
  }
  fft(&fd, INVERSE);

  // limit data to signed 16 bits and copy to sprite
  memset(ro.wave_plot, BLACK, PLOT_HEIGHT*PLOT_WIDTH);
  draw_grid(GREY, LT_BLUE);
  LIMIT(-0x8000, fd.xr[0], 0x7fff);
  int y0 = PLOT_HEIGHT - 1 - ((fd.xr[0] + 0x8000) >> 9);
  int x0 = 0;
  for(i=2; i<NPT; i+=2)
  {
    LIMIT(-0x8000, fd.xr[i], 0x7fff);
    int x = i >> 1;
    int y = PLOT_HEIGHT - 1 - ((fd.xr[i] + 0x8000) >> 9);
    line(ro.wave_plot, x0, y0, x, y, GREEN);
    x0 = x;
    y0 = y;
  }
  // inform the wimp to replot the sprite
  icon_state_change(0, ro.handle[WIN_WAVEFORMS], ICON_WED_WAVEFORM);
}


/*
 * send_edit_wave
 * --------------
 * Sends a pointer to the edit harmonics structure to the synth so
 * it can update its copy of the edit waveform.
 *
 * action = the bit number to set/clear in the synth switches to
 *          select what the edit waveform is applied to in the
 *          'user' edit instrument.
 *
 *        = 0,  oscillator 1
 *        = 1,  oscillator 2
 *        = 2,  modulation oscillator
 *        = -1, switches are not changed
 *
 * state  = the state to set the bit to, if one is selected
 *
 * The waveform harmonic data is always sent, so the synth edit
 * waveform will always be updated.
 */
void send_edit_wave(int action, int state)
{
  _kernel_swi_regs regs;
  int blk[12];

  if(action == -1) // don't change switches
  {
    regs.r[2] = 0;
    regs.r[3] = 0;
  }
  else
  {
    cur_use = ((cur_use & ~(1<<action)) | (state << action));
    regs.r[2] = 1 << action;
    regs.r[3] = state << action;
  }
  regs.r[0] = SYN_EDIT_WAVE;
  regs.r[1] = (int)blk;
  regs.r[4] = (int)&edit_harm;
  _kernel_swi(MIDISynth_Edit, &regs, &regs);
  update_syn_vars(blk);
}


/*
 * save_wave
 * ---------
 * w = wave index to save in, -1 = save new
 * h = pointer to harmonics data, 0 = delete [w]
 */
void save_wave(int w, harm_t *h)
{
  _kernel_oserror *err;
  _kernel_swi_regs regs;
  int blk[12];

  regs.r[0] = SYN_SAVE_WAVE;
  regs.r[1] = (int)blk;
  regs.r[2] = (int)h;
  regs.r[3] = w;
  err = _kernel_swi(MIDISynth_Edit, &regs, &regs);

  if(!err)
    update_syn_vars(blk);
  else
    report_error(err->errmess, 1);
}


/*
 * update_wave_display
 * -------------------
 */
static void update_wave_display(int wav_num)
{
  ro.cur_wav = wav_num;
  int win = ro.handle[WIN_WAVEFORMS];
  harm_t *h = &edit_harm;
  int i;
  for(i=0; i<NUM_HARMS; i++)
  {
    struct hrm_s *r = &h->hrm[i];
    int n = isqrt((r->re * r->re) + (r->im * r->im));
    int db;
    if(n > m40dB)
      db = (int)(round(200 * log10((double)n / (1<<14)))); // tenth db's
    else
    {
      n = 0;
      db = -400;
    }
    slider_display_value(db, SLDR_WED_AMP, WIN_WAVEFORMS, ICON_WED_AMP_SLDR + 2*i);

    if(i == cur_harm)
    {
      if(n)
      {
        int phase = (int)(round(atan2(r->im, r->re) * 180 / PI)); // degrees
        icon_text_change(itoa(db), win, ICON_WED_MAG_VAL);
        icon_text_change(itoa(phase), win, ICON_WED_PHASE_VAL);
      }
      else
      {
        icon_text_change("", win, ICON_WED_MAG_VAL);
        icon_text_change("", win, ICON_WED_PHASE_VAL);
      }
    }
  }
  icon_text_change(h->name, win, ICON_WED_NAME);
  icon_text_change(itoa(ro.cur_wav), win, ICON_WED_WAV_VAL);
  icon_text_change(itoa(cur_harm + 1), win, ICON_WED_HARM_VAL);

  plot_waveform(h);

  if(cur_use)
    send_edit_wave(-1,0); // update the synth copy
}


/*
 * update_waveforms_window
 * -----------------------
 * Only called when the window is opened
 */
void update_waveforms_window(void)
{
  synth_command(SYN_NULL,0); // get curent settings
  edit_harm = ro.harm[ro.cur_wav];
  update_wave_display(ro.cur_wav);
}


/*
 * update_magnitude
 * ----------------
 * n = entered magnitude in dB's
 */
static void update_magnitude(int n)
{
  LIMIT(-401, n, 30);
  struct hrm_s *h = &edit_harm.hrm[cur_harm];
  int mag = isqrt((h->re * h->re) + (h->im * h->im));
  if(n < -400)
    h->re = h->im = 0;
  else if(mag == 0)
  {
    h->re = m40dB;
    h->im = 0;
  }
  else
  {
    int mag = round(pow(10, (double)n / 200) * (1<<14));
    double phase = atan2(h->im, h->re);
    h->re = (int)round(cos(phase) * mag);
    h->im = (int)round(sin(phase) * mag);
  }
  update_wave_display(ro.cur_wav);
}


/*
 * update_phase
 * ------------
 * n = entered phase in degrees
 */
static void update_phase(int n)
{
  n = ((n + 179) % 360) - 179;
  struct hrm_s *h = &edit_harm.hrm[cur_harm];
  int mag = isqrt((h->re * h->re) + (h->im * h->im));
  double phase = (double)n * PI / 180;
  h->re = (int)round(cos(phase) * mag);
  h->im = (int)round(sin(phase) * mag);
  update_wave_display(ro.cur_wav);
}


/*
 * wave_slider_control
 * -------------------
 * blk = block from Wimp_GetPointerInfo
 * Returns True if dealt with here, else False
 */
int wave_slider_control(int *blk)
{
  if(ro.slider.window != ro.handle[WIN_WAVEFORMS])
    return FALSE;

  struct hrm_s *h = &edit_harm.hrm[cur_harm];

  if(blk[2] == MOUSE_ADJUST) // adjust phase
  {
    if((h->re == 0) && (h->im == 0))
    {
      icon_text_change("", ro.slider.window, ICON_WED_MAG_VAL);
      icon_text_change("", ro.slider.window, ICON_WED_PHASE_VAL);
    }
    else
    {
      int n = p0 + ((blk[0] - x0) / 2);
      while(n < -179) n += 360;
      n = ((n + 179) % 360) - 179;
      icon_text_change(itoa(n), ro.slider.window, ICON_WED_PHASE_VAL);
      int mag = isqrt((h->re * h->re) + (h->im * h->im));
      int db = (int)(round(200 * log10((double)mag / (1<<14)))); // tenth db's
      icon_text_change(itoa(db), ro.slider.window, ICON_WED_MAG_VAL);
      double phase = (double)n * PI / 180;
      h->re = (int)round(cos(phase) * mag);
      h->im = (int)round(sin(phase) * mag);
    }
  }

  else // MOUSE_SELECT, adjust magnitude
  {
    int n = slider_get_posn(blk, SLDR_WED_AMP);
    slider_display_value(n, SLDR_WED_AMP, WIN_WAVEFORMS, ro.slider.icon);

    if(n == -400) // dragged off bottom
    {
      h->re = h->im = 0;
      icon_text_change("", ro.slider.window, ICON_WED_MAG_VAL);
      icon_text_change("", ro.slider.window, ICON_WED_PHASE_VAL);
    }
    else
    {
      int mag = round(pow(10, (double)n / 200) * (1<<14));
      double phase;
      if((h->re == 0) && (h->im == 0))
        phase = 0; // -PI/2;
      else
        phase = atan2(h->im, h->re);
      h->re = (int)round(cos(phase) * mag);
      h->im = (int)round(sin(phase) * mag);
      icon_text_change(itoa(n), ro.slider.window, ICON_WED_MAG_VAL);
      int deg = (int)(round(phase * 180 / PI)); // degrees
      icon_text_change(itoa(deg), ro.slider.window, ICON_WED_PHASE_VAL);
    }
  }

  plot_waveform(&edit_harm);

  if(cur_use)
    send_edit_wave(-1,0); // update the synth copy

  return TRUE;
}


/*
 * wave_mouse_click
 * ---------------
 * blk = block from Wimp_Poll
 * state = icon state, selected or not
 * Returns True if dealt with here, else False
 */
int wave_mouse_click(int *blk, int state)
{
  static int swapped;     // true when temp is loaded from ro.harm
  static harm_t temp;
  int ret = TRUE;
  int n;
  const char err1[] = "Waveform must have a name";

  // Control window
  if(blk[3] == ro.handle[WIN_WAVEFORMS])
  {
    if(blk[2] == MOUSE_MENU)
      open_menu(blk[0] - 64, blk[1], MENU_WAVEFORM, (int)&waveform_menu);

    else
    {
      int inc = 0;
      if(blk[2] == MOUSE_SELECT)
        inc = 1;
      else if (blk[2] == MOUSE_ADJUST)
        inc = -1;

       switch (blk[4])
      {
        case ICON_WED_RELOAD: // reload / restore
          read_text_string(blk[3], ICON_WED_NAME, edit_harm.name); // read waveform name
          if(swapped)
            edit_harm = temp; // edit = temp
          else
          {
            temp = edit_harm; // temp = edit
            edit_harm = ro.harm[ro.cur_wav]; // edit = current
          }
          update_wave_display(ro.cur_wav);
          swapped ^= 1;
          icon_text_change((swapped) ? "Restore" : "Reload", blk[3], blk[4]);
          icon_colour_change((swapped) ? 0x1b : 0x17, blk[3], blk[4]); // red, black
          break;

        case ICON_WED_CLEAR:
          memset(&edit_harm, 0, sizeof(harm_t));
          cur_harm = 0;
          icon_state_change(0, blk[3], ICON_WED_INVERT);
          icon_state_change(0, blk[3], ICON_WED_REVERSE);
          update_wave_display(ro.cur_wav);
          break;

        case ICON_WED_SAVE:
          read_text_string(blk[3], ICON_WED_NAME, edit_harm.name); // read waveform name
          if(edit_harm.name[0] == 0)
            report_error(err1, 0);
          else
            save_wave(ro.cur_wav, &edit_harm);
          break;

        case ICON_WED_SAVE_NEW:
          read_text_string(blk[3], ICON_WED_NAME, edit_harm.name); // read waveform name
          if(edit_harm.name[0] == 0)
            report_error(err1, 0);
          else
            save_wave(-1, &edit_harm);
          break;

        case ICON_WED_WAV_DEC:
          inc = -inc;
        case ICON_WED_WAV_INC:
          n = read_numeric_value(blk[3], ICON_WED_WAV_VAL) + inc;
          WRAP(0, n, ro.num_wavs - 1);
          while(ro.harm[n].name[0] == 0) // skip deleted slots
          {
            n += inc;
            WRAP(0, n, ro.num_wavs - 1);
          }
          icon_text_change(itoa(n), blk[3], ICON_WED_WAV_VAL);
          edit_harm = ro.harm[n];
          update_wave_display(n);
          icon_state_change(0, blk[3], ICON_WED_INVERT);
          icon_state_change(0, blk[3], ICON_WED_REVERSE);
          break;

        case ICON_WED_HARM_DEC:
          inc = -inc;
        case ICON_WED_HARM_INC:
          n = read_numeric_value(blk[3], ICON_WED_HARM_VAL) + inc - 1;
          WRAP(0, n, NUM_HARMS - 1);
          cur_harm = n;
          update_wave_display(ro.cur_wav);
          break;

        case ICON_WED_MAG_DEC:
          inc = -inc;
        case ICON_WED_MAG_INC:
          {
            char s[NAME_LEN];
            read_text_string(blk[3], ICON_WED_MAG_VAL, s);
            if(s[0] == 0)
              n = -401;
            else
              n = atoi(s);
          }
          n += inc;
          update_magnitude(n);
          break;

        case ICON_WED_PHASE_DEC:
          inc = -inc;
        case ICON_WED_PHASE_INC:
          n = read_numeric_value(blk[3], ICON_WED_PHASE_VAL) + inc;
          update_phase(n);
          break;

        case ICON_WED_GAIN_DEC:
          inc = -inc;
        case ICON_WED_GAIN_INC:
          // Multiply / divide all harmonic data by the increment.
          for(n=0; n<NUM_HARMS; n++)
          {
            struct hrm_s *h = &edit_harm.hrm[n];
            int mag = isqrt((h->re * h->re) + (h->im * h->im));
            if(mag >= 23142) // +3dB
              break;
          }
          if((inc < 0) || (n >= NUM_HARMS)) // Don't increase if any harmonic is already at max
          { // ok to adjust
            for(n=0; n<NUM_HARMS; n++)
            {
              struct hrm_s *h = &edit_harm.hrm[n];
              double i = 1 + (double)inc / 50; // 2 percent increment
              h->re = (int)(round((double)h->re * i));
              h->im = (int)(round((double)h->im * i));
            }
            update_wave_display(ro.cur_wav);
          }
          break;

        case ICON_WED_SHF_LEFT:
          inc = -inc;
        case ICON_WED_SHF_RIGHT:
          // increase / decrease all harmonic phase as follows,
          // For each harmonic:
          //   The nth harmonic will be adjusted by n times the increment.
          for(n=0; n<NUM_HARMS; n++)
          {
            struct hrm_s *h = &edit_harm.hrm[n];
            if((h->re != 0) || (h->im != 0))
            {
              int i = -inc * (n + 1);
              double phase = atan2(h->im, h->re) + (PI / 180 * i);
              double mag = sqrt((h->re * h->re) + (h->im * h->im));
              h->re = (int)round(cos(phase) * mag);
              h->im = (int)round(sin(phase) * mag);
            }
          }
          update_wave_display(ro.cur_wav);
          break;

        case ICON_WED_INVERT:
          for(n=0; n<NUM_HARMS; n++)
          {
            struct hrm_s *h = &edit_harm.hrm[n];
            h->re = -h->re;
            h->im = -h->im;
          }
          update_wave_display(ro.cur_wav);
          break;

        case ICON_WED_REVERSE:
          for(n=0; n<NUM_HARMS; n++)
          {
            struct hrm_s *h = &edit_harm.hrm[n];
            h->im = -h->im;
          }
          update_wave_display(ro.cur_wav);
          break;

        default: // magnitude bars
          if((blk[4] >= ICON_WED_AMP_BACK) &&
             (blk[4] <= (ICON_WED_AMP_BACK + 63)))
          {
            if(((blk[4] - ICON_WED_AMP_BACK) & 1) == 0)
              blk[4]++;
            ro.slider.window = blk[3];
            ro.slider.icon = blk[4];
            cur_harm = (blk[4] - ICON_WED_AMP_BACK) >> 1;
            struct hrm_s *h = &edit_harm.hrm[cur_harm];
            if(!h->re && !h->im)
              p0 = x0 = 0;
            else
            {
              p0 = (int)round(atan2(h->im, h->re) * 180 / PI);
              x0 = blk[0];
            }
            icon_text_change(itoa(cur_harm + 1), blk[3], ICON_WED_HARM_VAL);
          }
          break;
      }
    }
  }

  else
    ret = FALSE;

  return ret;
}


/*
 * wave_key_press
 * -------------
 * blk = block from Wimp_GetIconState
 * key = current character
 * p = pointer to icon text
 * Returns True if dealt with here, else False
 */
int wave_key_press(int *blk, int key, char *p)
{
  int n = atoi(p);

  // Control window
  if(blk[0] == ro.handle[WIN_WAVEFORMS])
  {
    if(key == '\r')
      switch(blk[1])
      {
        case ICON_WED_NAME: // waveform name
          if(p[0])
            strncpy(edit_harm.name, p, NAME_LEN);
          break;

        case ICON_WED_WAV_VAL: // waveform number
          LIMIT(0, n, ro.num_wavs - 1);
          while(ro.harm[n].name[0] == 0) // skip deleted slots
            if(++n >= ro.num_wavs)
              n = 0;
          icon_text_change(itoa(n), blk[0], ICON_WED_WAV_VAL);
          edit_harm = ro.harm[n];
          update_wave_display(n);
          break;

        case ICON_WED_HARM_VAL: // harmonic number
          LIMIT(1, n, NUM_HARMS);
          cur_harm = n - 1;
          update_wave_display(ro.cur_wav);
          break;

        case ICON_WED_MAG_VAL: // harmonic magnitude
          update_magnitude(n);
          break;

        case ICON_WED_PHASE_VAL: // harmonic phase
          update_phase(n);
          break;
      }
  }

  else
    return FALSE;

  return TRUE;
}

