/*
 wildmidi.c
 Midi Player using the WildMidi Midi Processing Library

 Copyright (C) Chris Ison  2001-2011
 Copyright (C) Bret Curtis 2013-2016

 This file is part of WildMIDI.

 WildMIDI is free software: you can redistribute and/or modify the player
 under the terms of the GNU General Public License and you can redistribute
 and/or modify the library under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation, either version 3 of
 the licenses, or(at your option) any later version.

 WildMIDI 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 and
 the GNU Lesser General Public License for more details.

 You should have received a copy of the GNU General Public License and the
 GNU Lesser General Public License along with WildMIDI.  If not,  see
 <http://www.gnu.org/licenses/>.

 */

#include "config.h"

#include <limits.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(__DJGPP__)
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "getopt_long.h"
#include <conio.h>
#define getopt dj_getopt /* hack */
#include <unistd.h>
#undef getopt
#define msleep(s) usleep((s)*1000)
#include <io.h>
#ifdef AUDIODRV_DOSSB
#include "dossb.h"
#endif

#elif (defined _WIN32) || (defined __CYGWIN__)
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef __CYGWIN__
#include <unistd.h>
#else
#include <conio.h>
#endif
#include <windows.h>
#include <mmsystem.h>
#define msleep(s) Sleep((s))
#include <io.h>
#include "getopt_long.h"

#elif defined(__OS2__) || defined(__EMX__)
#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_OS2MM
#ifdef __EMX__
#define INCL_KBD
#define INCL_VIO
#endif
#include <os2.h>
#include <os2me.h>
#include <conio.h>
#define msleep(s) DosSleep((s))
#include <fcntl.h>
#include <io.h>
#include "getopt_long.h"
#ifdef __EMX__
#include <sys/types.h> /* for off_t typedef */
int putch (int c) {
	char ch = c;
	VioWrtTTY(&ch, 1, 0);
	return c;
}
int kbhit (void) {
	KBDKEYINFO k;
	if (KbdPeek(&k, 0))
		return 0;
	return (k.fbStatus & KBDTRF_FINAL_CHAR_IN);
}
#endif

#elif defined(WILDMIDI_AMIGA)
extern void amiga_sysinit (void);
extern int amiga_usleep(unsigned long millisec);
#define msleep(s) amiga_usleep((s)*1000)
extern int amiga_getch (unsigned char *ch);
#include <proto/exec.h>
#include <proto/dos.h>
#include "getopt_long.h"
#ifdef AUDIODRV_AHI
#include <devices/ahi.h>
#endif

#else /* unix build */
static int msleep(unsigned long millisec);
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <getopt.h>
#include <time.h>
#ifdef AUDIODRV_ALSA
#  include <alsa/asoundlib.h>
#elif defined AUDIODRV_OSS
#   if defined HAVE_SYS_SOUNDCARD_H
#   include <sys/soundcard.h>
#   elif defined HAVE_MACHINE_SOUNDCARD_H
#   include <machine/soundcard.h>
#   elif defined HAVE_SOUNDCARD_H
#   include <soundcard.h> /* less common, but exists. */
#   endif
#elif defined AUDIODRV_OPENAL
#   include <al.h>
#   include <alc.h>
#endif
#endif /* !_WIN32, !__DJGPP__ (unix build) */

#include "wildmidi_lib.h"
#include "wm_tty.h"
#include "filenames.h"


struct _midi_test {
	unsigned char *data;
	unsigned long int size;
};

/* scale test from 0 to 127
 * test a
 * offset 18-21 (0x12-0x15) - track size
 * offset 25 (0x1A) = bank number
 * offset 28 (0x1D) = patch number
 */
static unsigned char midi_test_c_scale[] = {
	0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, /* 0x00    */
	0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x4d, 0x54, /* 0x08    */
	0x72, 0x6b, 0x00, 0x00, 0x02, 0x63, 0x00, 0xb0, /* 0x10    */
	0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x90, 0x00,	/* 0x18  C */
	0x64, 0x08, 0x80, 0x00, 0x00, 0x08, 0x90, 0x02,	/* 0x20  D */
	0x64, 0x08, 0x80, 0x02, 0x00, 0x08, 0x90, 0x04,	/* 0x28  E */
	0x64, 0x08, 0x80, 0x04, 0x00, 0x08, 0x90, 0x05,	/* 0x30  F */
	0x64, 0x08, 0x80, 0x05, 0x00, 0x08, 0x90, 0x07,	/* 0x38  G */
	0x64, 0x08, 0x80, 0x07, 0x00, 0x08, 0x90, 0x09,	/* 0x40  A */
	0x64, 0x08, 0x80, 0x09, 0x00, 0x08, 0x90, 0x0b,	/* 0x48  B */
	0x64, 0x08, 0x80, 0x0b, 0x00, 0x08, 0x90, 0x0c,	/* 0x50  C */
	0x64, 0x08, 0x80, 0x0c, 0x00, 0x08, 0x90, 0x0e,	/* 0x58  D */
	0x64, 0x08, 0x80, 0x0e, 0x00, 0x08, 0x90, 0x10,	/* 0x60  E */
	0x64, 0x08, 0x80, 0x10, 0x00, 0x08, 0x90, 0x11,	/* 0x68  F */
	0x64, 0x08, 0x80, 0x11, 0x00, 0x08, 0x90, 0x13,	/* 0x70  G */
	0x64, 0x08, 0x80, 0x13, 0x00, 0x08, 0x90, 0x15,	/* 0x78  A */
	0x64, 0x08, 0x80, 0x15, 0x00, 0x08, 0x90, 0x17,	/* 0x80  B */
	0x64, 0x08, 0x80, 0x17, 0x00, 0x08, 0x90, 0x18,	/* 0x88  C */
	0x64, 0x08, 0x80, 0x18, 0x00, 0x08, 0x90, 0x1a,	/* 0x90  D */
	0x64, 0x08, 0x80, 0x1a, 0x00, 0x08, 0x90, 0x1c,	/* 0x98  E */
	0x64, 0x08, 0x80, 0x1c, 0x00, 0x08, 0x90, 0x1d,	/* 0xA0  F */
	0x64, 0x08, 0x80, 0x1d, 0x00, 0x08, 0x90, 0x1f,	/* 0xA8  G */
	0x64, 0x08, 0x80, 0x1f, 0x00, 0x08, 0x90, 0x21,	/* 0xB0  A */
	0x64, 0x08, 0x80, 0x21, 0x00, 0x08, 0x90, 0x23,	/* 0xB8  B */
	0x64, 0x08, 0x80, 0x23, 0x00, 0x08, 0x90, 0x24,	/* 0xC0  C */
	0x64, 0x08, 0x80, 0x24, 0x00, 0x08, 0x90, 0x26,	/* 0xC8  D */
	0x64, 0x08, 0x80, 0x26, 0x00, 0x08, 0x90, 0x28,	/* 0xD0  E */
	0x64, 0x08, 0x80, 0x28, 0x00, 0x08, 0x90, 0x29,	/* 0xD8  F */
	0x64, 0x08, 0x80, 0x29, 0x00, 0x08, 0x90, 0x2b,	/* 0xE0  G */
	0x64, 0x08, 0x80, 0x2b, 0x00, 0x08, 0x90, 0x2d,	/* 0xE8  A */
	0x64, 0x08, 0x80, 0x2d, 0x00, 0x08, 0x90, 0x2f,	/* 0xF0  B */
	0x64, 0x08, 0x80, 0x2f, 0x00, 0x08, 0x90, 0x30,	/* 0xF8  C */
	0x64, 0x08, 0x80, 0x30, 0x00, 0x08, 0x90, 0x32,	/* 0x100 D */
	0x64, 0x08, 0x80, 0x32, 0x00, 0x08, 0x90, 0x34,	/* 0x108 E */
	0x64, 0x08, 0x80, 0x34, 0x00, 0x08, 0x90, 0x35,	/* 0x110 F */
	0x64, 0x08, 0x80, 0x35, 0x00, 0x08, 0x90, 0x37,	/* 0x118 G */
	0x64, 0x08, 0x80, 0x37, 0x00, 0x08, 0x90, 0x39,	/* 0x120 A */
	0x64, 0x08, 0x80, 0x39, 0x00, 0x08, 0x90, 0x3b,	/* 0X128 B */
	0x64, 0x08, 0x80, 0x3b, 0x00, 0x08, 0x90, 0x3c,	/* 0x130 C */
	0x64, 0x08, 0x80, 0x3c, 0x00, 0x08, 0x90, 0x3e,	/* 0x138 D */
	0x64, 0x08, 0x80, 0x3e, 0x00, 0x08, 0x90, 0x40,	/* 0X140 E */
	0x64, 0x08, 0x80, 0x40, 0x00, 0x08, 0x90, 0x41,	/* 0x148 F */
	0x64, 0x08, 0x80, 0x41, 0x00, 0x08, 0x90, 0x43,	/* 0x150 G */
	0x64, 0x08, 0x80, 0x43, 0x00, 0x08, 0x90, 0x45,	/* 0x158 A */
	0x64, 0x08, 0x80, 0x45, 0x00, 0x08, 0x90, 0x47,	/* 0x160 B */
	0x64, 0x08, 0x80, 0x47, 0x00, 0x08, 0x90, 0x48,	/* 0x168 C */
	0x64, 0x08, 0x80, 0x48, 0x00, 0x08, 0x90, 0x4a,	/* 0x170 D */
	0x64, 0x08, 0x80, 0x4a, 0x00, 0x08, 0x90, 0x4c,	/* 0x178 E */
	0x64, 0x08, 0x80, 0x4c, 0x00, 0x08, 0x90, 0x4d,	/* 0x180 F */
	0x64, 0x08, 0x80, 0x4d, 0x00, 0x08, 0x90, 0x4f,	/* 0x188 G */
	0x64, 0x08, 0x80, 0x4f, 0x00, 0x08, 0x90, 0x51,	/* 0x190 A */
	0x64, 0x08, 0x80, 0x51, 0x00, 0x08, 0x90, 0x53,	/* 0x198 B */
	0x64, 0x08, 0x80, 0x53, 0x00, 0x08, 0x90, 0x54,	/* 0x1A0 C */
	0x64, 0x08, 0x80, 0x54, 0x00, 0x08, 0x90, 0x56,	/* 0x1A8 D */
	0x64, 0x08, 0x80, 0x56, 0x00, 0x08, 0x90, 0x58,	/* 0x1B0 E */
	0x64, 0x08, 0x80, 0x58, 0x00, 0x08, 0x90, 0x59,	/* 0x1B8 F */
	0x64, 0x08, 0x80, 0x59, 0x00, 0x08, 0x90, 0x5b,	/* 0x1C0 G */
	0x64, 0x08, 0x80, 0x5b, 0x00, 0x08, 0x90, 0x5d,	/* 0x1C8 A */
	0x64, 0x08, 0x80, 0x5d, 0x00, 0x08, 0x90, 0x5f,	/* 0x1D0 B */
	0x64, 0x08, 0x80, 0x5f, 0x00, 0x08, 0x90, 0x60,	/* 0x1D8 C */
	0x64, 0x08, 0x80, 0x60, 0x00, 0x08, 0x90, 0x62,	/* 0x1E0 D */
	0x64, 0x08, 0x80, 0x62, 0x00, 0x08, 0x90, 0x64,	/* 0x1E8 E */
	0x64, 0x08, 0x80, 0x64, 0x00, 0x08, 0x90, 0x65,	/* 0x1F0 F */
	0x64, 0x08, 0x80, 0x65, 0x00, 0x08, 0x90, 0x67,	/* 0x1F8 G */
	0x64, 0x08, 0x80, 0x67, 0x00, 0x08, 0x90, 0x69,	/* 0x200 A */
	0x64, 0x08, 0x80, 0x69, 0x00, 0x08, 0x90, 0x6b,	/* 0x208 B */
	0x64, 0x08, 0x80, 0x6b, 0x00, 0x08, 0x90, 0x6c,	/* 0x210 C */
	0x64, 0x08, 0x80, 0x6c, 0x00, 0x08, 0x90, 0x6e,	/* 0x218 D */
	0x64, 0x08, 0x80, 0x6e, 0x00, 0x08, 0x90, 0x70,	/* 0x220 E */
	0x64, 0x08, 0x80, 0x70, 0x00, 0x08, 0x90, 0x71,	/* 0x228 F */
	0x64, 0x08, 0x80, 0x71, 0x00, 0x08, 0x90, 0x73,	/* 0x230 G */
	0x64, 0x08, 0x80, 0x73, 0x00, 0x08, 0x90, 0x75,	/* 0x238 A */
	0x64, 0x08, 0x80, 0x75, 0x00, 0x08, 0x90, 0x77,	/* 0x240 B */
	0x64, 0x08, 0x80, 0x77, 0x00, 0x08, 0x90, 0x78,	/* 0x248 C */
	0x64, 0x08, 0x80, 0x78, 0x00, 0x08, 0x90, 0x7a,	/* 0x250 D */
	0x64, 0x08, 0x80, 0x7a, 0x00, 0x08, 0x90, 0x7c,	/* 0x258 E */
	0x64, 0x08, 0x80, 0x7c, 0x00, 0x08, 0x90, 0x7d,	/* 0x260 F */
	0x64, 0x08, 0x80, 0x7d, 0x00, 0x08, 0x90, 0x7f,	/* 0x268 G */
	0x64, 0x08, 0x80, 0x7f, 0x00, 0x08, 0xff, 0x2f, /* 0x270   */
	0x00 /* 0x278   */
};

