/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            drumgizmo.cc
 *
 *  Thu Sep 16 10:24:40 CEST 2010
 *  Copyright 2010 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of DrumGizmo.
 *
 *  DrumGizmo 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.
 *
 *  DrumGizmo 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 DrumGizmo; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "drumgizmo.h"

#include <math.h>
#include <stdio.h>

#include <event.h>
#include <audiotypes.h>

#include <string.h>

#include "drumkitparser.h"

DrumGizmo::DrumGizmo(AudioOutputEngine *o, AudioInputEngine *i)
  : oe(o), ie(i)
{
}

DrumGizmo::~DrumGizmo()
{
  /*
  AudioFiles::iterator i = audiofiles.begin();
  while(i != audiofiles.end()) {
    AudioFile *audiofile = i->second;
    delete audiofile;
    i++;
  }
  */
}

std::string DrumGizmo::drumkitfile()
{
  return kitfile;
}

bool DrumGizmo::loadkit(const std::string &kitfile)
{
  this->kitfile = kitfile;
  kit = DrumKit();
  DrumKitParser parser(kitfile, kit);
  if(parser.parse()) return false;
  /*
  Instruments::iterator i = kit.instruments.begin();
  while(i != kit.instruments.end()) {
    Instrument &instr = i->second;
    InstrumentParser iparser(instr.file, instr);
    if(iparser.parse()) return false;
    i++;
  }
  */
  return true;
}

bool DrumGizmo::init(bool preload)
{
  if(preload) {
    /*
    AudioFiles::iterator i = audiofiles.begin();
    while(i != audiofiles.end()) {
      AudioFile *audiofile = i->second;
      audiofile->load();
      i++;
    }
    */
  }

  if(!ie->init(kit.instruments)) return false;
  if(!oe->init(kit.channels)) return false;

  return true;
}

bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples)
{
  ie->pre();
  oe->pre(nsamples);


  //
  // Read new events
  //

  size_t nev;
  event_t *evs = ie->run(pos, nsamples, &nev);

  for(size_t e = 0; e < nev; e++) {
    if(evs[e].type == TYPE_ONSET) {
      Instrument *i = NULL;
      int d = evs[e].instrument;
      /*
        Instruments::iterator it = kit.instruments.begin();
        while(d-- && it != kit.instruments.end()) {
        i = &(it->second);
        it++;
        }
      */
      
      if(d < (int)kit.instruments.size()) {
        i = kit.instruments[d];
      }
      
      if(i == NULL) {
        printf("Missing Instrument %d.\n", evs[e].instrument);
        continue;
      }
      
      Sample *s = i->sample(evs[e].velocity, evs[e].offset + pos);
      
      if(s == NULL) {
        printf("Missing Sample.\n");
        continue;
      }
      
      Channels::iterator j = kit.channels.begin();
      while(j != kit.channels.end()) {
        Channel &ch = *j;
        AudioFile *af = s->getAudioFile(&ch);
        if(af == NULL) {
          //printf("Missing AudioFile.\n");
        } else {
          //printf("Adding event %d.\n", evs[e].offset);
          Event *evt = new EventSample(ch.num, 1.0, af);
          evt->offset = evs[e].offset + pos;
          activeevents[ch.num].push_back(evt);
        }
        j++;
      }
    }
    
    if(evs[e].type == TYPE_STOP) {
      return false;
    }
    
  }
    
  free(evs);
  
  
  //
  // Write audio
  //
  
  for(size_t c = 0; c < kit.channels.size(); c++) {
    sample_t *buf = samples;
    bool internal = false;
    if(oe->getBuffer(c)) {
      buf = oe->getBuffer(c);
      internal = true;
    }
    memset(buf, 0, nsamples * sizeof(sample_t));
    getSamples(c, pos, buf, nsamples);
    if(!internal) oe->run(c, samples, nsamples);
  }
  
  ie->post();
  oe->post(nsamples);
  
  pos += nsamples;

  return true;
}

void DrumGizmo::run()
{
  ie->start();
  oe->start();

  size_t pos = 0;
  size_t nsamples = 512;
  sample_t samples[nsamples];

  while(run(pos, samples, nsamples) == true) {
    pos += nsamples;
  }

  ie->stop();
  oe->stop();
}

#ifdef SSE
#define N 8
typedef float vNsf __attribute__ ((vector_size(sizeof(float)*N)));
#endif/*SSE*/

