/* 

    TiMidity -- Experimental MIDI to WAVE converter
    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>

    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, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    wave_audio.c

    Functions to output RIFF WAVE format data to a file or stdout.

*/

#ifdef __WIN32__
#    include <stdlib.h>
#    include <io.h>
#else
#    include <unistd.h>
#endif
#include <fcntl.h>
#include <errno.h>

#ifdef __FreeBSD__
#    include <stdio.h>
#endif

#include <string.h>
#include <stdio.h>

#include "gtim.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "readmidi.h"
#include "output.h"
#include "controls.h"
#include "tables.h"

#if !defined(OPEN_MODE)
#    ifdef __WIN32__
#        define OPEN_MODE O_WRONLY | O_CREAT | O_TRUNC | O_BINARY
#    else
#        define OPEN_MODE O_WRONLY | O_CREAT | O_TRUNC
#    endif
#endif

static int open_output (void);	/* 0=success, 1=warning, -1=fatal error */
static void close_output (void);
static void output_data (int32 *buf, uint32 count);
static int driver_output_data (unsigned char *buf, uint32 count);
static void flush_output (void);
static void purge_output (void);
static int output_count (uint32 ct);
static int output_pause (int do_pause);

/* export the playback mode */

#define dpm wave_play_mode

PlayMode dpm = {
    DEFAULT_RATE, PE_16BIT | PE_SIGNED,
    -1,
    {0, 0, 0, 0, 0},
    "RIFF WAVE file", 'w',
    "output.wav",
    open_output,
    close_output,
    output_data,
    driver_output_data,
    flush_output,
    purge_output,
    output_count,
    output_pause
};

/*************************************************************************/

#define WAVE_FORMAT_PCM       0x01
#define UPDATE_HEADER_STEP (128*1024)	/* 128KB */

static unsigned int bytes_output, next_bytes;
static int already_warning_lseek;
static int frame_size = 0;
static int bytes_per_sample;

static const char *orig_RIFFheader =
    "RIFF" "\377\377\377\377" "WAVE" "fmt " "\020\000\000\000" "\001\000"
    /* 22: channels */ "\001\000"
    /* 24: frequency */ "xxxx"
    /* 28: bytes/second */ "xxxx"
    /* 32: bytes/sample */ "\004\000"
    /* 34: bits/sample */ "\020\000"
    "data" "\377\377\377\377";

/* Count the number of bytes output so the header can be fixed when
   closing the file */

/* We only support 16-bit signed and 8-bit unsigned data -- WAVEs have
   to be supported because TiMidity is a "MIDI-to-WAVE converter"...

   uLaw WAVEs might be useful and not too hard to implement. I just
   don't know what should go in the "fmt " block. */

static int
open_output (void)
{
    char    RIFFheader[44];
    int     t;

    if (dpm.encoding & PE_24BIT)
	bytes_per_sample = 3;
    else if (dpm.encoding & PE_16BIT)
	bytes_per_sample = 2;
    else
	bytes_per_sample = 1;

    if (requested_ochannels) {
	if (requested_ochannels == 1)
	    dpm.encoding |= PE_MONO;
	num_ochannels = requested_ochannels;
    }
    else {
	if (dpm.encoding & PE_MONO)
	    num_ochannels = 1;
	else if (dpm.encoding & PE_24BIT)
	    num_ochannels = 2;
	else
	    num_ochannels = 6;
    }

    dpm.encoding &= ~(PE_BYTESWAP | PE_ULAW);

    if (bytes_per_sample > 1)
	dpm.encoding |= PE_SIGNED;
    else
	dpm.encoding &= ~PE_SIGNED;

    frame_size = bytes_per_sample * num_ochannels;


    if (dpm.name && dpm.name[0] == '-' && dpm.name[1] == '\0')
	dpm.fd = 1;		/* data to stdout */
    else {
	/* Open the audio file */
	dpm.fd = open (dpm.name, OPEN_MODE, 0644);
	if (dpm.fd < 0) {
	    ctl->cmsg (CMSG_ERROR, VERB_NORMAL, "%s: %s",
		       dpm.name, strerror (errno));
	    return -1;
	}
    }

    /* Generate a (rather non-standard) RIFF header. We don't know yet
       what the block lengths will be. We'll fix that at close if this
       is a seekable file. */

    memcpy (RIFFheader, orig_RIFFheader, 44);

    RIFFheader[20] = WAVE_FORMAT_PCM;

    RIFFheader[22] = num_ochannels;

    *((int *) (RIFFheader + 24)) = LE_LONG (dpm.rate);

    t = dpm.rate * frame_size;
    *((int *) (RIFFheader + 28)) = LE_LONG (t);

    t = bytes_per_sample;
    RIFFheader[34] = t * 8;
    RIFFheader[32] = t * num_ochannels;

    write (dpm.fd, RIFFheader, 44);

    /* Reset the length counter */
    bytes_output = 0;
    next_bytes = bytes_output + UPDATE_HEADER_STEP;
    already_warning_lseek = 0;

    output_buffer_full = 100;
    voices_ceiling = voices;
    fast_load = 0;

    return 0;
}