static struct _midi_test midi_test[] = {
	{ midi_test_c_scale, 663 },
	{ NULL, 0 }
};

static int midi_test_max = 1;

/*
 ==============================
 Audio Output Functions

 We have two 'drivers': first is the wav file writer which is
 always available. the second, if it is really compiled in,
 is the system audio output driver. only _one of the two_ can
 be active, not both.
 ==============================
 */

static unsigned int rate = 32072;

static int (*send_output)(char * output_data, int output_size);
static void (*close_output)(void);
static void (*pause_output)(void);
static void (*resume_output)(void);

#define wmidi_geterrno() errno /* generic case */
#if defined(_WIN32)
static int audio_fd = -1;
#define WM_IS_BADF(_fd) ((_fd)<0)
#define WM_BADF -1
static inline int wmidi_open_write (const char *path) {
	return _open(path, (O_RDWR | O_CREAT | O_TRUNC | O_BINARY), 0664);
}
static inline void wmidi_close (int fd) {
	_close(fd);
}
static inline long wmidi_seekset (int fd, long ofs) {
	return _lseek(fd, ofs, SEEK_SET);
}
static inline int wmidi_write (int fd, const void *buf, size_t size) {
	return _write(fd, buf, size);
}

#elif defined(__DJGPP__)
static int audio_fd = -1;
#define WM_IS_BADF(_fd) ((_fd)<0)
#define WM_BADF -1
static inline int wmidi_open_write (const char *path) {
	return open(path, (O_RDWR | O_CREAT | O_TRUNC | O_BINARY), 0664);
}
static inline void wmidi_close (int fd) {
	close(fd);
}
static inline off_t wmidi_seekset (int fd, off_t ofs) {
	return lseek(fd, ofs, SEEK_SET);
}
static inline int wmidi_write (int fd, const void *buf, size_t size) {
	return write(fd, buf, size);
}

#elif defined(__OS2__) || defined(__EMX__)
static int audio_fd = -1;
#define WM_IS_BADF(_fd) ((_fd)<0)
#define WM_BADF -1
static inline int wmidi_open_write (const char *path) {
	return open(path, (O_RDWR | O_CREAT | O_TRUNC | O_BINARY), 0664);
}
static inline void wmidi_close (int fd) {
	close(fd);
}
static inline off_t wmidi_seekset (int fd, off_t ofs) {
	return lseek(fd, ofs, SEEK_SET);
}
static inline int wmidi_write (int fd, const void *buf, size_t size) {
	return write(fd, buf, size);
}

#elif defined(WILDMIDI_AMIGA)
static BPTR audio_fd = 0;
#define WM_IS_BADF(_fd) ((_fd)==0)
#define WM_BADF 0
#undef wmidi_geterrno
static int wmidi_geterrno (void) {
	switch (IoErr()) {
	case ERROR_OBJECT_NOT_FOUND: return ENOENT;
	case ERROR_DISK_FULL: return ENOSPC;
	}
	return EIO; /* better ?? */
}
static inline BPTR wmidi_open_write (const char *path) {
	return Open((const STRPTR) path, MODE_NEWFILE);
}
static inline LONG wmidi_close (BPTR fd) {
	return Close(fd);
}
static inline LONG wmidi_seekset (BPTR fd, LONG ofs) {
	return Seek(fd, ofs, OFFSET_BEGINNING);
}
static LONG wmidi_write (BPTR fd, /*const*/ void *buf, LONG size) {
	LONG written = 0, result;
	unsigned char *p = (unsigned char *)buf;
	while (written < size) {
		result = Write(fd, p + written, size - written);
		if (result < 0) return result;
		written += result;
	}
	return written;
}

#else /* common posix case */
static int audio_fd = -1;
#define WM_IS_BADF(_fd) ((_fd)<0)
#define WM_BADF -1
static inline int wmidi_open_write (const char *path) {
	return open(path, (O_RDWR | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH));
}
static inline int wmidi_close (int fd) {
	return close(fd);
}
static inline off_t wmidi_seekset (int fd, off_t ofs) {
	return lseek(fd, ofs, SEEK_SET);
}
static inline ssize_t wmidi_write (int fd, const void *buf, size_t size) {
	return write(fd, buf, size);
}
#endif

static void pause_output_nop(void) {
}
static void resume_output_nop(void) {
}

/*
 Wav Output Functions
 */

