/*
  ZynAddSubFX - a software synthesizer

  SynthNote.cpp - Abstract Synthesizer Note Instance
  Copyright (C) 2016 Mark McCurry

  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.
*/
#include "SynthNote.h"
#include "../globals.h"
#include <cstring>
#include <new>
#include <iostream>

namespace zyncarla {

SynthNote::SynthNote(SynthParams &pars)
    :memory(pars.memory),
    legato(pars.synth, pars.frequency, pars.velocity, pars.portamento,
            pars.note, pars.quiet), ctl(pars.ctl), synth(pars.synth), time(pars.time)
{}

SynthNote::Legato::Legato(const SYNTH_T &synth_, float freq, float vel, int port,
                          int note, bool quiet)
    :synth(synth_)
{
    // Initialise some legato-specific vars
    msg = LM_Norm;
    fade.length = (int)(synth.samplerate_f * 0.005f);      // 0.005f seems ok.
    if(fade.length < 1)
        fade.length = 1;                    // (if something's fishy)
    fade.step  = (1.0f / fade.length);
    decounter  = -10;
    param.freq = freq;
    param.vel  = vel;
    param.portamento = port;
    param.midinote   = note;
    lastfreq = 0.0f;
    silent   = quiet;
}

int SynthNote::Legato::update(LegatoParams pars)
{
    if(pars.externcall)
        msg = LM_Norm;
    if(msg != LM_CatchUp) {
        lastfreq   = param.freq;
        param.freq = pars.frequency;
        param.vel  = pars.velocity;
        param.portamento = pars.portamento;
        param.midinote   = pars.midinote;
        if(msg == LM_Norm) {
            if(silent) {
                fade.m = 0.0f;
                msg    = LM_FadeIn;
            }
            else {
                fade.m = 1.0f;
                msg    = LM_FadeOut;
                return 1;
            }
        }
        if(msg == LM_ToNorm)
            msg = LM_Norm;
    }
    return 0;
}

void SynthNote::Legato::apply(SynthNote &note, float *outl, float *outr)
{
    if(silent) // Silencer
        if(msg != LM_FadeIn) {
            memset(outl, 0, synth.bufferbytes);
            memset(outr, 0, synth.bufferbytes);
        }
    try {
        switch (msg) {
            case LM_CatchUp: // Continue the catch-up...
                if (decounter == -10)
                    decounter = fade.length;
                //Yea, could be done without the loop...
                for (int i = 0; i < synth.buffersize; ++i) {
                    decounter--;
                    if (decounter < 1) {
                        // Catching-up done, we can finally set
                        // the note to the actual parameters.
                        decounter = -10;
                        msg = LM_ToNorm;
                        LegatoParams pars{param.freq, param.vel, param.portamento,
                                          param.midinote, false};
                        note.legatonote(pars);
                        break;
                    }
                }
                break;
            case LM_FadeIn: // Fade-in
                if (decounter == -10)
                    decounter = fade.length;
                silent = false;
                for (int i = 0; i < synth.buffersize; ++i) {
                    decounter--;
                    if (decounter < 1) {
                        decounter = -10;
                        msg = LM_Norm;
                        break;
                    }
                    fade.m += fade.step;
                    outl[i] *= fade.m;
                    outr[i] *= fade.m;
                }
                break;
            case LM_FadeOut: // Fade-out, then set the catch-up
                if (decounter == -10)
                    decounter = fade.length;
                for (int i = 0; i < synth.buffersize; ++i) {
                    decounter--;
                    if (decounter < 1) {
                        for (int j = i; j < synth.buffersize; ++j) {
                            outl[j] = 0.0f;
                            outr[j] = 0.0f;
                        }
                        decounter = -10;
                        silent = true;
                        // Fading-out done, now set the catch-up :
                        decounter = fade.length;
                        msg = LM_CatchUp;
                        //This freq should make this now silent note to catch-up/resync
                        //with the heard note for the same length it stayed at the
                        //previous freq during the fadeout.
                        float catchupfreq = param.freq * (param.freq / lastfreq);
                        LegatoParams pars{catchupfreq, param.vel, param.portamento,
                                          param.midinote, false};
                        note.legatonote(pars);
                        break;
                    }
                    fade.m -= fade.step;
                    outl[i] *= fade.m;
                    outr[i] *= fade.m;
                }
                break;
            default:
                break;
        }
    } catch (std::bad_alloc &ba) {
        std::cerr << "failed to apply legato: " << ba.what() << std::endl;
    }
}

void SynthNote::setVelocity(float velocity_) {
    legato.setSilent(true); //Let legato.update(...) returns 0.
    LegatoParams pars{legato.getFreq(), velocity_,
               legato.getPortamento(), legato.getMidinote(), true};
    try {
        legatonote(pars);
    } catch (std::bad_alloc &ba) {
        std::cerr << "failed to set velocity to legato note: " << ba.what() << std::endl;
    }
    legato.setDecounter(0); //avoid chopping sound due fade-in
}

}