static int
update_header (void)
{
    off_t   save_point;
    int     tmp;

    if (already_warning_lseek)
	return 0;

    save_point = lseek (dpm.fd, 0, SEEK_CUR);
    if (save_point == -1 || lseek (dpm.fd, 4, SEEK_SET) == -1) {
	fprintf (stderr, "Warning: file output: %s: Can't make valid header",
		 strerror (errno));
	already_warning_lseek = 1;
	return 0;
    }

    tmp = LE_LONG (bytes_output + 44 - 8);
    if (write (dpm.fd, &tmp, 4) == -1) {
	lseek (dpm.fd, save_point, SEEK_SET);
	return -1;
    }
    lseek (dpm.fd, 40, SEEK_SET);
    tmp = LE_LONG (bytes_output);
    write (dpm.fd, &tmp, 4);

    lseek (dpm.fd, save_point, SEEK_SET);

    return 0;
}


static int
output_count (uint32 ct)
{
    return (int) ct;
}

static int
driver_output_data (unsigned char *buf, uint32 count)
{
    count = (uint32)buf[0];
    return count;
}

static void
output_data (int32 *buf, uint32 count)
{
    int     	n;
    uint32	i;
    unsigned    bytes;
    double	a0 = butterworth_q0[1][0];
    double	a1 = butterworth_q0[1][1];
    double	a2 = butterworth_q0[1][2];
    double	b0 = butterworth_q0[1][3];
    double	b1 = butterworth_q0[1][4];
    double	insamp, outsamp;
    static double xx0 = 0, xx1 = 0, yy0 = 0, yy1 = 0;


    if (dpm.fd == -1)
	return;

    if (num_ochannels == 6) for (i = 0; i < count; i++) {
        insamp = (double)buf[6*i + 5];
	outsamp = a0 * insamp + a1 * xx0 + a2 * xx1 - b0 * yy0 - b1 * yy1;
	xx1 = xx0;
	xx0 = insamp;
	yy1 = yy0;
	yy0 = outsamp;
	buf[6*i + 5] = (int32)outsamp;
    }

    bytes = count * frame_size;
    count *= num_ochannels;

    if (dpm.encoding & PE_24BIT) {
	/* Convert data to signed 24-bit PCM */
	s32tos24 (buf, count);
    }
    else if (dpm.encoding & PE_16BIT) {
	s32tos16l (buf, count);	/* Little-endian data */
    }
    else {
	s32tou8 (buf, count);
    }

    while (((n = write (dpm.fd, buf, bytes)) == -1) && errno == EINTR);
    if (n == -1) {
	fprintf (stderr, "write error: %s", strerror (errno));
	return;
    }

    bytes_output += bytes;

#if UPDATE_HEADER_STEP > 0
    if (bytes_output >= next_bytes) {
	if (update_header () == -1)
	    return;
	next_bytes = bytes_output + UPDATE_HEADER_STEP;
    }
#endif /* UPDATE_HEADER_STEP */

}

static void
close_output (void)
{
    if (dpm.fd != 1 && dpm.fd != -1) {	/* We don't close stdout */
	update_header ();
	close (dpm.fd);
	dpm.fd = -1;
    }
}

/* Dummies */
static void
flush_output (void)
{
}
static void
purge_output (void)
{
}
static int
output_pause (int do_pause)
{
	return do_pause;
}