static char wav_file[1024];
#if (INT_MAX == 2147483647)
static unsigned int wav_size;
#else
static unsigned long int wav_size;
#endif

static int write_wav_output(char * output_data, int output_size);
static void close_wav_output(void);

static int open_wav_output(void) {

	unsigned char wav_hdr[] = {
		0x52, 0x49, 0x46, 0x46, /* "RIFF"  */
		0x00, 0x00, 0x00, 0x00, /* riffsize: pcm size + 36 (filled when closing.) */
		0x57, 0x41, 0x56, 0x45, /* "WAVE"  */
		0x66, 0x6D, 0x74, 0x20, /* "fmt "  */
		0x10, 0x00, 0x00, 0x00, /* length of this RIFF block: 16  */
		0x01, 0x00,		/* wave format == 1 (WAVE_FORMAT_PCM)  */
		0x02, 0x00,		/* channels == 2  */
		0x00, 0x00, 0x00, 0x00, /* sample rate (filled below)  */
		0x00, 0x00, 0x00, 0x00, /* bytes_per_sec: rate * channels * format bytes  */
		0x04, 0x00,		/* block alignment: channels * format bytes == 4  */
		0x10, 0x00,		/* format bits == 16  */
		0x64, 0x61, 0x74, 0x61, /* "data"  */
		0x00, 0x00, 0x00, 0x00	/* datasize: the pcm size (filled when closing.)  */
	};

	if (wav_file[0] == '\0')
		return (-1);

	audio_fd = wmidi_open_write(wav_file);
	if (WM_IS_BADF(audio_fd)) {
		fprintf(stderr, "Error: unable to open file for writing (%s)\r\n", strerror(wmidi_geterrno()));
		return (-1);
	} else {
		unsigned long int bytes_per_sec;

		wav_hdr[24] = (rate) & 0xFF;
		wav_hdr[25] = (rate >> 8) & 0xFF;

		bytes_per_sec = rate * 4;
		wav_hdr[28] = (bytes_per_sec) & 0xFF;
		wav_hdr[29] = (bytes_per_sec >> 8) & 0xFF;
		wav_hdr[30] = (bytes_per_sec >> 16) & 0xFF;
		wav_hdr[31] = (bytes_per_sec >> 24) & 0xFF;
	}

	if (wmidi_write(audio_fd, &wav_hdr, 44) < 0) {
		fprintf(stderr, "ERROR: failed writing wav header (%s)\r\n", strerror(wmidi_geterrno()));
		wmidi_close(audio_fd);
		audio_fd = WM_BADF;
		return (-1);
	}

	wav_size = 0;
	send_output = write_wav_output;
	close_output = close_wav_output;
	pause_output = pause_output_nop;
	resume_output = resume_output_nop;
	return (0);
}

static int write_wav_output(char * output_data, int output_size) {
/* the library specifically outputs LE data - no need swapping. */
	if (wmidi_write(audio_fd, output_data, output_size) < 0) {
		fprintf(stderr, "\nERROR: failed writing wav (%s)\r\n", strerror(wmidi_geterrno()));
		wmidi_close(audio_fd);
		audio_fd = WM_BADF;
		return (-1);
	}

	wav_size += output_size;
	return (0);
}

static void close_wav_output(void) {
	unsigned char wav_count[4];
	if (WM_IS_BADF(audio_fd))
		return;

	printf("Finishing and closing wav output\r");
	wav_count[0] = (wav_size) & 0xFF;
	wav_count[1] = (wav_size >> 8) & 0xFF;
	wav_count[2] = (wav_size >> 16) & 0xFF;
	wav_count[3] = (wav_size >> 24) & 0xFF;
	wmidi_seekset(audio_fd, 40);
	if (wmidi_write(audio_fd, &wav_count, 4) < 0) {
		fprintf(stderr, "\nERROR: failed writing wav (%s)\r\n", strerror(wmidi_geterrno()));
		goto end;
	}

	wav_size += 36;
	wav_count[0] = (wav_size) & 0xFF;
	wav_count[1] = (wav_size >> 8) & 0xFF;
	wav_count[2] = (wav_size >> 16) & 0xFF;
	wav_count[3] = (wav_size >> 24) & 0xFF;
	wmidi_seekset(audio_fd, 4);
	if (wmidi_write(audio_fd, &wav_count, 4) < 0) {
		fprintf(stderr, "\nERROR: failed writing wav (%s)\r\n", strerror(wmidi_geterrno()));
		goto end;
	}

end:	printf("\n");
	if (!WM_IS_BADF(audio_fd))
		wmidi_close(audio_fd);
	audio_fd = WM_BADF;
}

#if (defined _WIN32) || (defined __CYGWIN__)

static HWAVEOUT hWaveOut = NULL;
static CRITICAL_SECTION waveCriticalSection;

#define open_audio_output open_mm_output
static int write_mm_output (char * output_data, int output_size);
static void close_mm_output (void);

static WAVEHDR *mm_blocks = NULL;
#define MM_BLOCK_SIZE 16384
#define MM_BLOCK_COUNT 3

static DWORD mm_free_blocks = MM_BLOCK_COUNT;
static DWORD mm_current_block = 0;

#if defined(_MSC_VER) && (_MSC_VER < 1300)
typedef DWORD DWORD_PTR;
#endif

static void CALLBACK mmOutProc (HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
	/* unused params */
	(void)hWaveOut;
	(void)dwParam1;
	(void)dwParam2;

	if(uMsg != WOM_DONE)
		return;
	/* increment mm_free_blocks */
	EnterCriticalSection(&waveCriticalSection);
	(*(DWORD *)dwInstance)++;
	LeaveCriticalSection(&waveCriticalSection);
}

static int
open_mm_output (void) {
	WAVEFORMATEX wfx;
	char *mm_buffer;
	int i;

	InitializeCriticalSection(&waveCriticalSection);

	if((mm_buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ((MM_BLOCK_SIZE + sizeof(WAVEHDR)) * MM_BLOCK_COUNT))) == NULL) {
		fprintf(stderr, "Memory allocation error\r\n");
		return -1;
	}

	mm_blocks = (WAVEHDR*)mm_buffer;
	mm_buffer += sizeof(WAVEHDR) * MM_BLOCK_COUNT;

	for(i = 0; i < MM_BLOCK_COUNT; i++) {
		mm_blocks[i].dwBufferLength = MM_BLOCK_SIZE;
		mm_blocks[i].lpData = mm_buffer;
		mm_buffer += MM_BLOCK_SIZE;
	}

	wfx.nSamplesPerSec = rate;
	wfx.wBitsPerSample = 16;
	wfx.nChannels = 2;
	wfx.cbSize = 0;
	wfx.wFormatTag = WAVE_FORMAT_PCM;
	wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
	wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;

	if(waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, (DWORD_PTR)mmOutProc, (DWORD_PTR)&mm_free_blocks, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
		fprintf(stderr, "unable to open WAVE_MAPPER device\r\n");
		HeapFree(GetProcessHeap(), 0, mm_blocks);
		hWaveOut = NULL;
		mm_blocks = NULL;
		return -1;
	}

	send_output = write_mm_output;
	close_output = close_mm_output;
	pause_output = pause_output_nop;
	resume_output = resume_output_nop;
	return (0);
}

static int
write_mm_output (char * output_data, int output_size) {
	WAVEHDR* current;
	int free_size = 0;
	int data_read = 0;
	current = &mm_blocks[mm_current_block];

	while (output_size) {
		if(current->dwFlags & WHDR_PREPARED)
			waveOutUnprepareHeader(hWaveOut, current, sizeof(WAVEHDR));
		free_size = MM_BLOCK_SIZE - current->dwUser;
		if (free_size > output_size)
			free_size = output_size;

		memcpy(current->lpData + current->dwUser, &output_data[data_read], free_size);
		current->dwUser += free_size;
		output_size -= free_size;
		data_read += free_size;

		if (current->dwUser < MM_BLOCK_SIZE) {
			return (0);
		}

		current->dwBufferLength = MM_BLOCK_SIZE;
		waveOutPrepareHeader(hWaveOut, current, sizeof(WAVEHDR));
		waveOutWrite(hWaveOut, current, sizeof(WAVEHDR));
		EnterCriticalSection(&waveCriticalSection);
		mm_free_blocks--;
		LeaveCriticalSection(&waveCriticalSection);
		while(!mm_free_blocks)
			Sleep(10);
		mm_current_block++;
		mm_current_block %= MM_BLOCK_COUNT;
		current = &mm_blocks[mm_current_block];
		current->dwUser = 0;
	}
	return (0);
}

static void
close_mm_output (void) {
	int i;

	if (!hWaveOut) return;

	printf("Shutting down sound output\r\n");
	for (i = 0; i < MM_BLOCK_COUNT; i++) {
		while (waveOutUnprepareHeader(hWaveOut, &mm_blocks[i], sizeof(WAVEHDR))
				== WAVERR_STILLPLAYING) {
			Sleep(10);
		}
	}

	waveOutClose (hWaveOut);
	HeapFree(GetProcessHeap(), 0, mm_blocks);
	hWaveOut = NULL;
	mm_blocks = NULL;
}

