/*
 * so-666.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-666.h"

static float dist(float in) {
  float out;
  out = tanhf(in);
  return out;
}

inline float feedback_scale(float input) {
  return 0.01 + powf(input, 4.0) * 0.9;
}
inline float cutoff_scale(float input) { return powf(input, 5.0); }

void runSO_666(LV2_Handle arg, uint32_t nframes) {
  so_666* so = (so_666*)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* outbuffer = so->output;
  int* status = so->status;

  int i, note;
  float sample, damp;

  if (*so->controlmode_p > 0) {
    so->ffeedback = feedback_scale(*so->feedback_p);
    so->fcutoff = cutoff_scale(*so->cutoff_p);
    so->freso = *so->resonance_p;
    so->volume = *so->volume_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;
            }
          } 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_scale((cutoff + 50.0) / 200.0);
            } else if (evt[1] == 71) {
              unsigned int resonance = evt[2];
              so->freso = resonance / 127.0;
            } else if (evt[1] == 7) {
              so->volume = evt[2];
            } else if (evt[1] == 1) {
              unsigned int feedback = evt[2];
              so->ffeedback = feedback_scale(feedback / 127.0);
            }
          }
        }
      }
      iter = lv2_atom_sequence_next(iter);
    }
    sample = (((float)rand() / (float)RAND_MAX) * 2.0 - 1.0) * 0.001;

    for (note = 0; note < NUMNOTES; note++) {
      damp = so->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);
      }
      strings[note][stringpos[note]] =
          dist(strings[note][stringpos[note]]) * 0.99;

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

    so->hpval += (sample - so->hplast) * 0.0001;
    so->hplast += so->hpval;
    so->hpval *= 0.96;
    sample -= so->hplast;

    so->lpval += (sample - so->lplast) * so->fcutoff *
                 (1.0 - tanh(so->lplast) * tanh(so->lplast) * 0.9);
    so->lplast += so->lpval;
    so->lpval *= so->freso;
    sample = so->lplast;
    for (note = 0; note < NUMNOTES; note++) {
      if (status[note] > 0) {
        strings[note][stringpos[note]] += sample * so->ffeedback;
      }

      if (fabs(strings[note][stringpos[note]]) <= 0.0001) {
        strings[note][stringpos[note]] = 0.0;
      }
      stringpos[note]++;
      if (stringpos[note] >= stringlength[note]) {
        stringpos[note] = 0;
      }
    }
    outbuffer[i] = dist(sample) * (so->volume / 127.0);
  }
}

LV2_Handle instantiateSO_666(const LV2_Descriptor* descriptor, double s_rate,
                             const char* path,
                             const LV2_Feature* const* features) {
  so_666* so = malloc(sizeof(so_666));
  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 feedback, cutoff, resonance;
  feedback = 32;
  cutoff = 64;
  resonance = 64;
  so->volume = 100;
  so->fcutoff = powf((cutoff + 50.0) / 200.0, 5.0);
  so->freso = resonance / 127.0;
  so->ffeedback = 0.01 + powf(feedback / 127.0, 4.0) * 0.9;

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

  int note;
  for (note = 0; note < NUMNOTES; note++) {
    float freq = 440.0 * powf(2.0, (note + BASENOTE - 69) / 12.0);
    // so->stringcutoff[note] = ( freq * 16.0 ) / (float)s_rate;
    so->stringcutoff[note] = 0.9;
    int length = (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;
    }
    unsigned int i;
    for (i = 0; i < (length); i++) {
      so->strings[note][i] = 0.0;
    }
    so->stringpos[note] = 0;
    so->status[note] = 0;
  }

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

void connectPortSO_666(LV2_Handle instance, uint32_t port,
                       void* data_location) {
  so_666* so = (so_666*)instance;
  switch (port) {
    case PORT_OUTPUT:
      so->output = data_location;
      break;
    case PORT_MIDI:
      so->midi_p = data_location;
      break;
    case PORT_CONTROLMODE:
      so->controlmode_p = data_location;
      break;
    case PORT_FEEDBACK:
      so->feedback_p = data_location;
      break;
    case PORT_RESONANCE:
      so->resonance_p = data_location;
      break;
    case PORT_CUTOFF:
      so->cutoff_p = data_location;
      break;
    case PORT_VOLUME:
      so->volume_p = data_location;
      break;
    case PORT_CHANNEL:
      so->channel_p = data_location;
      break;
    default:
      fputs("Warning, unconnected port!\n", stderr);
  }
}
