/* 
 * Public domain skeleton for a /dev/music compatible OSS application.
 *
 * Use the included Makefile.music to compile this (make -f Makefile.music).
 */

/* 
 * Standard includes
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>

/*
 * This program uses just one output device which is defined by the following
 * macro. However the OSS API permits using any number of devices 
 * simultaneously.
 */
#define MY_DEVICE	0 /* 0 is the first available device */

/* 
 * The OSS API macros assume that the file descriptor of /dev/music
 * (or /dev/sequencer) is stored in variable called seqfd. It has to be
 * defined in one source file. Other source files in the same application
 * should define it extern.
 */
int seqfd=-1;

/*
 * A buffer needs to be allocated for buffering the events locally in 
 * the program (prior writing them to the device file). The SEQ_DEFINEBUF
 * macro can be used to define the buffer. The argument is the size of the
 * buffer (in bytes). 1024 is a good size (128 events).
 *
 * Note that SEQ_DEFINEBUF() should be used only in one source file in each
 * application. In other source files you should use SEQ_USE_EXTBUF().
 */
#define BUFFSIZE	1024
SEQ_DEFINEBUF(BUFFSIZE);

/*
 * seqbuf_dump() routine is required only wnen OSSLib is NOT used. It's
 * purpose is to write buffered events to the device file.
 */

#ifndef OSSLIB
/*
 * NOTE! Don't ever define seqbuf_dump() in two source files or when OSSlib
 *       is used. It may have unpredictable results.
 */
void
seqbuf_dump ()
{
  if (_seqbufptr)
    if (write (seqfd, _seqbuf, _seqbufptr) == -1)
      {
	perror ("write /dev/music");
	exit (-1);
      }
  _seqbufptr = 0;
}
#endif

/* 
 * main
 */

int main(int argc, char *argv[])
{
	int error, ndevices, tmp;

	/*
	 * First open the device file (/dev/music in this case but
	 * /dev/sequencer will work in the same way). The device is
	 * opened with O_WRONLY since we are only going to write. Use
	 * O_WRONLY or O_RDWR if you need to use input (too).
	 */

	if ((seqfd=open("/dev/music", O_WRONLY, 0))==-1)
	{
		perror("/dev/music");
		exit(-1);
	}


	/*
	 * Now initialize OSSlib if required.
	 */
#ifdef OSSLIB
	if ((error=OSS_init(seqfd, BUFFSIZE)) != 0)
	{
		fprintf(stderr, "Failed to initialize OSSlib, error %d\n",
			error);
		exit(-1);
	}
#endif

	/*
 	 * Check that the (synth) device to be used is available.
   	 */

	if (ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &ndevices)==-1)
	{
	   perror("SNDCTL_SEQ_NRSYNTHS");
	   exit(-1);
	}

	if (MY_DEVICE >= ndevices)
	{
	   fprintf(stderr, 
		"Error: The requested playback device doesn't exist\n");
	   exit(-1);
	}

	/*****************************************************************
 	 * The above was all just (mandatory) initialization code. Now the
	 * fun part. Play a middle C note (#60) using acoustic piano 
	 * (GM instrument 0). Set tempo to 60 (BPM) and timebase to 96
	 * (ticks/beat). Wait for 96 timing ticks (duration of 1/4th note)
	 * and issue note off. Finally wait for 1000 ticks just to let the
	 * note to decay normally before closing the device.
	 *****************************************************************/

	/*
	 * Setup timing parameters. The defaults may vary so set them
	 * explicitly.
	 */
	tmp = 96;
	if (ioctl(seqfd, SNDCTL_TMR_TIMEBASE, &tmp)==-1)
	{
		perror("Set timebase");
		exit(-1);
	}

	tmp = 60;
	if (ioctl(seqfd, SNDCTL_TMR_TEMPO, &tmp)==-1)
	{
		perror("Set tempo");
		exit(-1);
	}

	/*
	 * Next use OSSlib to cache the instrument (if required). This is
	 * recommended to be done in advance (before SEQ_START_TIMER()) since
	 * patch loading from disk to the device can be time consuming. Load
	 * only the instruments that are required due to limited memory
	 * capacity of certain devices.
	 *
	 * NOTE! OSSLib loads the instrument automaticly when SEQ_PGM_CHANGE
	 *       is called. Loading it in advance saves you from possible
	 *	 delays associated with demand loading.
	 */
	SEQ_LOAD_GMINSTR(MY_DEVICE, 0); /* 0=Acoustic piano */

	/*
	 * Now we are ready to start playing. The first task is to start
	 * the timer. This is mandatory stem since otherwise the timer
	 * will never get started. It's extremely important to start the
	 * timer just immediately before writing the first event. Doing
	 * it too early will cause tempo problems in the beginning.
	 */
	SEQ_START_TIMER();

	/*
	 * Select the program/instrument 0 on the MIDI channel 0.
	 */
	SEQ_PGM_CHANGE(MY_DEVICE, 0, 0);

	/*
	 * Start the note (60=Middle C) on channel 0. Use 64 as velocity.
	 */
	SEQ_START_NOTE(MY_DEVICE, 0, 60, 64);

	/*
	 * Then have relative delay of 96 ticks. The delay is from the
	 * previous timing event or from the time when SEQ_START_TIMER()
	 * was called.
	 */
	SEQ_DELTA_TIME(96);

	/*
	 * Now stop the note. The sound will not stop immediately. The note
	 * just starts decaying and fades off.
	 */
	SEQ_STOP_NOTE(MY_DEVICE, 0, 60, 64);

	/*
	 * Have a final delay of 1000 ticks. This gives the last note(s) time
	 * to decay naturally. Closing the device without this delay just
	 * aborts all voices prematurely.
	 */
	SEQ_DELTA_TIME(1000);
	/*
         * Finally flush all events still in the local buffer (mandatory
	 * step before closing the device or prior pausing the application.
	 * It's the SEQ_DUMPBUF() call that actually writes the events to the
 	 * device.
	 */
	
	SEQ_DUMPBUF();
	close(seqfd);
	exit(0);
}