#elif (defined(__OS2__) || defined(__EMX__)) && defined(AUDIODRV_OS2DART)
/* based on Dart code originally written by Kevin Langman for XMP */

#define open_audio_output open_dart_output
static int write_dart_output (char *output_data, int output_size);
static void close_dart_output (void);

#define BUFFERCOUNT 4

static MCI_MIX_BUFFER MixBuffers[BUFFERCOUNT];
static MCI_MIXSETUP_PARMS MixSetupParms;
static MCI_BUFFER_PARMS BufferParms;
static MCI_GENERIC_PARMS GenericParms;

static ULONG DeviceID = 0;
static ULONG bsize = 16;
static short next = 2;
static short ready = 1;

static HMTX dart_mutex;

/* Buffer update thread (created and called by DART) */
static LONG APIENTRY OS2_Dart_UpdateBuffers
	(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags) {

	if ((ulFlags == MIX_WRITE_COMPLETE) ||
		((ulFlags == (MIX_WRITE_COMPLETE | MIX_STREAM_ERROR)) &&
		 (ulStatus == ERROR_DEVICE_UNDERRUN))) {
		DosRequestMutexSem(dart_mutex, SEM_INDEFINITE_WAIT);
		ready++;
		DosReleaseMutexSem(dart_mutex);
	}
	return (TRUE);
}

static int
open_dart_output(void) {
	int i;
	MCI_AMP_OPEN_PARMS AmpOpenParms;

	if (DosCreateMutexSem(NULL, &dart_mutex, 0, 0) != NO_ERROR) {
		fprintf(stderr, "Failed creating a MutexSem.\r\n");
		return (-1);
	}

	/* compute a size for circa 1/4" of playback. */
	bsize = rate >> 2;
	bsize <<= 1; /* stereo */
	bsize <<= 1; /* 16 bit */
	for (i = 15; i >= 12; i--) {
		if (bsize & (1 << i))
			break;
	}
	bsize = (1 << i);
	/* make sure buffer is not greater than 64 Kb: DART can't handle it. */
	if (bsize > 65536)
		bsize = 65536;

	MixBuffers[0].pBuffer = NULL; /* marker */
	memset(&GenericParms, 0, sizeof(MCI_GENERIC_PARMS));

	/* open AMP device */
	memset(&AmpOpenParms, 0, sizeof(MCI_AMP_OPEN_PARMS));
	AmpOpenParms.usDeviceID = 0;

	AmpOpenParms.pszDeviceType =
		(PSZ) MAKEULONG(MCI_DEVTYPE_AUDIO_AMPMIX, 0); /* 0: default waveaudio device */

	if(mciSendCommand(0, MCI_OPEN, MCI_WAIT|MCI_OPEN_TYPE_ID|MCI_OPEN_SHAREABLE,
			  (PVOID) &AmpOpenParms, 0) != MCIERR_SUCCESS) {
		fprintf(stderr, "Failed opening DART audio device\r\n");
		return (-1);
	}

	DeviceID = AmpOpenParms.usDeviceID;

	/* setup playback parameters */
	memset(&MixSetupParms, 0, sizeof(MCI_MIXSETUP_PARMS));

	MixSetupParms.ulBitsPerSample = 16;
	MixSetupParms.ulFormatTag = MCI_WAVE_FORMAT_PCM;
	MixSetupParms.ulSamplesPerSec = rate;
	MixSetupParms.ulChannels = 2;
	MixSetupParms.ulFormatMode = MCI_PLAY;
	MixSetupParms.ulDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO;
	MixSetupParms.pmixEvent = OS2_Dart_UpdateBuffers;

	if (mciSendCommand(DeviceID, MCI_MIXSETUP,
			   MCI_WAIT | MCI_MIXSETUP_INIT,
			   (PVOID) & MixSetupParms, 0) != MCIERR_SUCCESS) {

		mciSendCommand(DeviceID, MCI_CLOSE, MCI_WAIT,
			       (PVOID) & GenericParms, 0);
		fprintf(stderr, "Failed DART mixer setup\r\n");
		return (-1);
	}

	/*bsize = MixSetupParms.ulBufferSize;*/
	/*printf("Dart Buffer Size = %lu\n", bsize);*/

	BufferParms.ulNumBuffers = BUFFERCOUNT;
	BufferParms.ulBufferSize = bsize;
	BufferParms.pBufList = MixBuffers;

	if (mciSendCommand(DeviceID, MCI_BUFFER,
			   MCI_WAIT | MCI_ALLOCATE_MEMORY,
			   (PVOID) & BufferParms, 0) != MCIERR_SUCCESS) {
		fprintf(stderr, "DART Memory allocation error\r\n");
		mciSendCommand(DeviceID, MCI_CLOSE, MCI_WAIT,
			       (PVOID) & GenericParms, 0);
		return (-1);
	}

	for (i = 0; i < BUFFERCOUNT; i++) {
		MixBuffers[i].ulBufferLength = bsize;
	}

	/* Start Playback */
	memset(MixBuffers[0].pBuffer, /*32767 */ 0, bsize);
	memset(MixBuffers[1].pBuffer, /*32767 */ 0, bsize);
	MixSetupParms.pmixWrite(MixSetupParms.ulMixHandle, MixBuffers, 2);

	send_output = write_dart_output;
	close_output = close_dart_output;
	pause_output = pause_output_nop;
	resume_output = resume_output_nop;

	return (0);
}

static int
write_dart_output (char *output_data, int output_size) {
	static int idx = 0;

	if (idx + output_size > bsize) {
		do {
			DosRequestMutexSem(dart_mutex, SEM_INDEFINITE_WAIT);
			if (ready != 0) {
				DosReleaseMutexSem(dart_mutex);
				break;
			}
			DosReleaseMutexSem(dart_mutex);
			DosSleep(20);
		} while (TRUE);

		MixBuffers[next].ulBufferLength = idx;
		MixSetupParms.pmixWrite(MixSetupParms.ulMixHandle, &(MixBuffers[next]), 1);
		ready--;
		next++;
		idx = 0;
		if (next == BUFFERCOUNT) {
			next = 0;
		}
	}
	memcpy(&((char *)MixBuffers[next].pBuffer)[idx], output_data, output_size);
	idx += output_size;
	return (0);
}

static void
close_dart_output (void) {
	printf("Shutting down sound output\r\n");
	if (MixBuffers[0].pBuffer) {
		mciSendCommand(DeviceID, MCI_BUFFER,
			       MCI_WAIT | MCI_DEALLOCATE_MEMORY, &BufferParms, 0);
		MixBuffers[0].pBuffer = NULL;
	}
	if (DeviceID) {
		mciSendCommand(DeviceID, MCI_CLOSE, MCI_WAIT,
			       (PVOID) &GenericParms, 0);
		DeviceID = 0;
	}
}

#elif defined(__DJGPP__) && defined(AUDIODRV_DOSSB)
/* SoundBlaster/Pro/16/AWE32 driver for DOS -- adapted from
 * libMikMod,  written by Andrew Zabolotny <bit@eltech.ru>,
 * further fixes by O.Sezer <sezero@users.sourceforge.net>.
 * Timer callback functionality replaced by a push mechanism
 * to keep the wildmidi player changes to a minimum, for now.
 */

/* The last buffer byte filled with sound */
static unsigned int buff_tail = 0;

static int write_sb_output(char *data, unsigned int siz) {
	unsigned int dma_size, dma_pos;
	unsigned int cnt;

	sb_query_dma(&dma_size, &dma_pos);
	/* There isn't much sense in filling less than 256 bytes */
	dma_pos &= ~255;

	/* If nothing to mix, quit */
	if (buff_tail == dma_pos)
		return 0;

	/* If DMA pointer still didn't wrapped around ... */
	if (dma_pos > buff_tail) {
		if ((cnt = dma_pos - buff_tail) > siz)
			cnt = siz;
		memcpy(sb.dma_buff->linear + buff_tail, data, cnt);
		buff_tail += cnt;
		/* If we arrived right to the DMA buffer end, jump to the beginning */
		if (buff_tail >= dma_size)
			buff_tail = 0;
	} else {
		/* If wrapped around, fill first to the end of buffer */
		if ((cnt = dma_size - buff_tail) > siz)
			cnt = siz;
		memcpy(sb.dma_buff->linear + buff_tail, data, cnt);
		buff_tail += cnt;
		siz -= cnt;
		if (!siz) return cnt;

		/* Now fill from buffer beginning to current DMA pointer */
		if (dma_pos > siz) dma_pos = siz;
		data += cnt;
		cnt += dma_pos;

		memcpy(sb.dma_buff->linear, data, dma_pos);
		buff_tail = dma_pos;
	}
	return cnt;
}

static int write_sb_s16stereo(char *data, int siz) {
/* libWildMidi sint16 stereo -> SB16 sint16 stereo */
	int i;
	while (1) {
		i = write_sb_output(data, siz);
		if ((siz -= i) <= 0) return 0;
		data += i;
		/*usleep(100);*/
	}
}