void DrumGizmo::getSamples(int ch, int pos, sample_t *s, size_t sz)
{
  for(std::list< Event* >::iterator i = activeevents[ch].begin();
      i != activeevents[ch].end();
      i++) {
    bool removeevent = false;

    Event *event = *i;

    Event::type_t type = event->type();
    switch(type) {
    case Event::sample:
      {
        EventSample *evt = (EventSample *)event;
        AudioFile *af = evt->file;
        af->load(); // Make sure it is loaded.

        size_t n = 0;
        if(evt->offset > (size_t)pos) n = evt->offset - pos;
        size_t end = sz;
        if(evt->t + end - n > af->size) end = af->size - evt->t + n;

#ifdef SSE
        size_t optend = ((end - n) / N) * N + n;
        for(; n < optend; n += N) {
          *(vNsf*)&(s[n]) += *(vNsf*)&(af->data[evt->t]);
          evt->t += N;
        }
#endif
        //printf("n: %d end: %d, diff: %d", n, end, end - n); fflush(stdout);
        for(; n < end; n++) {
          s[n] += af->data[evt->t];
          evt->t++;
        }

        if(evt->t > af->size) removeevent = true;
      }
      break;
    }

    if(removeevent) {
      delete event;
      i = activeevents[ch].erase(i);
    }

  }
}

void DrumGizmo::stop()
{
  // engine.stop();
}

#include "configuration.h"

std::string float2str(float a)
{
  char buf[256];
  sprintf(buf, "%f", a);
  return buf;
}

std::string bool2str(bool a)
{
  return a?"true":"false";
}

float str2float(std::string a)
{
  if(a == "") return 0.0;
  return atof(a.c_str());
}

std::string DrumGizmo::configString()
{
  return
    "<config>\n"
    "  <value name=\"drumkitfile\">" + kitfile + "</value>\n"
    "  <value name=\"midimapfile\">" + midimapfile + "</value>\n"
    "  <value name=\"enable_velocity_modifier\">" +
    bool2str(Conf::enable_velocity_modifier) + "</value>\n"
    "  <value name=\"velocity_modifier_falloff\">" +
    float2str(Conf::velocity_modifier_falloff) + "</value>\n"
    "  <value name=\"velocity_modifier_weight\">" +
    float2str(Conf::velocity_modifier_weight) + "</value>\n"
    "  <value name=\"enable_velocity_randomiser\">" +
    bool2str(Conf::enable_velocity_randomiser) + "</value>\n"
    "  <value name=\"velocity_randomiser_weight\">" +
    float2str(Conf::velocity_randomiser_weight) + "</value>\n"
    "</config>";
}

#include "saxparser.h"

class ConfigParser : public SAXParser {
public:
  ConfigParser()
  {
    str = NULL;
  }

  void characterData(std::string &data)
  {
    if(str) str->append(data);
  }

  void startTag(std::string name, attr_t attr)
  {
    if(name == "value" && attr.find("name") != attr.end()) {
      values[attr["name"]] = "";
      str = &values[attr["name"]];
    }
  }

  void endTag(std::string name)
  {
    if(name == "value") str = NULL;
  }

  std::string value(std::string name, std::string def = "")
  {
    if(values.find(name) == values.end()) return def;
    return values[name];
  }

  std::map<std::string, std::string> values;
  std::string *str;
};

void DrumGizmo::setConfigString(std::string cfg)
{
  printf("Load config: %s\n", cfg.c_str());

  std::string dkf;
  ConfigParser p;
  p.parse(cfg);

  midimapfile = p.value("midimapfile");

  if(p.value("enable_velocity_modifier") != "") {
    Conf::enable_velocity_modifier =
      p.value("enable_velocity_modifier") == "true";
  }

  if(p.value("velocity_modifier_falloff") != "") {
    Conf::velocity_modifier_falloff =
      str2float(p.value("velocity_modifier_falloff"));
  }

  if(p.value("velocity_modifier_weight") != "") {
    Conf::velocity_modifier_weight =
      str2float(p.value("velocity_modifier_weight"));
  }

  if(p.value("enable_velocity_randomiser") != "") {
    Conf::enable_velocity_randomiser =
      p.value("enable_velocity_randomiser") == "true";
  }

  if(p.value("velocity_randomiser_weight") != "") {
    Conf::velocity_randomiser_weight =
      str2float(p.value("velocity_randomiser_weight"));
  }

  if(drumkitfile() != p.value("drumkitfile")) {
    loadkit(p.values["drumkitfile"]);
    init(true);
  }
}

#ifdef TEST_DRUMGIZMO
//deps: instrument.cc sample.cc channel.cc audiofile.cc drumkit.cc drumkitparser.cc configuration.cc saxparser.cc instrumentparser.cc path.cc
//cflags: $(SNDFILE_CFLAGS) $(EXPAT_CFLAGS) -I../include -DSSE -msse -msse2 -msse3
//libs: $(SNDFILE_LIBS) $(EXPAT_LIBS)
#include "test.h"

static float f(size_t x)
{
  return x + 1.0;
}

