/*
   ZynAddSubFX - a software synthesizer

   OssMultiEngine.cpp - Multi channel audio output for Open Sound System
   Copyright (C) 2014 Hans Petter Selasky

  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 <cstring>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/soundcard.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <iostream>
#include <signal.h>

#include "Nio.h"
#include "../Misc/Master.h"
#include "../Misc/Part.h"
#include "../Misc/MiddleWare.h"
#include "../Misc/Util.h"

#include "OssMultiEngine.h"
#include "Compressor.h"

using namespace std;

extern zyncarla::MiddleWare *middleware;

namespace zyncarla {

OssMultiEngine :: OssMultiEngine(const SYNTH_T &synth,
    const oss_devs_t &oss_devs)
    :AudioOut(synth),
    linux_oss_wave_out_dev(oss_devs.linux_wave_out)
{
    /* setup variables */
    name = "OSS-MULTI";
    audioThread = 0;
    handle = -1;
    channels = 0;
    en = false;
    is32bit = false;
    buffersize = 0;

    /* compute worst case buffer size */
    maxbuffersize = NUM_MIDI_PARTS * sizeof(int) * synth.buffersize * 2;
    /* allocate buffer */
    smps.ps32 = new int[maxbuffersize / sizeof(int)];
    memset(smps.ps32, 0, maxbuffersize);

    /* setup compressor */
    unsigned peaksize = NUM_MIDI_PARTS * sizeof(float);
    peaks = new float[peaksize / sizeof(float)];
    memset(peaks, 0, peaksize);
}

OssMultiEngine :: ~OssMultiEngine()
{
    Stop();
    delete [] smps.ps32;
    delete [] peaks;
}

    bool
OssMultiEngine :: openAudio()
{
    int snd_samplerate;
    int snd_fragment;
    int x;

    /* check if already open */
    if (handle != -1)
        return (true);

    const char *device = getenv("DSP_DEVICE");
    if(device == 0)
        device = linux_oss_wave_out_dev;

    /* NOTE: PIPEs and FIFOs can block when opening them */
    handle = open(device, O_WRONLY, O_NONBLOCK);
    if (handle == -1) {
        cerr << "ERROR - I can't open the "
            << device << '.' << endl;
        return (false);
    }
    ioctl(handle, SNDCTL_DSP_RESET, 0);

    /* Figure out the correct format first */
    int snd_format16 = AFMT_S16_NE;

#ifdef AFMT_S32_NE
    int snd_format32 = AFMT_S32_NE;
    if (ioctl(handle, SNDCTL_DSP_SETFMT, &snd_format32) == 0) {
        is32bit = true;
    } else
#endif
    if (ioctl(handle, SNDCTL_DSP_SETFMT, &snd_format16) == 0) {
        is32bit = false;
    } else {
        cerr << "ERROR - Cannot set DSP format for "
            << device << '.' << endl;
        goto error;
    }
    for (x = NUM_MIDI_PARTS * 2; x >= 2; x -= 2) {
        if (ioctl(handle, SNDCTL_DSP_CHANNELS, &x) == 0)
            break;
    }
    if (x == 0) {
        cerr << "ERROR - Cannot set DSP channels for "
            << device << '.' << endl;
        goto error;
    }
    channels = x;

    snd_samplerate = synth.samplerate;

    ioctl(handle, SNDCTL_DSP_SPEED, &snd_samplerate);

    if (snd_samplerate != (int)synth.samplerate) {
        cerr << "ERROR - Cannot set samplerate for "
            << device << ". " << snd_samplerate
            << " != " << synth.samplerate << endl;
        goto error;
    }

    /* compute buffer size for 16-bit samples */
    buffersize = 2 * synth.buffersize * channels;
    if (is32bit)
        buffersize *= 2;

    for (x = 4; x < 20; x++) {
        if ((1 << x) >= buffersize)
            break;
    }

    snd_fragment = 0x20000 | x;		/* 2x buffer */

    ioctl(handle, SNDCTL_DSP_SETFRAGMENT, &snd_fragment);

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&audioThread, &attr, _audioThreadCb, this);

    return (true);

error:
    close(handle);
    handle = -1;
    return (false);
}

    void
OssMultiEngine :: stopAudio()
{
    int fd = handle;

    /* check if already closed */
    if (fd == -1)
        return;
    handle = -1;

    /* close handle first, so that write() exits */
    close(fd);

    pthread_join(audioThread, 0);
    audioThread = 0;
}

    bool
OssMultiEngine :: Start()
{
    return (openAudio());
}

    void
OssMultiEngine :: Stop()
{
    stopAudio();
}

    void
OssMultiEngine :: setAudioEn(bool enable)
{
    if (enable)
        openAudio();
    else
        stopAudio();
}

bool
OssMultiEngine :: getAudioEn() const
{
    return (handle != -1);
}

    void *
OssMultiEngine :: _audioThreadCb(void *arg)
{
    return (static_cast<OssMultiEngine *>(arg))->audioThreadCb();
}

    void *
OssMultiEngine :: audioThreadCb()
{
    /*
     * In case the audio device is a PIPE/FIFO, we need to ignore
     * any PIPE signals:
     */
    signal(SIGPIPE, SIG_IGN);

    set_realtime();

    while(getAudioEn()) {
        int error;
        int x;
        int y;

        /* get next buffer */
        getNext();

        /* extract audio from the "channels / 2" first parts */
        for (x = 0; x != channels; x += 2) {
            Part *part = middleware->spawnMaster()->part[x / 2];

            if (is32bit) {
                for (y = 0; y != synth.buffersize; y++) {
                    float l = part->partoutl[y];
                    float r = part->partoutr[y];
                    stereoCompressor(synth.samplerate, peaks[x/2], l, r);
                    smps.ps32[y * channels + x] = (int)(l * 2147483647.0f);
                    smps.ps32[y * channels + x + 1] = (int)(r * 2147483647.0f);
                }
            } else {
                for (y = 0; y != synth.buffersize; y++) {
                    float l = part->partoutl[y];
                    float r = part->partoutr[y];
                    stereoCompressor(synth.samplerate, peaks[x/2], l, r);
                    smps.ps16[y * channels + x] = (short int)(l * 32767.0f);
                    smps.ps16[y * channels + x + 1] = (short int)(r * 32767.0f);
                }
            }
        }

        /* write audio buffer to DSP device */
        do {
            /* make a copy of handle, in case of OSS audio disable */
            int fd = handle;
            if (fd == -1)
                goto done;
            error = write(fd, smps.ps32, buffersize);
        } while (error == -1 && errno == EINTR);

        if(error == -1)
            goto done;
    }
done:
    pthread_exit(0);
    return (0);
}

}