static int write_sb_u8stereo(char *data, int siz) {
/* libWildMidi sint16 stereo -> SB uint8 stereo */
	signed short *src = (signed short *) data;
	unsigned char *dst = (unsigned char *) data;
	int i = (siz /= 2);
	for (; i >= 0; --i) {
		*dst++ = (*src++ >> 8) + 128;
	}
	while (1) {
		i = write_sb_output(data, siz);
		if ((siz -= i) <= 0) return 0;
		data += i;
		/*usleep(100);*/
	}
}

static int write_sb_u8mono(char *data, int siz) {
/* libWildMidi sint16 stereo -> SB uint8 mono */
	signed short *src = (signed short *) data;
	unsigned char *dst = (unsigned char *) data;
	int i = (siz /= 4); int val;
	for (; i >= 0; --i) {
	/* do a cheap (left+right)/2 */
		val  = *src++;
		val += *src++;
		*dst++ = (val >> 9) + 128;
	}
	while (1) {
		i = write_sb_output(data, siz);
		if ((siz -= i) <= 0) return 0;
		data += i;
		/*usleep(100);*/
	}
}

static void sb_silence_s16(void) {
	memset(sb.dma_buff->linear, 0, sb.dma_buff->size);
}

static void sb_silence_u8(void) {
	memset(sb.dma_buff->linear, 0x80, sb.dma_buff->size);
}

static void close_sb_output(void)
{
	sb.timer_callback = NULL;
	sb_output(FALSE);
	sb_stop_dma();
	sb_close();
}

#define open_audio_output open_sb_output
static int open_sb_output(void)
{
	if (!sb_open()) {
		fprintf(stderr, "Sound Blaster initialization failed.\n");
		return -1;
	}

	if (rate < 4000) rate = 4000;
	if (sb.caps & SBMODE_STEREO) {
		if (rate > sb.maxfreq_stereo)
			rate = sb.maxfreq_stereo;
	} else {
		if (rate > sb.maxfreq_mono)
			rate = sb.maxfreq_mono;
	}

	/* Enable speaker output */
	sb_output(TRUE);

	/* Set our routine to be called during SB IRQs */
	buff_tail = 0;
	sb.timer_callback = NULL;/* see above  */

	/* Start cyclic DMA transfer */
	if (!sb_start_dma(((sb.caps & SBMODE_16BITS) ? SBMODE_16BITS | SBMODE_SIGNED : 0) |
							(sb.caps & SBMODE_STEREO), rate)) {
		sb_output(FALSE);
		sb_close();
		fprintf(stderr, "Sound Blaster: DMA start failed.\n");
		return -1;
	}

	if (sb.caps & SBMODE_16BITS) { /* can do stereo, too */
		send_output = write_sb_s16stereo;
		pause_output = sb_silence_s16;
		resume_output = resume_output_nop;
		printf("Sound Blaster 16 or compatible (16 bit, stereo, %u Hz)\n", rate);
	} else if (sb.caps & SBMODE_STEREO) {
		send_output = write_sb_u8stereo;
		pause_output = sb_silence_u8;
		resume_output = resume_output_nop;
		printf("Sound Blaster Pro or compatible (8 bit, stereo, %u Hz)\n", rate);
	} else {
		send_output = write_sb_u8mono;
		pause_output = sb_silence_u8;
		resume_output = resume_output_nop;
		printf("Sound Blaster %c or compatible (8 bit, mono, %u Hz)\n",
						(sb.dspver < SBVER_20)? '1' : '2', rate);
	}
	close_output = close_sb_output;

	return 0;
}

#elif defined(WILDMIDI_AMIGA) && defined(AUDIODRV_AHI)

/* Driver for output to native Amiga AHI device:
 * Written by Szilárd Biró <col.lawrence@gmail.com>, loosely based
 * on an old AOS4 version by Fredrik Wikstrom <fredrik@a500.org>
 */

#define BUFFERSIZE (4 << 10)

static struct MsgPort *AHImp = NULL;
static struct AHIRequest *AHIReq[2] = { NULL, NULL };
static int active = 0;
static char *AHIBuf[2] = { NULL, NULL };

#define open_audio_output open_ahi_output
static int write_ahi_output(char * output_data, int output_size);
static void close_ahi_output(void);

static int open_ahi_output(void) {
	AHImp = CreateMsgPort();
	if (AHImp) {
		AHIReq[0] = (struct AHIRequest *)CreateIORequest(AHImp, sizeof(struct AHIRequest));
		if (AHIReq[0]) {
			AHIReq[0]->ahir_Version = 4;
			AHIReq[1] = AllocVec(sizeof(struct AHIRequest), MEMF_PUBLIC);
			if (AHIReq[1]) {
				if (!OpenDevice(AHINAME, AHI_DEFAULT_UNIT, (struct IORequest *)AHIReq[0], 0)) {
					/*AHIReq[0]->ahir_Std.io_Message.mn_Node.ln_Pri = 0;*/
					AHIReq[0]->ahir_Std.io_Command = CMD_WRITE;
					AHIReq[0]->ahir_Std.io_Data = NULL;
					AHIReq[0]->ahir_Std.io_Offset = 0;
					AHIReq[0]->ahir_Frequency = rate;
					AHIReq[0]->ahir_Type = AHIST_S16S;/* 16 bit stereo */
					AHIReq[0]->ahir_Volume = 0x10000;
					AHIReq[0]->ahir_Position = 0x8000;
					CopyMem(AHIReq[0], AHIReq[1], sizeof(struct AHIRequest));

					AHIBuf[0] = AllocVec(BUFFERSIZE, MEMF_PUBLIC | MEMF_CLEAR);
					if (AHIBuf[0]) {
						AHIBuf[1] = AllocVec(BUFFERSIZE, MEMF_PUBLIC | MEMF_CLEAR);
						if (AHIBuf[1]) {
							send_output = write_ahi_output;
							close_output = close_ahi_output;
							pause_output = pause_output_nop;
							resume_output = resume_output_nop;
							return (0);
						}
					}
				}
			}
		}
	}

	close_ahi_output();
	fprintf(stderr, "ERROR: Unable to open AHI output\r\n");
	return (-1);
}

static int write_ahi_output(char * output_data, int output_size) {
	int chunk;
	while (output_size > 0) {
		if (AHIReq[active]->ahir_Std.io_Data) {
			WaitIO((struct IORequest *) AHIReq[active]);
		}
		chunk = (output_size < BUFFERSIZE)? output_size : BUFFERSIZE;
		memcpy(AHIBuf[active], output_data, chunk);
		output_size -= chunk;
		output_data += chunk;

		AHIReq[active]->ahir_Std.io_Data = AHIBuf[active];
		AHIReq[active]->ahir_Std.io_Length = chunk;
		AHIReq[active]->ahir_Link = !CheckIO((struct IORequest *) AHIReq[active ^ 1]) ? AHIReq[active ^ 1] : NULL;
		SendIO((struct IORequest *)AHIReq[active]);
		active ^= 1;
	}
	return (0);
}

static void close_ahi_output(void) {
	if (AHIReq[1]) {
		AHIReq[0]->ahir_Link = NULL; /* in case we are linked to req[0] */
		if (!CheckIO((struct IORequest *) AHIReq[1])) {
			AbortIO((struct IORequest *) AHIReq[1]);
			WaitIO((struct IORequest *) AHIReq[1]);
		}
		FreeVec(AHIReq[1]);
		AHIReq[1] = NULL;
	}
	if (AHIReq[0]) {
		if (!CheckIO((struct IORequest *) AHIReq[0])) {
			AbortIO((struct IORequest *) AHIReq[0]);
			WaitIO((struct IORequest *) AHIReq[0]);
		}
		if (AHIReq[0]->ahir_Std.io_Device) {
			CloseDevice((struct IORequest *) AHIReq[0]);
			AHIReq[0]->ahir_Std.io_Device = NULL;
		}
		DeleteIORequest((struct IORequest *) AHIReq[0]);
		AHIReq[0] = NULL;
	}
	if (AHImp) {
		DeleteMsgPort(AHImp);
		AHImp = NULL;
	}
	if (AHIBuf[0]) {
		FreeVec(AHIBuf[0]);
		AHIBuf[0] = NULL;
	}
	if (AHIBuf[1]) {
		FreeVec(AHIBuf[1]);
		AHIBuf[1] = NULL;
	}
}

#else
#ifdef AUDIODRV_ALSA

static int alsa_first_time = 1;
static snd_pcm_t *pcm = NULL;
static char pcmname[64];

#define open_audio_output open_alsa_output
static int write_alsa_output(char * output_data, int output_size);
static void close_alsa_output(void);

