/*
 * so-kl5.c
 *
 * Copyright (C) 2011 - Jeremy Salwen
 * Copyright (C) 2010 - 50m30n3
 * Copyright (C) 2020 - Google LLC
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "so-kl5.h"

inline float sustain_scale(float input) { return 0.6 + powf(input, 0.4) * 0.4; }
void runSO_kl5(LV2_Handle arg, uint32_t nframes) {
  so_kl5* so = (so_kl5*)arg;
  LV2_Atom_Event* iter = lv2_atom_sequence_begin(&so->midi_p->body);

  float** strings = so->strings;
  unsigned int* stringpos = so->stringpos;
  unsigned int* stringlength = so->stringlength;
  float* tempstring = so->tempstring;
  float* stringcutoff = so->stringcutoff;
  float* outbuffer = so->output;
  int* status = so->status;

  int i, note;
  float sample, damp;

  if (*so->controlmode_p > 0) {
    so->volume = *so->volume_p;
    so->fcutoff = *so->cutoff_p;
    so->sattack = *so->attack_p;
    so->freso = (*so->resonance_p) * (1.0 - (*so->cutoff_p));
    so->ssustain = sustain_scale(*so->sustain_p);
  }

  for (i = 0; i < nframes; i++) {
    while (!lv2_atom_sequence_is_end(&so->midi_p->body, so->midi_p->atom.size,
                                     iter)) {
      if (iter->time.frames > i) {
        break;
      }
      if (iter->body.type == so->uris.midi_MidiEvent) {
        const uint8_t* evt = (uint8_t*)(iter + 1);
        if ((evt[0] & MIDI_CHANNELMASK) == (int)(*so->channel_p)) {
          if ((evt[0] & MIDI_COMMANDMASK) == MIDI_NOTEON) {
            note = evt[1];
            if ((note >= BASENOTE) && (note < BASENOTE + NUMNOTES)) {
              note -= BASENOTE;

              status[note] = 1;

              int j;
              for (j = 0; j < stringlength[note]; j++) {
                tempstring[j] = ((float)rand() / (float)RAND_MAX) * 2.0 - 1.0;
              }
              float velocity = evt[2];
              float freq = stringcutoff[note] * 0.25 + velocity / 127.0 * 0.2 +
                           so->sattack + 0.1;

              for (j = 0; j < 30; j++) {
                tempstring[0] =
                    tempstring[0] * freq +
                    tempstring[stringlength[note] - 1] * (1.0 - freq);
                int k;
                for (k = 1; k < stringlength[note]; k++) {
                  tempstring[k] =
                      tempstring[k] * freq +
                      tempstring[(k - 1) % stringlength[note]] * (1.0 - freq);
                }
              }

              float avg = 0.0;

              for (j = 0; j < stringlength[note]; j++) {
                avg += tempstring[j];
              }

              avg /= stringlength[note];

              float scale = 0.0;

              for (j = 0; j < stringlength[note]; j++) {
                tempstring[j] -= avg;
                if (fabs(tempstring[j]) > scale) scale = fabs(tempstring[j]);
              }

              float min = 10.0;
              int minpos = 0;

              for (j = 0; j < stringlength[note]; j++) {
                tempstring[j] /= scale;
                if (fabs(tempstring[j]) +
                        fabs(tempstring[j] - tempstring[j - 1]) * 5.0 <
                    min) {
                  min = fabs(tempstring[j]) +
                        fabs(tempstring[j] - tempstring[j - 1]) * 5.0;
                  minpos = j;
                }
              }

              float vol = velocity / 256.0;

              for (j = 0; j < stringlength[note]; j++) {
                strings[note][(stringpos[note] + j) % stringlength[note]] +=
                    tempstring[(j + minpos) % stringlength[note]] * vol;
              }
            }
          } else if ((evt[0] & MIDI_COMMANDMASK) == MIDI_NOTEOFF) {
            note = evt[1];
            if ((note >= BASENOTE) && (note < BASENOTE + NUMNOTES)) {
              note -= BASENOTE;
              status[note] = 0;
            }
          } else if ((*so->controlmode_p <= 0) &&
                     (evt[0] & MIDI_COMMANDMASK) == MIDI_CONTROL) {
            if (evt[1] == 74) {
              unsigned int cutoff = evt[2];
              so->fcutoff = (cutoff + 5.0) / 400.0;
            } else if (evt[1] == 71) {
              unsigned int resonance = evt[2];
              so->freso = (resonance / 140.0) * (1.0 - so->fcutoff);
            } else if (evt[1] == 73) {
              unsigned int attack = evt[2];
              so->sattack = (attack + 5.0) / 800.0;
            } else if (evt[1] == 7) {
              so->volume = evt[2];
            } else if (evt[1] == 1 || evt[1] == 64) {
              unsigned int sustain = evt[2];
              so->ssustain = sustain_scale(sustain / 127.0);
            }
          }
        }
      }
      iter = lv2_atom_sequence_next(iter);
    }
    sample = 0.0;

    for (note = 0; note < NUMNOTES; note++) {
      damp = stringcutoff[note];

      if (stringpos[note] > 0)
        strings[note][stringpos[note]] =
            strings[note][stringpos[note]] * damp +
            strings[note][stringpos[note] - 1] * (1.0 - damp);
      else
        strings[note][stringpos[note]] =
            strings[note][stringpos[note]] * damp +
            strings[note][stringlength[note] - 1] * (1.0 - damp);

      damp = ((float)note / (float)NUMNOTES) * 0.0049995 + 0.0049995;

      if (status[note] == 0)
        strings[note][stringpos[note]] *= 0.8 + so->ssustain * 0.19 + damp;
      else
        strings[note][stringpos[note]] *= 0.99 + damp;

      sample += strings[note][stringpos[note]];
    }

    for (note = 0; note < NUMNOTES; note++) {
      if (fabs(strings[note][stringpos[note]]) <= 0.00001)
        strings[note][stringpos[note]] = 0.0;

      stringpos[note]++;
      if (stringpos[note] >= stringlength[note]) stringpos[note] = 0;
    }

    so->lpval += (sample - so->lplast) * so->fcutoff;
    so->lplast += so->lpval;
    so->lpval *= so->freso;
    sample = so->lplast;

    outbuffer[i] = sample * (so->volume / 127.0);
  }
}

LV2_Handle instantiateSO_kl5(const LV2_Descriptor* descriptor, double s_rate,
                             const char* path,
                             const LV2_Feature* const* features) {
  so_kl5* so = malloc(sizeof(so_kl5));
  for (int i = 0; features[i]; ++i) {
    if (!strcmp(features[i]->URI, LV2_URID__map)) {
      LV2_URID_Map* map = (LV2_URID_Map*)features[i]->data;
      so->uris.midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
      break;
    }
  }

  puts("SO-666 v.1.0 by 50m30n3 2009");

  unsigned int cutoff, resonance, sustain, attack;
  sustain = 0;
  cutoff = 64;
  resonance = 100;
  attack = 4;
  so->volume = 100;

  so->lplast = 0;
  so->lpval = 0;

  so->fcutoff = (cutoff + 5.0) / 400.0;
  so->sattack = (attack + 5.0) / 800.0;
  so->freso = (resonance / 160.0) * (1.0 - so->fcutoff);
  so->ssustain = 0.6 + powf(sustain / 127.0, 0.4);

  int note;
  for (note = 0; note < NUMNOTES; note++) {
    float freq = 440.0 * powf(2.0, (note + BASENOTE - 69) / 12.0);
    so->stringcutoff[note] =
        0.5 + pow((double)note / (double)NUMNOTES, 0.5) * 0.45;
    int length = round((float)s_rate / freq);
    so->stringlength[note] = length;
    so->strings[note] = malloc(length * sizeof(float));
    if (so->strings[note] == NULL) {
      fputs("Error allocating memory\n", stderr);
      return 0;
    }
    int i;
    for (i = 0; i < length; i++) {
      so->strings[note][i] = 0.0;
    }
    so->stringpos[note] = 0;
    so->status[note] = 0;
  }

  float freq = 440.0 * powf(2.0, (BASENOTE - 69) / 12.0);
  float length = (float)s_rate / freq;
  so->tempstring = malloc(length * sizeof(float));
  if (so->tempstring == NULL) {
    fputs("Error allocating memory\n", stderr);
    return 0;
  }

  return so;
}
void cleanupSO_kl5(LV2_Handle instance) {
  so_kl5* so = (so_kl5*)instance;
  free(so->tempstring);
  int note;
  for (note = 0; note < NUMNOTES; note++) {
    free(so->strings[note]);
  }
  free(so);
}

void connectPortSO_kl5(LV2_Handle instance, uint32_t port,
                       void* data_location) {
  so_kl5* so = (so_kl5*)instance;
  switch (port) {
    case KL5_PORT_OUTPUT:
      so->output = data_location;
      break;
    case KL5_PORT_MIDI:
      so->midi_p = data_location;
      break;
    case KL5_PORT_CONTROLMODE:
      so->controlmode_p = data_location;
      break;
    case KL5_PORT_VOLUME:
      so->volume_p = data_location;
      break;
    case KL5_PORT_SUSTAIN:
      so->sustain_p = data_location;
      break;
    case KL5_PORT_CUTOFF:
      so->cutoff_p = data_location;
      break;
    case KL5_PORT_RESONANCE:
      so->resonance_p = data_location;
      break;
    case KL5_PORT_ATTACK:
      so->attack_p = data_location;
      break;
    case KL5_PORT_CHANNEL:
      so->channel_p = data_location;
      break;
    default:
      fputs("Warning, unconnected port!\n", stderr);
  }
}
