/*
  ZynAddSubFX - a software synthesizer

  OutMgr.cpp - Audio Output Manager
  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 "OutMgr.h"
#include <algorithm>
#include <iostream>
#include <cassert>
#include "AudioOut.h"
#include "Engine.h"
#include "EngineMgr.h"
#include "InMgr.h"
#include "WavEngine.h"
#include "../Misc/Master.h"
#include "../Misc/Util.h" //for set_realtime()
using namespace std;

namespace zyn {

OutMgr &OutMgr::getInstance(const SYNTH_T *synth)
{
    static OutMgr instance(synth);
    return instance;
}

OutMgr::OutMgr(const SYNTH_T *synth_)
    :wave(new WavEngine(*synth_)),
      priBuf(new float[4096],
             new float[4096]), priBuffCurrent(priBuf),
      master(NULL), stales(0), synth(*synth_)
{
    assert(synth_);
    currentOut = NULL;

    //init samples
    outr = new float[synth.buffersize];
    outl = new float[synth.buffersize];
    memset(outl, 0, synth.bufferbytes);
    memset(outr, 0, synth.bufferbytes);
}

OutMgr::~OutMgr()
{
    delete wave;
    delete [] priBuf.l;
    delete [] priBuf.r;
    delete [] outr;
    delete [] outl;
}

/* Sequence of a tick
 * 1) Lets remove old/stale samples
 * 2) Apply appliciable midi events
 * 3) Lets see if we need to generate samples
 * 4) Lets generate some
 * 5) Goto 2 if more are needed
 * 6) Lets return those samples to the primary and secondary outputs
 * 7) Lets wait for another tick
 */
const Stereo<float *> OutMgr::tick(unsigned int frameSize)
{
    InMgr &midi = InMgr::getInstance();
    //SysEv->execute();
    removeStaleSmps();
    int i=0;
    while(frameSize > storedSmps()) {
        if(!midi.empty()) {
            midi.flush(i*synth.buffersize, (i+1)*synth.buffersize);
        }
        master->AudioOut(outl, outr);
        addSmps(outl, outr);
        i++;
    }
    stales = frameSize;
    return priBuf;
}

AudioOut *OutMgr::getOut(string name)
{
    return dynamic_cast<AudioOut *>(EngineMgr::getInstance().getEng(name));
}

string OutMgr::getDriver() const
{
    return currentOut->name;
}

bool OutMgr::setSink(string name)
{
    AudioOut *sink = getOut(name);

    if(!sink)
        return false;

    if(currentOut)
        currentOut->setAudioEn(false);

    currentOut = sink;
    currentOut->setAudioEn(true);

    bool success = currentOut->getAudioEn();

    //Keep system in a valid state (aka with a running driver)
    if(!success)
        (currentOut = getOut("NULL"))->setAudioEn(true);

    return success;
}

string OutMgr::getSink() const
{
    if(currentOut)
        return currentOut->name;
    else {
        cerr << "BUG: No current output in OutMgr " << __LINE__ << endl;
        return "ERROR";
    }
    return "ERROR";
}

void OutMgr::setAudioCompressor(bool isEnabled)
{
    currentOut->isOutputCompressionEnabled=isEnabled;
}

bool OutMgr::getAudioCompressor(void)
{
    return currentOut->isOutputCompressionEnabled;
}

void OutMgr::setMaster(Master *master_)
{
    master=master_;
}

void OutMgr::applyOscEventRt(const char *msg)
{
    master->applyOscEvent(msg);
}

//perform a cheap linear interpolation for resampling
//This will result in some distortion at frame boundaries
//returns number of samples produced
static size_t resample(float *dest,
                       const float *src,
                       float s_in,
                       float s_out,
                       size_t elms)
{
    size_t out_elms = elms * s_out / s_in;
    float  r_pos    = 0.0f;
    for(int i = 0; i < (int)out_elms; ++i, r_pos += s_in / s_out)
        dest[i] = interpolate(src, elms, r_pos);

    return out_elms;
}

void OutMgr::addSmps(float *l, float *r)
{
    //allow wave file to syphon off stream
    wave->push(Stereo<float *>(l, r), synth.buffersize);

    const int s_out = currentOut->getSampleRate(),
              s_sys = synth.samplerate;

    if(s_out != s_sys) { //we need to resample
        const size_t steps = resample(priBuffCurrent.l,
                                      l,
                                      s_sys,
                                      s_out,
                                      synth.buffersize);
        resample(priBuffCurrent.r, r, s_sys, s_out, synth.buffersize);

        priBuffCurrent.l += steps;
        priBuffCurrent.r += steps;
    }
    else { //just copy the samples
        memcpy(priBuffCurrent.l, l, synth.bufferbytes);
        memcpy(priBuffCurrent.r, r, synth.bufferbytes);
        priBuffCurrent.l += synth.buffersize;
        priBuffCurrent.r += synth.buffersize;
    }
}

void OutMgr::removeStaleSmps()
{
    if(!stales)
        return;

    const int leftover = storedSmps() - stales;

    assert(leftover > -1);

    //leftover samples [seen at very low latencies]
    if(leftover) {
        memmove(priBuf.l, priBuffCurrent.l - leftover, leftover * sizeof(float));
        memmove(priBuf.r, priBuffCurrent.r - leftover, leftover * sizeof(float));
        priBuffCurrent.l = priBuf.l + leftover;
        priBuffCurrent.r = priBuf.r + leftover;
    }
    else
        priBuffCurrent = priBuf;

    stales = 0;
}

}