static int open_alsa_output(void) {
	snd_pcm_hw_params_t *hw;
	snd_pcm_sw_params_t *sw;
	int err;
	unsigned int alsa_buffer_time;
	unsigned int alsa_period_time;
	unsigned int r;

	if (!pcmname[0]) {
		strcpy(pcmname, "default");
	}

	if ((err = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
		fprintf(stderr, "Error: audio open error: %s\r\n", snd_strerror(err));
		return -1;
	}

	snd_pcm_hw_params_alloca(&hw);

	if ((err = snd_pcm_hw_params_any(pcm, hw)) < 0) {
		fprintf(stderr, "ERROR: No configuration available for playback: %s\r\n",
				snd_strerror(err));
		goto fail;
	}

	if ((err = snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
		fprintf(stderr, "Cannot set access mode: %s.\r\n", snd_strerror(err));
		goto fail;
	}

	/* the library specifically outputs LE data. */
	if (snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S16_LE) < 0) {
		fprintf(stderr, "ALSA does not support 16bit signed audio for your soundcard\r\n");
		goto fail;
	}

	if (snd_pcm_hw_params_set_channels(pcm, hw, 2) < 0) {
		fprintf(stderr, "ALSA does not support stereo for your soundcard\r\n");
		goto fail;
	}

	r = rate;
	if (snd_pcm_hw_params_set_rate_near(pcm, hw, &rate, 0) < 0) {
		fprintf(stderr, "ALSA does not support %uHz for your soundcard\r\n", rate);
		goto fail;
	}
	if (r != rate) {
		fprintf(stderr, "ALSA: sample rate set to %uHz instead of %u\r\n", rate, r);
	}

	alsa_buffer_time = 500000;
	alsa_period_time = 50000;

	if ((err = snd_pcm_hw_params_set_buffer_time_near(pcm, hw, &alsa_buffer_time, 0)) < 0) {
		fprintf(stderr, "Set buffer time failed: %s.\r\n", snd_strerror(err));
		goto fail;
	}

	if ((err = snd_pcm_hw_params_set_period_time_near(pcm, hw, &alsa_period_time, 0)) < 0) {
		fprintf(stderr, "Set period time failed: %s.\r\n", snd_strerror(err));
		goto fail;
	}

	if (snd_pcm_hw_params(pcm, hw) < 0) {
		fprintf(stderr, "Unable to install hw params\r\n");
		goto fail;
	}

	snd_pcm_sw_params_alloca(&sw);
	snd_pcm_sw_params_current(pcm, sw);
	if (snd_pcm_sw_params(pcm, sw) < 0) {
		fprintf(stderr, "Unable to install sw params\r\n");
		goto fail;
	}

	send_output = write_alsa_output;
	close_output = close_alsa_output;
	pause_output = pause_output_nop;
	resume_output = resume_output_nop;
	return (0);

fail:	close_alsa_output();
	return -1;
}

static int write_alsa_output(char * output_data, int output_size) {
	int err;
	snd_pcm_uframes_t frames;

	while (output_size > 0) {
		frames = snd_pcm_bytes_to_frames(pcm, output_size);
		if ((err = snd_pcm_writei(pcm, output_data, frames)) < 0) {
			if (snd_pcm_state(pcm) == SND_PCM_STATE_XRUN) {
				if ((err = snd_pcm_prepare(pcm)) < 0)
					fprintf(stderr, "\nsnd_pcm_prepare() failed.\r\n");
				alsa_first_time = 1;
				continue;
			}
			return err;
		}

		output_size -= snd_pcm_frames_to_bytes(pcm, err);
		output_data += snd_pcm_frames_to_bytes(pcm, err);
		if (alsa_first_time) {
			alsa_first_time = 0;
			snd_pcm_start(pcm);
		}
	}
	return (0);
}

static void close_alsa_output(void) {
	if (!pcm) return;
	printf("Shutting down sound output\r\n");
	snd_pcm_close(pcm);
	pcm = NULL;
}

#elif defined AUDIODRV_OSS

#if !defined(AFMT_S16_NE)
#ifdef WORDS_BIGENDIAN
#define AFMT_S16_NE AFMT_S16_BE
#else
#define AFMT_S16_NE AFMT_S16_LE
#endif
#endif

#define DEFAULT_FRAGSIZE 14
#define DEFAULT_NUMFRAGS 16

static char pcmname[64];

#define open_audio_output open_oss_output
static int write_oss_output(char * output_data, int output_size);
static void close_oss_output(void);

static void pause_output_oss(void) {
	ioctl(audio_fd, SNDCTL_DSP_POST, 0);
}

static int open_oss_output(void) {
	int tmp;
	unsigned int r;

	if (!pcmname[0]) {
		strcpy(pcmname, "/dev/dsp");
	}

	if ((audio_fd = open(pcmname, O_WRONLY)) < 0) {
		fprintf(stderr, "ERROR: Unable to open dsp (%s)\r\n", strerror(errno));
		return (-1);
	}
	if (ioctl(audio_fd, SNDCTL_DSP_RESET, 0) < 0) {
		fprintf(stderr, "ERROR: Unable to reset dsp\r\n");
		goto fail;
	}

	/* the library specifically outputs LE data. */
	tmp = AFMT_S16_LE;
	if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &tmp) < 0) {
		fprintf(stderr, "ERROR: Unable to set 16bit sound format\r\n");
		goto fail;
	}

	tmp = 2;
	if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &tmp) < 0) {
		fprintf(stderr, "ERROR: Unable to set stereo\r\n");
		goto fail;
	}

	r = rate;
	if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &rate) < 0) {
		fprintf(stderr, "ERROR: Unable to set %uHz sample rate\r\n", rate);
		goto fail;
	}
	if (r != rate) {
		fprintf(stderr, "OSS: sample rate set to %uHz instead of %u\r\n", rate, r);
	}

	tmp = (DEFAULT_NUMFRAGS<<16)|DEFAULT_FRAGSIZE;
	if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) {
		fprintf(stderr, "ERROR: Unable to set fragment size\r\n");
		goto fail;
	}

	send_output = write_oss_output;
	close_output = close_oss_output;
	pause_output = pause_output_oss;
	resume_output = resume_output_nop;
	return (0);

fail:	close_oss_output();
	return (-1);
}

static int write_oss_output(char * output_data, int output_size) {
	int res = 0;
	while (output_size > 0) {
		res = write(audio_fd, output_data, output_size);
		if (res > 0) {
			output_size -= res;
			output_data += res;
		} else {
			fprintf(stderr, "\nOSS: write failure to dsp: %s.\r\n",
				strerror(errno));
			return (-1);
		}
	}
	return (0);
}

static void close_oss_output(void) {
	if (audio_fd < 0)
		return;
	printf("Shutting down sound output\r\n");
	ioctl(audio_fd, SNDCTL_DSP_RESET, 0);
	close(audio_fd);
	audio_fd = -1;
}

#elif defined AUDIODRV_OPENAL

#define NUM_BUFFERS 4

static ALCdevice *device;
static ALCcontext *context;
static ALuint sourceId = 0;
static ALuint buffers[NUM_BUFFERS];
static ALuint frames = 0;

#define open_audio_output open_openal_output

static void pause_output_openal(void) {
	alSourcePause(sourceId);
}

static int write_openal_output(char * output_data, int output_size) {
	ALint processed, state;
	ALuint bufid;

	if (frames < NUM_BUFFERS) { /* initial state: fill the buffers */
		alBufferData(buffers[frames], AL_FORMAT_STEREO16, output_data,
				output_size, rate);

		/* Now queue and start playback! */
		if (++frames == NUM_BUFFERS) {
			alSourceQueueBuffers(sourceId, frames, buffers);
			alSourcePlay(sourceId);
		}
		return 0;
	}

	/* Get relevant source info */
	alGetSourcei(sourceId, AL_SOURCE_STATE, &state);
	if (state == AL_PAUSED) { /* resume it, then.. */
		alSourcePlay(sourceId);
		if (alGetError() != AL_NO_ERROR) {
			fprintf(stderr, "\nError restarting playback\r\n");
			return (-1);
		}
	}

	processed = 0;
	while (processed == 0) { /* Wait until we have a processed buffer */
		alGetSourcei(sourceId, AL_BUFFERS_PROCESSED, &processed);
	}

	/* Unqueue and handle each processed buffer */
	alSourceUnqueueBuffers(sourceId, 1, &bufid);

	/* Read the next chunk of data, refill the buffer, and queue it
	 * back on the source */
	alBufferData(bufid, AL_FORMAT_STEREO16, output_data, output_size, rate);
	alSourceQueueBuffers(sourceId, 1, &bufid);
	if (alGetError() != AL_NO_ERROR) {
		fprintf(stderr, "\nError buffering data\r\n");
		return (-1);
	}

	/* Make sure the source hasn't underrun */
	alGetSourcei(sourceId, AL_SOURCE_STATE, &state);
	/*printf("STATE: %#08x - %d\n", state, queued);*/
	if (state != AL_PLAYING) {
		ALint queued;

		/* If no buffers are queued, playback is finished */
		alGetSourcei(sourceId, AL_BUFFERS_QUEUED, &queued);
		if (queued == 0) {
			fprintf(stderr, "\nNo buffers queued for playback\r\n");
			return (-1);
		}

		alSourcePlay(sourceId);
	}

	return (0);
}