class AITest : public AudioInputEngine {
public:
  bool init(Instruments &instruments) { return true; }
  void setParm(std::string parm, std::string value) {}
  bool start() { return true; }
  void stop() {}
  void pre() {}
  event_t *run(size_t pos, size_t len, size_t *nevents)
  {
    event_t *e = NULL;
    *nevents = 0;

    if(pos <= offset && offset < pos + len) {
      e = new event_t;

      e->type = TYPE_ONSET;
      e->instrument = 0;
      e->velocity = 1.0;
      e->offset = offset - pos;

      *nevents = 1;
    }
    return e;
  }
  void post() {}
  size_t offset;
};

class AOTest : public AudioOutputEngine {
public:
  bool init(Channels channels) { return true; }
  void setParm(std::string parm, std::string value) {}
  bool start() { return true; }
  void stop() {}
  void pre(size_t nsamples) {}
  void run(int ch, sample_t *samples, size_t nsamples)
  {
  }
  void post(size_t nsamples) {}
};

const char xml_kit[] =
  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
  "<drumkit name=\"test\" description=\"\">\n"
  "  <channels>\n"
  "    <channel name=\"ch1\"/>\n"
  "  </channels>\n"
  "  <instruments>\n"
  "    <instrument name=\"instr1\" file=\"instr1.xml\">\n"
  "      <channelmap in=\"ch1\" out=\"ch1\"/>\n"
  "		</instrument>\n"
  "	</instruments>\n"
  "</drumkit>";

const char xml_instr[] =
  "<?xml version='1.0' encoding='UTF-8'?>\n"
  "<instrument name=\"instr1\">\n"
  " <samples>\n"
  "  <sample name=\"sample1\">\n"
  "   <audiofile channel=\"ch1\" file=\"instr1.wav\"/>\n"
  "  </sample>\n"
  " </samples>\n"
  " <velocities>\n"
  "  <velocity lower=\"0\" upper=\"1.0\">\n"
  "   <sampleref name=\"sample1\"/>\n"
  "  </velocity>\n"
  " </velocities>\n"
  "</instrument>";

#define PCM_SIZE 100

void createTestKit()
{
  FILE *fp;
  fp = fopen("/tmp/kit.xml", "w");
  fwrite(xml_kit, strlen(xml_kit), 1, fp);
  fclose(fp);

  fp = fopen("/tmp/instr1.xml", "w");
  fwrite(xml_instr, strlen(xml_instr), 1, fp);
  fclose(fp);

  SF_INFO sf_info;
  sf_info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
  sf_info.samplerate = 44100;
  sf_info.channels = 1;

  SNDFILE *fh = sf_open("/tmp/instr1.wav", SFM_WRITE, &sf_info);
  if(!fh) {
    printf("Error: %s\n", sf_strerror(fh));
  }

  size_t size = PCM_SIZE;
  sample_t samples[size];

  for(size_t i = 0; i < size; i++) {
    samples[i] = f(i);//(float)i / (float)size;
  }

  sf_write_float(fh, samples, size); 
  sf_close(fh);
}

void deleteTestKit()
{
  unlink("/tmp/kit.xml");
  unlink("/tmp/instr1.xml");
  unlink("/tmp/instr1.wav");
}

TEST_BEGIN;

createTestKit();

size_t size = PCM_SIZE;
//for(size_t chunksz = 1; chunksz < size + 1; chunksz++) {
size_t chunksz = 16; {
  sample_t samples[chunksz];

  for(size_t offset = 0; offset < chunksz + size + 1; offset++) {
    //size_t offset = 5; {
    for(size_t padding = 0; padding < chunksz + size + offset + 1; padding++) {
      //size_t padding = 2; {
      TEST_MSG("Values (offset %d, padding %d, chunksz %d)",
               offset, padding, chunksz);
      
      AOTest ao;
      AITest ai; ai.offset = offset;
      DrumGizmo dg(&ao, &ai);
      dg.loadkit("/tmp/kit.xml");
      
      size_t pos = 0;
      //      sample_t samples[chunksz];
      while(pos < offset + size + padding) {
        dg.run(pos, samples, chunksz);
        
        float err = 0;
        size_t errcnt = 0;
        for(size_t i = 0; i < chunksz && pos < offset + size + padding; i++) {
          float val = 0.0;
          if(pos >= offset && pos < (offset + size)) val = f(pos - offset);
          float diff = samples[i] - val;
          /*
          if(diff != 0.0) {
            TEST_EQUAL_FLOAT(samples[i], val,
                           "samples[%d] ?= val, pos %d", i, pos);
          }
          */
          if(diff != 0.0) errcnt++;

          err += fabs(diff);
          pos++;
        }

        TEST_EQUAL_FLOAT(err, 0.0,
                         "Compare error (offset %d, padding %d, chunksz %d)",
                         offset, padding, chunksz);
        TEST_EQUAL_INT(errcnt, 0,
                       "Compare count (offset %d, padding %d, chunksz %d)",
                       offset, padding, chunksz);
      }

    }
  }
}

deleteTestKit();

TEST_END;

#endif/*TEST_DRUMGIZMO*/