static void close_openal_output(void) {
	if (!context) return;
	printf("Shutting down sound output\r\n");
	alSourceStop(sourceId);			/* stop playing */
	alSourcei(sourceId, AL_BUFFER, 0);	/* unload buffer from source */
	alDeleteBuffers(NUM_BUFFERS, buffers);
	alDeleteSources(1, &sourceId);
	alcDestroyContext(context);
	alcCloseDevice(device);
	context = NULL;
	device = NULL;
	frames = 0;
}

static int open_openal_output(void) {
	/* setup our audio devices and contexts */
	device = alcOpenDevice(NULL);
	if (!device) {
		fprintf(stderr, "OpenAL: Unable to open default device.\r\n");
		return (-1);
	}

	context = alcCreateContext(device, NULL);
	if (context == NULL || alcMakeContextCurrent(context) == ALC_FALSE) {
		if (context != NULL)
			alcDestroyContext(context);
		alcCloseDevice(device);
		context = NULL;
		device = NULL;
		fprintf(stderr, "OpenAL: Failed to create the default context.\r\n");
		return (-1);
	}

	/* setup our sources and buffers */
	alGenSources(1, &sourceId);
	alGenBuffers(NUM_BUFFERS, buffers);

	send_output = write_openal_output;
	close_output = close_openal_output;
	pause_output = pause_output_openal;
	resume_output = resume_output_nop;
	return (0);
}

#else /* no audio output driver compiled in: */

#define open_audio_output open_noaudio_output
static int open_noaudio_output(void) {
	fprintf(stderr, "No audio output driver was selected at compile time.\r\n");
	return -1;
}

#endif /* AUDIODRV_ALSA */
#endif /* _WIN32 || __CYGWIN__ */

static struct option const long_options[] = {
	{ "version", 0, 0, 'v' },
	{ "help", 0, 0, 'h' },
	{ "rate", 1, 0, 'r' },
	{ "mastervol", 1, 0, 'm' },
	{ "config", 1, 0, 'c' },
	{ "wavout", 1, 0, 'o' },
	{ "log_vol", 0, 0, 'l' },
	{ "reverb", 0, 0, 'b' },
	{ "test_midi", 0, 0, 't' },
	{ "test_bank", 1, 0, 'k' },
	{ "test_patch", 1, 0, 'p' },
	{ "enhanced", 0, 0, 'e' },
#if defined(AUDIODRV_OSS) || defined(AUDIODRV_ALSA)
	{ "device", 1, 0, 'd' },
#endif
	{ "wholetempo", 0, 0, 'w' },
	{ "roundtempo", 0, 0, 'n' },
	{ NULL, 0, NULL, 0 }
};

static void do_help(void) {
	printf("  -v    --version     Display version info and exit\n");
	printf("  -h    --help        Display this help and exit\n");
#if defined(AUDIODRV_OSS) || defined(AUDIODRV_ALSA)
	printf("  -d D  --device=D    Use device D for audio output instead of default\n");
#endif
	printf("MIDI Options:\n");
	printf("  -w    --wholetempo  Round down tempo to whole number\n");
	printf("  -n    --roundtempo  Round tempo to nearest whole number\n");
	printf("Software Wavetable Options:\n");
	printf("  -o W  --wavout=W    Save output to W in 16bit stereo format wav file\n");
	printf("  -l    --log_vol     Use log volume adjustments\n");
	printf("  -r N  --rate=N      Set sample rate to N samples per second (Hz)\n");
	printf("  -c P  --config=P    Point to your wildmidi.cfg config file name/path\n");
	printf("                      defaults to: %s\n", WILDMIDI_CFG);
	printf("  -m V  --mastervol=V Set the master volume (0..127), default is 100\n");
	printf("  -b    --reverb      Enable final output reverb engine\n");
}

static void do_version(void) {
	printf("\nWildMidi %s Open Source Midi Sequencer\n", PACKAGE_VERSION);
	printf("Copyright (C) WildMIDI Developers 2001-2016\n\n");
	printf("WildMidi comes with ABSOLUTELY NO WARRANTY\n");
	printf("This is free software, and you are welcome to redistribute it under\n");
	printf("the terms and conditions of the GNU General Public License version 3.\n");
	printf("For more information see COPYING\n\n");
	printf("Report bugs to %s\n", PACKAGE_BUGREPORT);
	printf("WildMIDI homepage is at %s\n\n", PACKAGE_URL);
}

static void do_syntax(void) {
	printf("Usage: wildmidi [options] filename.mid\n\n");
}

static char config_file[1024];

int main(int argc, char **argv) {
	struct _WM_Info *wm_info;
	int i, res;
	int option_index = 0;
	unsigned short mixer_options = 0;
	void *midi_ptr;
	unsigned char master_volume = 100;
	char *output_buffer;
	unsigned long int perc_play;
	unsigned long int pro_mins;
	unsigned long int pro_secs;
	unsigned long int apr_mins;
	unsigned long int apr_secs;
	char modes[4];
	unsigned long int count_diff;
	unsigned char ch;
	int test_midi = 0;
	int test_count = 0;
	unsigned char *test_data;
	unsigned char test_bank = 0;
	unsigned char test_patch = 0;
	static char spinner[] = "|/-\\";
	static int spinpoint = 0;
	unsigned long int seek_to_sample;
	int inpause = 0;

#if defined(AUDIODRV_OSS) || defined(AUDIODRV_ALSA)
	pcmname[0] = 0;
#endif
	config_file[0] = 0;
	wav_file[0] = 0;

	do_version();
	while (1) {
		i = getopt_long(argc, argv, "vho:lr:c:m:btk:p:ed:wn", long_options,
				&option_index);
		if (i == -1)
			break;
		switch (i) {
		case 'v': /* Version */
			return (0);
		case 'h': /* help */
			do_syntax();
			do_help();
			return (0);
		case 'r': /* Sample Rate */
			res = atoi(optarg);
			if (res < 0 || res > 65535) {
				fprintf(stderr, "Error: bad rate %i.\n", res);
				return (1);
			}
			rate = res;
			break;
		case 'b': /* Reverb */
			mixer_options |= WM_MO_REVERB;
			break;
		case 'm': /* Master Volume */
			master_volume = (unsigned char) atoi(optarg);
			break;
		case 'o': /* Wav Output */
			if (!*optarg) {
				fprintf(stderr, "Error: empty wavfile name.\n");
				return (1);
			}
			strncpy(wav_file, optarg, sizeof(wav_file));
			wav_file[sizeof(wav_file) - 1] = 0;
			break;
		case 'c': /* Config File */
			if (!*optarg) {
				fprintf(stderr, "Error: empty config name.\n");
				return (1);
			}
			strncpy(config_file, optarg, sizeof(config_file));
			config_file[sizeof(config_file) - 1] = 0;
			break;
#if defined(AUDIODRV_OSS) || defined(AUDIODRV_ALSA)
		case 'd': /* Output device */
			if (!*optarg) {
				fprintf(stderr, "Error: empty device name.\n");
				return (1);
			}
			strncpy(pcmname, optarg, sizeof(pcmname));
			pcmname[sizeof(pcmname) - 1] = 0;
			break;
#endif
		case 'e': /* Enhanced Resampling */
			mixer_options |= WM_MO_ENHANCED_RESAMPLING;
			break;
		case 'l': /* log volume */
			mixer_options |= WM_MO_LOG_VOLUME;
			break;
		case 't': /* play test midis */
			test_midi = 1;
			break;
		case 'k': /* set test bank */
			test_bank = (unsigned char) atoi(optarg);
			break;
		case 'p': /* set test patch */
			test_patch = (unsigned char) atoi(optarg);
			break;
		case 'w': /* whole number tempo */
			mixer_options |= WM_MO_WHOLETEMPO;
			break;
		case 'n': /* whole number tempo */
			mixer_options |= WM_MO_ROUNDTEMPO;
			break;
		default:
			do_syntax();
			return (1);
		}
	}

	if (optind < argc || test_midi) {
		if (!config_file[0]) {
			strncpy(config_file, WILDMIDI_CFG, sizeof(config_file));
			config_file[sizeof(config_file) - 1] = 0;
		}

		printf("Initializing Sound System\n");
		if (wav_file[0] != '\0') {
			if (open_wav_output() == -1) {
				return (1);
			}
		} else {
			if (open_audio_output() == -1) {
				return (1);
			}
		}

		printf("Initializing %s\n\r\n", WildMidi_GetString(WM_GS_VERSION));
		if (WildMidi_Init(config_file, rate, mixer_options) == -1) {
			return (1);
		}

		printf(" +  Volume up        e  Better resampling    n  Next Midi\n");
		printf(" -  Volume down      l  Log volume           q  Quit\n");
		printf(" ,  1sec Seek Back   r  Reverb               .  1sec Seek Forward\n");
		printf("                     p  Pause On/Off\n\n");

		output_buffer = malloc(16384);
		if (output_buffer == NULL) {
			fprintf(stderr, "Not enough memory, exiting\n");
			WildMidi_Shutdown();
			return (1);
		}

		wm_inittty();
#ifdef WILDMIDI_AMIGA
		amiga_sysinit();
#endif

		WildMidi_MasterVolume(master_volume);

		while (optind < argc || test_midi) {
			if (!test_midi) {
				const char *real_file = FIND_LAST_DIRSEP(argv[optind]);

				if (!real_file) real_file = argv[optind];
				else real_file++;
				printf("Playing %s \r\n", real_file);

				midi_ptr = WildMidi_Open(argv[optind]);
				optind++;
				if (midi_ptr == NULL) {
					fprintf(stderr, "\rSkipping %s\r\n", real_file);
					continue;
				}
			} else {
				if (test_count == midi_test_max) {
					break;
				}
				test_data = malloc(midi_test[test_count].size);
				memcpy(test_data, midi_test[test_count].data,
						midi_test[test_count].size);
				test_data[25] = test_bank;
				test_data[28] = test_patch;
				midi_ptr = WildMidi_OpenBuffer(test_data, 633);
				test_count++;
				if (midi_ptr == NULL) {
					fprintf(stderr, "\rFailed loading test midi no. %i\r\n", test_count);
					continue;
				}
				printf("Playing test midi no. %i\r\n", test_count);
			}

			wm_info = WildMidi_GetInfo(midi_ptr);
			apr_mins = wm_info->approx_total_samples / (rate * 60);
			apr_secs = (wm_info->approx_total_samples % (rate * 60)) / rate;
			mixer_options = wm_info->mixer_options;
			modes[0] = (mixer_options & WM_MO_LOG_VOLUME)? 'l' : ' ';
			modes[1] = (mixer_options & WM_MO_REVERB)? 'r' : ' ';
			modes[2] = (mixer_options & WM_MO_ENHANCED_RESAMPLING)? 'e' : ' ';
			modes[3] = '\0';
			fprintf(stderr, "\r");

			while (1) {
				count_diff = wm_info->approx_total_samples
						- wm_info->current_sample;

				if (count_diff == 0)
					break;

				ch = 0;
#if defined(_WIN32) && !defined(__CYGWIN__)
				if (_kbhit()) {
					ch = _getch();
					_putch(ch);
				}
#elif defined(__DJGPP__) || defined(__OS2__) || defined(__EMX__)
				if (kbhit()) {
					ch = getch();
					putch(ch);
				}
#elif defined(WILDMIDI_AMIGA)
				amiga_getch (&ch);
#else
				if (read(STDIN_FILENO, &ch, 1) != 1)
					ch = 0;
#endif
				if (ch) {
					switch (ch) {
					case 'l':
						WildMidi_SetOption(midi_ptr, WM_MO_LOG_VOLUME,
								((mixer_options & WM_MO_LOG_VOLUME)
										^ WM_MO_LOG_VOLUME));
						mixer_options ^= WM_MO_LOG_VOLUME;
						modes[0] = (mixer_options & WM_MO_LOG_VOLUME)? 'l' : ' ';
						break;
					case 'r':
						WildMidi_SetOption(midi_ptr, WM_MO_REVERB,
								((mixer_options & WM_MO_REVERB) ^ WM_MO_REVERB));
						mixer_options ^= WM_MO_REVERB;
						modes[1] = (mixer_options & WM_MO_REVERB)? 'r' : ' ';
						break;
					case 'e':
						WildMidi_SetOption(midi_ptr, WM_MO_ENHANCED_RESAMPLING,
								((mixer_options & WM_MO_ENHANCED_RESAMPLING)
										^ WM_MO_ENHANCED_RESAMPLING));
						mixer_options ^= WM_MO_ENHANCED_RESAMPLING;
						modes[2] = (mixer_options & WM_MO_ENHANCED_RESAMPLING)? 'e' : ' ';
						break;
					case 'n':
						goto NEXTMIDI;
					case 'p':
						if (inpause) {
							inpause = 0;
							fprintf(stderr, "       \r");
							resume_output();
						} else {
							inpause = 1;
							fprintf(stderr, "Paused \r");
							pause_output();
							continue;
						}
						break;
					case 'q':
						printf("\r\n");
						if (inpause) goto end2;
						goto end1;
					case '-':
						if (master_volume > 0) {
							master_volume--;
							WildMidi_MasterVolume(master_volume);
						}
						break;
					case '+':
						if (master_volume < 127) {
							master_volume++;
							WildMidi_MasterVolume(master_volume);
						}
						break;
					case ',': /* fast seek backwards */
						if (wm_info->current_sample < rate) {
							seek_to_sample = 0;
						} else {
							seek_to_sample = wm_info->current_sample - rate;
						}
						WildMidi_FastSeek(midi_ptr, &seek_to_sample);
						break;
					case '.': /* fast seek forwards */
						if ((wm_info->approx_total_samples
								- wm_info->current_sample) < rate) {
							seek_to_sample = wm_info->approx_total_samples;
						} else {
							seek_to_sample = wm_info->current_sample + rate;
						}
						WildMidi_FastSeek(midi_ptr, &seek_to_sample);
						break;
					default:
						break;
					}
				}

				if (inpause) {
					wm_info = WildMidi_GetInfo(midi_ptr);
					perc_play = (wm_info->current_sample * 100)
							/ wm_info->approx_total_samples;
					pro_mins = wm_info->current_sample / (rate * 60);
					pro_secs = (wm_info->current_sample % (rate * 60)) / rate;
					fprintf(stderr,
						"        [Approx %2lum %2lus Total] [%s] [%3i] [%2lum %2lus Processed] [%2lu%%] 0  \r",
						apr_mins, apr_secs, modes, (int)master_volume, pro_mins,
						pro_secs, perc_play);

					msleep(5);
					continue;
				}

				res = WildMidi_GetOutput(midi_ptr, output_buffer,
							 (count_diff >= 4096)? 16384 : (count_diff * 4));
				if (res <= 0)
					break;

				wm_info = WildMidi_GetInfo(midi_ptr);
				perc_play = (wm_info->current_sample * 100)
						/ wm_info->approx_total_samples;
				pro_mins = wm_info->current_sample / (rate * 60);
				pro_secs = (wm_info->current_sample % (rate * 60)) / rate;
				fprintf(stderr,
						"        [Approx %2lum %2lus Total] [%s] [%3i] [%2lum %2lus Processed] [%2lu%%] %c  \r",
						apr_mins, apr_secs, modes, (int)master_volume, pro_mins,
						pro_secs, perc_play, spinner[spinpoint++ % 4]);

#if defined(AUDIODRV_OPENAL) && defined(WORDS_BIGENDIAN)
				if (context != NULL) { /* OpenAL is active, not wav writer */
					unsigned short *swp = (unsigned short *) output_buffer;
					/* the library specifically outputs LE data
					 * but OpenAL expects host-endian: do swap. */
					for (i = 0; i < res / 2; ++i) {
						swp[i] = (swp[i] << 8) | (swp[i] >> 8);
					}
				}
#endif
#if defined(AUDIODRV_AHI) && defined(WORDS_BIGENDIAN)
				if (AHIBuf[0] != NULL) { /* AHI is active, not wav writer */
					unsigned short *swp = (unsigned short *) output_buffer;
					/* the library specifically outputs LE data
					 * but AHI expects host-endian: do swap.  */
					for (i = 0; i < res / 2; ++i) {
						swp[i] = (swp[i] << 8) | (swp[i] >> 8);
					}
				}
#endif
				if (send_output(output_buffer, res) < 0) {
				/* driver prints an error message already. */
					printf("\r");
					goto end2;
				}
			}
			NEXTMIDI: fprintf(stderr, "\r\n");
			if (WildMidi_Close(midi_ptr) == -1) {
				fprintf(stderr, "OOPS: failed closing midi handle!\r\n");
			}
			memset(output_buffer, 0, 16384);
			send_output(output_buffer, 16384);
		}
end1:		memset(output_buffer, 0, 16384);
		send_output(output_buffer, 16384);
		msleep(5);
end2:		close_output();
		free(output_buffer);
		if (WildMidi_Shutdown() == -1)
			fprintf(stderr, "OOPS: failure shutting down libWildMidi\r\n");
		wm_resetty();
	} else {
		fprintf(stderr, "ERROR: No midi file given\r\n");
		do_syntax();
		return (1);
	}

	printf("\r\n");
	return (0);
}

/* helper / replacement functions: */

#if !(defined(_WIN32) || defined(__CYGWIN__) || defined(__DJGPP__) || defined(WILDMIDI_AMIGA) || defined(__OS2__) || defined(__EMX__))
static int msleep(unsigned long milisec) {
	struct timespec req = { 0, 0 };
	time_t sec = (int) (milisec / 1000);
	milisec = milisec - (sec * 1000);
	req.tv_sec = sec;
	req.tv_nsec = milisec * 1000000L;
	while (nanosleep(&req, &req) == -1)
		continue;
	return (1);
}
#endif
