summaryrefslogtreecommitdiff
path: root/tutorials/sndkit/softsynth
diff options
context:
space:
mode:
Diffstat (limited to 'tutorials/sndkit/softsynth')
-rw-r--r--tutorials/sndkit/softsynth/Makefile23
-rw-r--r--tutorials/sndkit/softsynth/README16
-rw-r--r--tutorials/sndkit/softsynth/softsynth.c651
-rw-r--r--tutorials/sndkit/softsynth/softsynth_gtk.c869
4 files changed, 1559 insertions, 0 deletions
diff --git a/tutorials/sndkit/softsynth/Makefile b/tutorials/sndkit/softsynth/Makefile
new file mode 100644
index 0000000..b522436
--- /dev/null
+++ b/tutorials/sndkit/softsynth/Makefile
@@ -0,0 +1,23 @@
+include /etc/oss.conf
+
+BINDIR=/usr/local/bin
+
+OSSINCLUDES=-I$(OSSLIBDIR)/include/sys -I$(OSSLIBDIR)/include -L/usr/lib/oss/lib
+CFLAGS=-lm -O6
+
+all: softsynth softsynth_gtk
+
+softsynth: softsynth.c
+ $(CC) $(CFLAGS) $(OSSINCLUDES) softsynth.c -o softsynth -lOSSlib
+
+softsynth_gtk1: softsynth_gtk.c
+ -$(CC) $(CFLAGS) `gtk-config --cflags --libs` $(OSSINCLUDES) softsynth_gtk.c -o softsynth_gtk -lOSSlib
+
+softsynth_gtk: softsynth_gtk.c
+ -$(CC) $(CFLAGS) `pkg-config gtk+-2.0 --cflags --libs` $(OSSINCLUDES) softsynth_gtk.c -o softsynth_gtk -lOSSlib
+
+install: all
+ -cp softsynth softsynth_gtk $(BINDIR)
+
+clean:
+ rm -f softsynth softsynth_gtk *.o x y z *~ core core.*
diff --git a/tutorials/sndkit/softsynth/README b/tutorials/sndkit/softsynth/README
new file mode 100644
index 0000000..1bfe1a8
--- /dev/null
+++ b/tutorials/sndkit/softsynth/README
@@ -0,0 +1,16 @@
+What is this?
+=============
+
+This directory contains a very simple software MIDI organ which is
+based on playback of sine waves at various frequencies and amplitudes. Please
+see the softsynth.c file for better explanation. This is probably the simpliest
+software synthesizer application one can write.
+
+The softsynth.c file contains the original version of this program
+(no GUI). It's compatible also with any of the earlier OSS versions if you
+use an OSSlib version that contains the MIDI parser library (shipped with
+OSS 3.99.1j and later).
+
+The softsynth_gtk.c contains another version of the original program. It
+has GTK+ GUI. In addition it uses several services that are only
+available in OSS 4.0 and later.
diff --git a/tutorials/sndkit/softsynth/softsynth.c b/tutorials/sndkit/softsynth/softsynth.c
new file mode 100644
index 0000000..12e77eb
--- /dev/null
+++ b/tutorials/sndkit/softsynth/softsynth.c
@@ -0,0 +1,651 @@
+/*
+ * Purpose: A simple software MIDI synthesizer program.
+ * Copyright (C) 4Front Technologies, 2002-2004. Released in public domain.
+ *
+ * Description:
+ * This is a pretty simple program that demonstrates how to do MIDI input at
+ * the same time with audio playback (using select). It also demonstrates how
+ * to use the MIDI loopback devices of OSS 4.0 (and later).
+ * Please note that this program is nothing but a programming example. It's
+ * "output quality" equals to $10 (or cheaper) toy organs. However it's very
+ * amazing how great some songs (MIDI files) sound even 90% of the MIDI
+ * information is simply ignored.
+ *
+ * What this program actually does is that it listen's to the MIDI input port
+ * and interprets the incoming MIDI stream (using the midiparser routines
+ * included in the OSSlib library).
+ *
+ * For simplicity reasons this program does nothing else but plays simple
+ * sine waves at the right note frequencies. Percussive sounds (MIDI
+ * channel 10) are simply ignored because playing them as sine waves doesn't
+ * make any sense. All MIDI controllers, pitch bend as well as all the other
+ * MIDI features are ignored too. However the all notes off control change
+ * message is handled because otherwise hanging notes will be left if the
+ * client (player) application gets killed abnormally.
+ *
+ * There is simple fixed envelope handling (actually just attack and decay)
+ * and primitive handling of note on velocity. These features appeared to be
+ * necessary because otherwise nobody can listen the output.
+ *
+ * This program is not too usefull as a synthesizer. It's not intended to be
+ * any super "modular synthesizer". However it demonstrates how simple it is
+ * to implement any kind of software MIDI synthesizer using the OSS API.
+ * You don't need to know how to use some 450 audio related calls or 300
+ * MIDI/sequencer related calls. As you will see practically everything will
+ * be handled automagically by OSS. So you can spend all your time on
+ * writing the application itself. This program was written, and debugged
+ * in less than 5 hours from scratch (including MIDI input, audio output
+ * and the actual synthesis). In fact it took longer time to write these
+ * comments than the application itself.
+ *
+ * The major benefit of this super simple design is that it cannot fail.
+ * Provided that you don't try to set the buffer size to a too small value
+ * the application logic is fully nuke proof. It will work unmodified with
+ * every sound card in the world (past, current and future).
+ *
+ * The MIDI parser code was taken from some earlier work but we have included
+ * if in the OSSlib library for you (under LGPL). Please feel free to use it
+ * in your own OSS MIDI applications.
+ *
+ * To use this program you will need to install the "4Front MIDI loopback"
+ * driver using the "Add new card/device" function of soundconf.
+ * Then start this program in background (the audio and MIDI device names
+ * can be given as command line arguments. For example
+ *
+ * softsynth /dev/dsp /dev/midi01
+ *
+ * You can find out the loopback MIDI device number by looking for the
+ * "MIDI loopback server side" devices using the {!xlink ossinfo} -m
+ * command. Btw, nothing prevents using any "real" physical MIDI port as the
+ * input.
+ *
+ * When the synthesizer server is running you can play any MIDI file using
+ * some OSS based MIDI sequencer/player such as {!xlink ossmplay}.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/select.h>
+#include <sys/soundcard.h>
+#include <midiparser.h>
+midiparser_common_t *parser = NULL;
+
+int audio_fd;
+int midi_fd;
+int sample_rate = 48000;
+
+/*
+ * The open_audio_device routine opens the audio device and initializes it
+ * for the required mode. This code was borrowed directly from the
+ * {!nlink singen.c} sample program. However since the buffer size
+ * is inportant with this kind of application we have added a call that
+ * sets the fragment and buffer sizes.
+ */
+
+static int
+open_audio_device (char *name, int mode)
+{
+ int tmp, fd;
+
+ if ((fd = open (name, mode, 0)) == -1)
+ {
+ perror (name);
+ exit (-1);
+ }
+
+/*
+ * Setup the audio buffering policy so that reasonably small latencies
+ * can be obtained.
+ *
+ * 4 fragments of 256 samples (512 bytes) might be good. 256 samples
+ * will give timing granularity of 256/sample_rate seconds (5.33 msec)
+ * which is fairly adequate. The effect of the granularity (fragment size) in
+ * this type of applications is timing jitter (or choking). Every event that
+ * occurs withing the 5.33 msec period (fragment time) will get executed
+ * in the beginning of the next period. If the fragment size is decreased
+ * then the granularity will decrease too. However this will cause slight
+ * increase in the CPU consumption of the application.
+ *
+ * The total buffer size (number_of_fragments*fragment_time) will define the
+ * maximum latency between the event (note on/off) and the actual change in the
+ * audio output. The average latency will be something like
+ * (number_of_fragments-0.5)*fragment_time). The theoretical average latency
+ * caused by this application is (4-0.5)*5.33 msec = ~19 msec).
+ *
+ * In musical terms 5.33 msec granularity equals to 1/750 note at 60 bpm
+ * and 19 msecs equals to 1/214. This should be pretty adequate.
+ *
+ * The latency can be decreased by limiting the number of fragments and/or the
+ * fragment size. However the after the buffer size drops close to the
+ * capabilities of the system (delays caused by the other applications) the
+ * audio output will start breaking. This can cured only by tuning the
+ * hardware and the software environments (tuning some kernel parameters and
+ * by killing all the other applications). However this is in no way an OSS
+ * issue.
+ *
+ * With these parameters it was possible to compile Linux kernel in another
+ * terminal window without any hiccup (fairly entry level 2.4 GHz P4 system
+ * running Linux 2.6.x).
+ */
+ tmp = 0x00040009;
+ if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
+ {
+ perror ("SNDCTL_DSP_SETFRAGMENT");
+ }
+
+/*
+ * Setup the device. Note that it's important to set the
+ * sample format, number of channels and sample rate exactly in this order.
+ * Some devices depend on the order.
+ */
+
+/*
+ * Set the sample format
+ */
+ tmp = AFMT_S16_NE; /* Native 16 bits */
+ if (ioctl (fd, SNDCTL_DSP_SETFMT, &tmp) == -1)
+ {
+ perror ("SNDCTL_DSP_SETFMT");
+ exit (-1);
+ }
+
+ if (tmp != AFMT_S16_NE)
+ {
+ fprintf (stderr,
+ "The device doesn't support the 16 bit sample format.\n");
+ exit (-1);
+ }
+
+/*
+ * Set the number of channels (mono)
+ */
+ tmp = 1;
+ if (ioctl (fd, SNDCTL_DSP_CHANNELS, &tmp) == -1)
+ {
+ perror ("SNDCTL_DSP_CHANNELS");
+ exit (-1);
+ }
+
+ if (tmp != 1)
+ {
+ fprintf (stderr, "The device doesn't support mono mode.\n");
+ exit (-1);
+ }
+
+/*
+ * Set the sample rate
+ */
+ sample_rate = 48000;
+ if (ioctl (fd, SNDCTL_DSP_SPEED, &sample_rate) == -1)
+ {
+ perror ("SNDCTL_DSP_SPEED");
+ exit (-1);
+ }
+
+/*
+ * No need for rate checking because we will automatically adjust the
+ * signal based on the actual sample rate. However most application must
+ * check the value of sample_rate and compare it to the requested rate.
+ *
+ * Small differences between the rates (10% or less) are normal and the
+ * applications should usually tolerate them. However larger differences may
+ * cause annoying pitch problems (Mickey Mouse).
+ */
+
+ return fd;
+}
+
+static int
+open_midi_device (char *name, int mode)
+{
+ int tmp, fd;
+
+/*
+ * This is pretty much all we nbeed.
+ */
+
+ if ((fd = open (name, mode, 0)) == -1)
+ {
+ perror (name);
+ exit (-1);
+ }
+
+ return fd;
+}
+
+#define MAX_VOICES 256
+
+typedef struct
+{
+ int active; /* ON/OFF */
+ int chn, note, velocity; /* MIDI note parameters */
+
+ float phase, step; /* Sine frequency generator */
+
+ float volume; /* Note volume */
+
+ float envelope, envelopestep; /* Envelope generator */
+ int envelopedir; /* 0=fixed level, 1=attack, -1=decay */
+} voice_t;
+
+static voice_t voices[MAX_VOICES] = { 0 };
+
+static int
+note_to_freq (int note_num)
+{
+
+/*
+ * This routine converts a midi note to a frequency (multiplied by 1000)
+ * Notice! This routine was copied from the OSS sequencer code.
+ */
+
+ int note, octave, note_freq;
+ static int notes[] = {
+ 261632, 277189, 293671, 311132, 329632, 349232,
+ 369998, 391998, 415306, 440000, 466162, 493880
+ };
+
+#define BASE_OCTAVE 5
+
+ octave = note_num / 12;
+ note = note_num % 12;
+
+ note_freq = notes[note];
+
+ if (octave < BASE_OCTAVE)
+ note_freq >>= (BASE_OCTAVE - octave);
+ else if (octave > BASE_OCTAVE)
+ note_freq <<= (octave - BASE_OCTAVE);
+
+ return note_freq;
+}
+
+/*
+ * The note_on() routine initializes a voice with the right
+ * frequency, volume and envelope parameters.
+ */
+
+static void
+note_on (int ch, int note, int velocity)
+{
+ int i;
+
+ for (i = 0; i < MAX_VOICES; i++)
+ if (!voices[i].active)
+ {
+ voice_t *v = &voices[i];
+ int freq;
+ float step;
+
+/*
+ * Record the MIDI note on message parameters (just in case)
+ */
+
+ v->chn = ch;
+ v->note = note;
+ v->velocity = velocity;
+
+/*
+ * Convert the note number to the actual frequency (multiplied by 1000).
+ * Then compute the step to be added to the phase angle to get the right
+ * frequency.
+ */
+
+ freq = note_to_freq (note);
+ step = 1000.0 * (float) sample_rate / (float) freq; /* Samples/cycle */
+ v->step = 2.0 * M_PI / step;
+ if (v->step > M_PI) /* Nyqvist was here */
+ return;
+ v->phase = 0.0;
+
+/*
+ * Compute the note volume based on the velocity. Use linear scale which
+ * maps velocity=0 to the 25% volume level. Proper synthesizers will use more
+ * advanced methods (such as logarithmic scales) but this is good for our
+ * purposes.
+ */
+ v->volume = 0.25 + ((float) velocity / 127.0) * 0.75;
+
+/*
+ * Initialize the envelope engine to start from zero level and to add
+ * some fixed amount to the envelope level after each sample.
+ */
+ v->envelope = 0.0;
+ v->envelopedir = 1;
+ v->envelopestep = 0.01;
+
+/*
+ * Fire the voice. However nothing will happen before the next audio
+ * period (fragment) gets computed. This means that all the voices started
+ * during the ending period will be rounded to start at the same moment.
+ */
+ v->active = 1;
+ break;
+ }
+}
+
+/*
+ * The note_off() routine finds all the voices that have matching channel and
+ * note numbers. Then it starts the envelope decay phase (10 times slower
+ * than the attack phase.
+ */
+
+static void
+note_off (int ch, int note, int velocity)
+{
+ int i;
+
+ for (i = 0; i < MAX_VOICES; i++)
+ if (voices[i].active && voices[i].chn == ch)
+ if (voices[i].note = note)
+ {
+ voice_t *v = &voices[i];
+ v->envelopedir = -1;
+ v->envelopestep = -0.001;
+ }
+}
+
+/*
+ * all_notes_off() is a version of note_off() that checks only the channel
+ * number. Used for the All Notes Off MIDI controller (123).
+ */
+
+static void
+all_notes_off (int ch)
+{
+ int i;
+
+ for (i = 0; i < MAX_VOICES; i++)
+ if (voices[i].active && voices[i].chn == ch)
+ {
+ voice_t *v = &voices[i];
+ v->envelopedir = -1;
+ v->envelopestep = -0.01;
+ }
+}
+
+/*
+ * Compute voice computes few samples (nloops) and sums them to the
+ * buffer (that contains the sum of all previously computed voices).
+ *
+ * In real world applications it may be necessary to convert this routine to
+ * use floating point buffers (-1.0 to 1.0 range) and do the conversion
+ * to fixed point only in the final output stage. Another change you may
+ * want to do is using multiple output buffers (for stereo or multiple
+ * channels) instead of the current mono scheme.
+ *
+ * For clarity reasons we have not done that.
+ */
+
+static void
+compute_voice (voice_t * v, short *buf, int nloops)
+{
+ int i;
+
+ for (i = 0; i < nloops; i++)
+ {
+ float val;
+
+/*
+ * First compute the sine wave (-1.0 to 1.0) and scale it to the right
+ * level. Finally sum the sample with the earlier voices in the buffer.
+ */
+ val = sin (v->phase) * 1024.0 * v->envelope * v->volume;
+ buf[i] += (short) val;
+
+/*
+ * Increase the phase angle for the next sample.
+ */
+ v->phase += v->step;
+
+/*
+ * Handle envelope attack or decay
+ */
+ switch (v->envelopedir)
+ {
+ case 1:
+ v->envelope += v->envelopestep;
+ if (v->envelope >= 1.0) /* Full level ? */
+ {
+ v->envelope = 1.0;
+ v->envelopestep = 0.0;
+ v->envelopedir = 0;
+ }
+ break;
+
+ case -1:
+ v->envelope += v->envelopestep;
+ if (v->envelope <= 0.0) /* Decay done */
+ {
+ v->envelope = 0.0;
+ v->envelopestep = 0.0;
+ v->envelopedir = 0;
+ v->active = 0; /* Shut up */
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * The midi_callback() function is called by the midi parser library when
+ * a complete MIDI message is seen in the input. The MIDI message number
+ * (lowest 4 bits usually set to zero), the channel (0-15), as well as the
+ * remaining bytes will be passed in the parameters.
+ *
+ * The MIDI parser library will handle oddities (like running status
+ * or use of note on with velocity of 0 as note off) so the application
+ * doesn't need to care about such nasty things.
+ *
+ * Note that the MIDI percussion channel 10 (9 as passed in the ch parameter)
+ * will be ignored. All other MIDI messages other than note on, note off
+ * and the "all notes off" controller are simply ignored.
+ *
+ * Macros like MIDI_NOTEON and MIDI_NOTEOFF are defined in soundcard.h.
+ */
+
+static void
+midi_callback (void *context, int category, unsigned char msg,
+ unsigned char ch, unsigned char *parms, int len)
+{
+ switch (msg)
+ {
+ case MIDI_NOTEON:
+ if (ch != 9) /* Avoid percussions */
+ note_on (ch, parms[0], parms[1]);
+ break;
+
+ case MIDI_NOTEOFF:
+ if (ch != 9) /* Avoid percussions */
+ note_off (ch, parms[0], parms[1]);
+ break;
+
+ case MIDI_CTL_CHANGE:
+ if (parms[0] == 123)
+ all_notes_off (ch);
+ break;
+
+ }
+}
+
+/*
+ * The handle_midi_input() routine reads all the MIDI input bytes
+ * that have been received by OSS since the last read. Note that
+ * this read will not block.
+ *
+ * Finally the received buffer is sent to the midi parser library which in turn
+ * calls midi_callback (see above) to handle the actual events.
+ */
+
+static void
+handle_midi_input (void)
+{
+ unsigned char buffer[256];
+ int l, i;
+
+ if ((l = read (midi_fd, buffer, sizeof (buffer))) == -1)
+ {
+ perror ("MIDI read");
+ exit (-1);
+ }
+
+ if (l > 0)
+ midiparser_input_buf (parser, buffer, l);
+}
+
+/*
+ * handle_audio_output() computes a new block of audio and writes it to the
+ * audio device. As you see there is no checking for blocking or available
+ * buffer space because it's simply not necessary with OSS 4.0 any more.
+ * If there is any blocking then the time below our "tolerances".
+ */
+
+static void
+handle_audio_output (void)
+{
+/*
+ * Ideally the buffer size equals to the fragment size (in samples).
+ * Using different sizes is not a big mistake but the granularity is
+ * defined by the buffer size or the fragment size (depending on which
+ * one is larger),
+ */
+ short buf[256];
+ int i;
+
+ memset (buf, 0, sizeof (buf));
+
+ /* Loop all the active voices */
+ for (i = 0; i < MAX_VOICES; i++)
+ if (voices[i].active)
+ compute_voice (&voices[i], buf, sizeof (buf) / sizeof (*buf));
+
+ if (write (audio_fd, buf, sizeof (buf)) == -1)
+ {
+ perror ("Audio write");
+ exit (-1);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ fd_set readfds, writefds;
+/*
+ * Use /dev/dsp as the default device because the system administrator
+ * may select the device using the {!xlink ossctl} program or some other
+ * methods
+ */
+ char *audiodev_name;
+ char *mididev_name;
+
+/*
+ * It's recommended to provide some method for selecting some other
+ * device than the default. We use command line argument but in some cases
+ * an environment variable or some configuration file setting may be better.
+ */
+ if (argc != 3)
+ {
+ fprintf (stderr, "Usage: %s audio_device midi_device\n", argv[0]);
+ exit (-1);
+ }
+
+ audiodev_name = argv[1];
+ mididev_name = argv[2];
+
+/*
+ * It's mandatory to use O_WRONLY in programs that do only playback. Other
+ * modes may cause increased resource (memory) usage in the driver. It may
+ * also prevent other applications from using the same device for
+ * recording at the same time.
+ */
+ audio_fd = open_audio_device (audiodev_name, O_WRONLY);
+
+/*
+ * Open the MIDI device for read access (only).
+ */
+
+ midi_fd = open_midi_device (mididev_name, O_RDONLY);
+
+/*
+ * Init the MIDI input parser (from OSSlib)
+ */
+
+ if ((parser = midiparser_create (midi_callback, NULL)) == NULL)
+ {
+ fprintf (stderr, "Creating a MIDI parser failed\n");
+ exit (-1);
+ }
+
+/*
+ * Then the select loop. This program uses select instead of poll. However
+ * you can use select if you like (it should not matter).
+ *
+ * The logic is very simple. Wait for MIDI input and audio output events.
+ * If there is any MIDI input then handle it (by modifying the voices[]
+ * array.
+ *
+ * When there is space to write more audio data then we simply compute one
+ * block of output and write it to the device.
+ */
+
+ while (1) /* Infinite loop */
+ {
+ int i, n;
+
+ FD_ZERO (&readfds);
+ FD_ZERO (&writefds);
+
+ FD_SET (audio_fd, &writefds);
+ FD_SET (midi_fd, &readfds);
+
+ if ((n = select (midi_fd + 1, &readfds, &writefds, NULL, NULL)) == -1)
+ {
+ perror ("select");
+ exit (-1);
+ }
+
+ if (n > 0)
+ {
+ if (FD_ISSET (midi_fd, &readfds))
+ handle_midi_input ();
+ if (FD_ISSET (audio_fd, &writefds))
+ handle_audio_output ();
+ }
+ }
+
+/*
+ * You may wonder what do we do between the songs. The answer is nothing.
+ * The note off messages (or the all notes off controller) takes care of
+ * shutting up the voices. When there are no voices playing the application
+ * will just output silent audio (until it's killed). So there is no need to
+ * know if a song has ended.
+ *
+ * However the MIDI loopback devices will retgurn a MIDI stop (0xfc) message
+ * when the client side is closed and a MIDI start (0xfa) message when some
+ * application starts playing. The server side application (synth) can
+ * use these events for it's purposes.
+ */
+
+/*
+ * That's all folks!
+ *
+ * This is pretty much all of it. This program can be easily improced by
+ * using some more advanced synthesis algorithm (wave table, sample playback,
+ * physical modelling or whatever else) and by interpreting all the MIDI
+ * messages. You can also add a nice GUI. You have complete freedom to
+ * modify this program and distribute it as your own work (under GPL, BSD
+ * proprietary or whatever license you can imagine) but only AS LONG AS YOU
+ * DON*T DO ANY STUPID CHANGES THAT BREAK THE RELIABILITY AND ROBUSTNESS.
+ *
+ * The point is that regardless of what you do there is no need to touch the
+ * audio/MIDI device related parts. They are already "state of the art".
+ * So you can spend all your time to work on the "payload" code. What you
+ * can do is changing the compute_voice() and midi_callback() routines and
+ * everything called by them.
+ */
+
+ exit (0);
+}
diff --git a/tutorials/sndkit/softsynth/softsynth_gtk.c b/tutorials/sndkit/softsynth/softsynth_gtk.c
new file mode 100644
index 0000000..7f9d6ac
--- /dev/null
+++ b/tutorials/sndkit/softsynth/softsynth_gtk.c
@@ -0,0 +1,869 @@
+/*
+ * Purpose: A simple software MIDI synthesizer program with GTK GUI.
+ * Copyright (C) 4Front Technologies, 2002-2004. Released in public domain.
+ *
+ * Description:
+ *
+ * This is an expanded version of the {!nlink softsynth.c} program
+ * (please take a look at the original program first). Also this program
+ * is more or less just an programming example that demontstrates how to
+ * implement "real-time" GUI controls in the simpliest possible way. While
+ * you can use this program as a program template, you may equally well start
+ * from the original one.
+ *
+ * Like the original there is not much to learn about synthesis in this program
+ * the only design goal has been to demonstrate some additional OSS MIDI
+ * features and to work as a test bed for them.
+ *
+ * The primary thing that has been done is replacing the original select() loop
+ * with a GTK (actually GDK) compatible mechanism (gdk_add_input and
+ * gtk_main). This is done in the main program.
+ *
+ * There are few other added features that should be mentioned:
+ *
+ * 1) The "Enhance switch" that makes the sound slightly "different" by
+ * adding some harmonics of the original note frequency. The sound is just
+ * different (not necessarily better). This together with the "Mute" button
+ * demonstrates how to do real-time adjustment of the settings.
+ * 2) The "mute" button was added just to be able to check if the latencies
+ * are audible or not.
+ * 3) The new SNDCTL_SETNAME call is used to change the description of the
+ * MIDI device. In this way the user of the client program can select the
+ * right device much easier.
+ * 4) The SNDCTL_GETSONG call is used to obtain the song name given
+ * by the client (ossmplay does this using SNDCTL_SETSONG).
+ * 5) Some code has been added to handle the MIDI timer start (0xfa) and
+ * stop (0xfc) messages. WIth loopback devices these messages can be
+ * used to find out when the client closed/opened the device. However if
+ * the server starts in the middle of a song there will be no start message.
+ *
+ * To use this program you will need to install the "4Front MIDI loopback"
+ * driver using the "Add new card/device" function of soundconf.
+ * Then start this program in background (the audio and MIDI device names
+ * can be given as command line arguments. For example
+ *
+ * softsynth /dev/dsp /dev/midi01
+ *
+ * You can find out the loopback MIDI device number by looking for the
+ * "MIDI loopback server side" devices using the {!xlink ossinfo} -m
+ * command. Btw, nothing prevents using any "real" physical MIDI port as the
+ * input.
+ *
+ * When the synthesizer server is running you can play any MIDI file using
+ * some OSS based MIDI sequencer/player such as {!xlink ossmplay}.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/select.h>
+#include <sys/soundcard.h>
+#include <midiparser.h>
+midiparser_common_t *parser = NULL;
+
+int audio_fd;
+int midi_fd;
+int sample_rate = 48000;
+int new_song = 1;
+
+int enhanced_mode = 0;
+float main_vol = 1.0;
+
+/*
+ * The open_audio_device routine opens the audio device and initializes it
+ * for the required mode. This code was borrowed directly from the
+ * {!nlink singen.c} sample program. However since the buffer size
+ * is inportant with this kind of application we have added a call that
+ * sets the fragment and buffer sizes.
+ */
+
+static int
+open_audio_device (char *name, int mode)
+{
+ int tmp, fd;
+
+ if ((fd = open (name, mode, 0)) == -1)
+ {
+ perror (name);
+ exit (-1);
+ }
+
+/*
+ * Setup the audio buffering policy so that reasonably small latencies
+ * can be obtained.
+ *
+ * 4 fragments of 256 samples (512 bytes) might be good. 256 samples
+ * will give timing granularity of 256/sample_rate seconds (5.33 msec)
+ * which is fairly adequate. The effect of the granularity (fragment size) in
+ * this type of applications is timing jitter (or choking). Every event that
+ * occurs withing the 5.33 msec period (fragment time) will get executed
+ * in the beginning of the next period. If the fragment size is decreased
+ * then the granularity will decrease too. However this will cause slight
+ * increase in the CPU consumption of the application.
+ *
+ * The total buffer size (number_of_fragments*fragment_time) will define the
+ * maximum latency between the event (note on/off) and the actual change in the
+ * audio output. The average latency will be something like
+ * (number_of_fragments-0.5)*fragment_time). The theoretical average latency
+ * caused by this application is (4-0.5)*5.33 msec = ~19 msec).
+ *
+ * In musical terms 5.33 msec granularity equals to 1/750 note at 60 bpm
+ * and 19 msecs equals to 1/214. This should be pretty adequate.
+ *
+ * The latency can be decreased by limiting the number of fragments and/or the
+ * fragment size. However the after the buffer size drops close to the
+ * capabilities of the system (delays caused by the other applications) the
+ * audio output will start breaking. This can cured only by tuning the
+ * hardware and the software environments (tuning some kernel parameters and
+ * by killing all the other applications). However this is in no way an OSS
+ * issue.
+ *
+ * With these parameters it was possible to compile Linux kernel in another
+ * terminal window without any hiccup (fairly entry level 2.4 GHz P4 system
+ * running Linux 2.6.x).
+ */
+ tmp = 0x00040009;
+ if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
+ {
+ perror ("SNDCTL_DSP_SETFRAGMENT");
+ }
+
+/*
+ * Setup the device. Note that it's important to set the
+ * sample format, number of channels and sample rate exactly in this order.
+ * Some devices depend on the order.
+ */
+
+/*
+ * Set the sample format
+ */
+ tmp = AFMT_S16_NE; /* Native 16 bits */
+ if (ioctl (fd, SNDCTL_DSP_SETFMT, &tmp) == -1)
+ {
+ perror ("SNDCTL_DSP_SETFMT");
+ exit (-1);
+ }
+
+ if (tmp != AFMT_S16_NE)
+ {
+ fprintf (stderr,
+ "The device doesn't support the 16 bit sample format.\n");
+ exit (-1);
+ }
+
+/*
+ * Set the number of channels (mono)
+ */
+ tmp = 1;
+ if (ioctl (fd, SNDCTL_DSP_CHANNELS, &tmp) == -1)
+ {
+ perror ("SNDCTL_DSP_CHANNELS");
+ exit (-1);
+ }
+
+ if (tmp != 1)
+ {
+ fprintf (stderr, "The device doesn't support mono mode.\n");
+ exit (-1);
+ }
+
+/*
+ * Set the sample rate
+ */
+ sample_rate = 48000;
+ if (ioctl (fd, SNDCTL_DSP_SPEED, &sample_rate) == -1)
+ {
+ perror ("SNDCTL_DSP_SPEED");
+ exit (-1);
+ }
+
+/*
+ * No need for rate checking because we will automatically adjust the
+ * signal based on the actual sample rate. However most application must
+ * check the value of sample_rate and compare it to the requested rate.
+ *
+ * Small differences between the rates (10% or less) are normal and the
+ * applications should usually tolerate them. However larger differences may
+ * cause annoying pitch problems (Mickey Mouse).
+ */
+
+ return fd;
+}
+
+static int
+open_midi_device (char *name, int mode)
+{
+ int tmp, fd;
+
+/*
+ * This is pretty much all we nbeed.
+ */
+
+ if ((fd = open (name, mode, 0)) == -1)
+ {
+ perror (name);
+ exit (-1);
+ }
+
+ return fd;
+}
+
+/*
+ ************** Some GTK+ related routines **********************
+ */
+#include <gtk/gtk.h>
+
+GtkWidget *main_window, *song_name, *time_code;
+
+static void
+close_all (GtkWidget * window, gpointer data)
+{
+ gtk_main_quit ();
+ exit (-1);
+}
+
+/*
+ * toggle_enhance() gets called when the "Enhance" button is hit. It just
+ * changes the state of the enhanced_mode flag which is used by the
+ * note_on() routine. This setting affects only the notes to be started
+ * after the change. It doesn't affect any of the currently playing voices.
+ */
+
+static void
+toggle_enhance (GtkWidget * window, gpointer data)
+{
+ enhanced_mode = !enhanced_mode; /* ON <-> OFF */
+}
+
+/*
+ * toggle_mute() handles the "Mute" button. The change will take effect
+ * at the moment when the next audio block to be computed starts playing
+ * on the device. So this button can be used to check how long the total
+ * latencies are (including any delays caused by device level FIFOs).
+ */
+
+static void
+toggle_mute (GtkWidget * window, gpointer data)
+{
+ if (main_vol > 0)
+ main_vol = 0.0;
+ else
+ main_vol = 1.0;
+}
+
+static void
+update_song_name (void)
+{
+ oss_longname_t name;
+ char tmp[256];
+
+/*
+ * Get the song name from the client and update the label. Notice that
+ * SNDCTL_GETSONG will return error (EINVAL) if the device doesn't
+ * support song names. This is not an error. It simple means that no
+ * song information is available. The song name may also be an empty
+ * string if the client has not registered any song name. Also this is
+ * perfectly normal.
+ *
+ * The difference between EINVAL and an empty string (if it matters) is that
+ * EINVAL means that the device will not return this info later (the
+ * application may stop polling for it).
+ */
+
+ if (ioctl (midi_fd, SNDCTL_GETSONG, name) != -1)
+ {
+ if (*name == 0)
+ strcpy (name, "Unknown song");
+ sprintf (tmp, "Song: %s", name);
+ gtk_label_set (GTK_LABEL (song_name), tmp);
+
+ /* Forward the song name to the audio device too */
+ ioctl (audio_fd, SNDCTL_SETSONG, name);
+ }
+
+ new_song = 0;
+}
+
+/*
+ * The create_user_interface() routine is pretty much a standard
+ * GUI initialization for any GTK based application. Not important
+ * for the logic of this program. We just create some buttons and one
+ * label and assign the callbacks to handle them.
+ */
+
+static void
+create_user_interface (void)
+{
+ GtkWidget *button, *vbox;
+ main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_signal_connect (GTK_OBJECT (main_window),
+ "destroy", GTK_SIGNAL_FUNC (close_all), NULL);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (main_window), vbox);
+
+ song_name = gtk_label_new ("Song: Unknown song");
+ gtk_box_pack_start (GTK_BOX (vbox), song_name, FALSE, FALSE, 0);
+ gtk_widget_show (song_name);
+
+ time_code = gtk_label_new ("Song: Unknown song");
+ gtk_box_pack_start (GTK_BOX (vbox), time_code, FALSE, FALSE, 0);
+ gtk_widget_show (time_code);
+
+ update_song_name ();
+
+ button = gtk_check_button_new_with_label ("Mute");
+ gtk_signal_connect (GTK_OBJECT (button),
+ "clicked", GTK_SIGNAL_FUNC (toggle_mute), NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gtk_check_button_new_with_label ("Enhance");
+ gtk_signal_connect (GTK_OBJECT (button),
+ "clicked", GTK_SIGNAL_FUNC (toggle_enhance), NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ button = gtk_button_new_with_label ("Exit");
+ gtk_signal_connect (GTK_OBJECT (button),
+ "clicked", GTK_SIGNAL_FUNC (close_all), NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gtk_widget_show (vbox);
+ gtk_widget_show (main_window);
+}
+
+/*
+ ************** The actual synthesis engine *********************
+ */
+
+#define MAX_VOICES 256
+
+typedef struct
+{
+ int active; /* ON/OFF */
+ int chn, note, velocity; /* MIDI note parameters */
+
+ float phase, step; /* Sine frequency generator */
+
+ float volume; /* Note volume */
+
+ float envelope, envelopestep; /* Envelope generator */
+ int envelopedir; /* 0=fixed level, 1=attack, -1=decay */
+} voice_t;
+
+static voice_t voices[MAX_VOICES] = { 0 };
+
+static int
+note_to_freq (int note_num)
+{
+
+/*
+ * This routine converts a midi note to a frequency (multiplied by 1000)
+ * Notice! This routine was copied from the OSS sequencer code.
+ */
+
+ int note, octave, note_freq;
+ static int notes[] = {
+ 261632, 277189, 293671, 311132, 329632, 349232,
+ 369998, 391998, 415306, 440000, 466162, 493880
+ };
+
+#define BASE_OCTAVE 5
+
+ octave = note_num / 12;
+ note = note_num % 12;
+
+ note_freq = notes[note];
+
+ if (octave < BASE_OCTAVE)
+ note_freq >>= (BASE_OCTAVE - octave);
+ else if (octave > BASE_OCTAVE)
+ note_freq <<= (octave - BASE_OCTAVE);
+
+ return note_freq;
+}
+
+/*
+ * fork_voice() creates another instance of a voice (just like the
+ * fork system call does). It's possible to change the pitch and volume
+ * by setting the freq_ratio and vol_scale parameters to values below 1.0.
+ */
+static void
+fork_voice (voice_t * orig, float freq_ratio, float vol_scale)
+{
+ int i;
+
+ for (i = 0; i < MAX_VOICES; i++)
+ if (!voices[i].active)
+ {
+ voice_t *v = &voices[i];
+
+ memcpy (v, orig, sizeof (voice_t));
+ v->step /= freq_ratio;
+ v->volume *= vol_scale;
+ return;
+ }
+}
+
+/*
+ * The note_on() routine initializes a voice with the right
+ * frequency, volume and envelope parameters.
+ */
+
+static void
+note_on (int ch, int note, int velocity)
+{
+ int i;
+
+ for (i = 0; i < MAX_VOICES; i++)
+ if (!voices[i].active)
+ {
+ voice_t *v = &voices[i];
+ int freq;
+ float step;
+
+ /*
+ * Record the MIDI note on message parameters (just in case)
+ */
+
+ v->chn = ch;
+ v->note = note;
+ v->velocity = velocity;
+
+ /*
+ * Convert the note number to the actual frequency (multiplied by 1000).
+ * Then compute the step to be added to the phase angle to get the right
+ * frequency.
+ */
+
+ freq = note_to_freq (note);
+ step = 1000.0 * (float) sample_rate / (float) freq; /* Samples/cycle */
+ v->step = 2.0 * M_PI / step;
+ if (v->step > M_PI) /* Nyqvist was here */
+ return;
+ v->phase = 0.0;
+
+ /*
+ * Compute the note volume based on the velocity. Use linear scale which
+ * maps velocity=0 to the 25% volume level. Proper synthesizers will use more
+ * advanced methods (such as logarithmic scales) but this is good for our
+ * purposes.
+ */
+ v->volume = 0.25 + ((float) velocity / 127.0) * 0.75;
+
+ /*
+ * Initialize the envelope engine to start from zero level and to add
+ * some fixed amount to the envelope level after each sample.
+ */
+ v->envelope = 0.0;
+ v->envelopedir = 1;
+ v->envelopestep = 0.01;
+
+ /*
+ * Fire the voice. However nothing will happen before the next audio
+ * period (fragment) gets computed. This means that all the voices started
+ * during the ending period will be rounded to start at the same moment.
+ */
+ v->active = 1;
+
+ if (enhanced_mode)
+ {
+/*
+ * Stupid test that adds some harmonic frequencies. This makes the output
+ * to sound bolder. This algorithm is called additive synthesis. However
+ * this program is not the best possible one for learning that technique.
+ */
+ fork_voice (v, 1.001, 0.9); /* Add some beating */
+ fork_voice (v, 2.0, 0.1);
+ fork_voice (v, 3.0, 0.2);
+ fork_voice (v, 4.0, 0.02);
+ fork_voice (v, 6.0, 0.01);
+ fork_voice (v, 8.0, 0.01);
+ }
+ break;
+ }
+}
+
+/*
+ * The note_off() routine finds all the voices that have matching channel and
+ * note numbers. Then it starts the envelope decay phase (10 times slower
+ * than the attack phase.
+ */
+
+static void
+note_off (int ch, int note, int velocity)
+{
+ int i;
+
+ for (i = 0; i < MAX_VOICES; i++)
+ if (voices[i].active && voices[i].chn == ch)
+ if (voices[i].note = note)
+ {
+ voice_t *v = &voices[i];
+ v->envelopedir = -1;
+ v->envelopestep = -0.001;
+ }
+}
+
+/*
+ * all_notes_off() is a version of note_off() that checks only the channel
+ * number. Used for the All Notes Off MIDI controller (123).
+ */
+
+static void
+all_notes_off (int ch)
+{
+ int i;
+
+ for (i = 0; i < MAX_VOICES; i++)
+ if (voices[i].active && voices[i].chn == ch)
+ {
+ voice_t *v = &voices[i];
+ v->envelopedir = -1;
+ v->envelopestep = -0.01;
+ }
+}
+
+/*
+ * all_voices_off() mutes all voices immediately.
+ */
+
+static void
+all_voices_off (int ch)
+{
+ int i;
+
+ for (i = 0; i < MAX_VOICES; i++)
+ if (voices[i].active && voices[i].chn == ch)
+ {
+ voice_t *v = &voices[i];
+
+ v->active = 0;
+ }
+}
+
+/*
+ * Compute voice computes few samples (nloops) and sums them to the
+ * buffer (that contains the sum of all previously computed voices).
+ *
+ * In real world applications it may be necessary to convert this routine to
+ * use floating point buffers (-1.0 to 1.0 range) and do the conversion
+ * to fixed point only in the final output stage. Another change you may
+ * want to do is using multiple output buffers (for stereo or multiple
+ * channels) instead of the current mono scheme.
+ *
+ * For clarity reasons we have not done that.
+ */
+
+static void
+compute_voice (voice_t * v, short *buf, int nloops)
+{
+ int i;
+
+ for (i = 0; i < nloops; i++)
+ {
+ float val;
+
+/*
+ * First compute the sine wave (-1.0 to 1.0) and scale it to the right
+ * level. Finally sum the sample with the earlier voices in the buffer.
+ */
+ val = sin (v->phase) * 1024.0 * v->envelope * v->volume * main_vol;
+ buf[i] += (short) val;
+
+/*
+ * Increase the phase angle for the next sample.
+ */
+ v->phase += v->step;
+
+/*
+ * Handle envelope attack or decay
+ */
+ switch (v->envelopedir)
+ {
+ case 1:
+ v->envelope += v->envelopestep;
+ if (v->envelope >= 1.0) /* Full level ? */
+ {
+ v->envelope = 1.0;
+ v->envelopestep = 0.0;
+ v->envelopedir = 0;
+ }
+ break;
+
+ case -1:
+ v->envelope += v->envelopestep;
+ if (v->envelope <= 0.0) /* Decay done */
+ {
+ v->envelope = 0.0;
+ v->envelopestep = 0.0;
+ v->envelopedir = 0;
+ v->active = 0; /* Shut up */
+ }
+ break;
+ }
+ }
+}
+
+/*
+ *********** Handling of OSS MIDI input and audio output ***********
+ */
+
+/*
+ * The midi_callback() function is called by the midi parser library when
+ * a complete MIDI message is seen in the input. The MIDI message number
+ * (lowest 4 bits usually set to zero), the channel (0-15), as well as the
+ * remaining bytes will be passed in the parameters.
+ *
+ * The MIDI parser library will handle oddities (like running status
+ * or use of note on with velocity of 0 as note off) so the application
+ * doesn't need to care about such nasty things.
+ *
+ * Note that the MIDI percussion channel 10 (9 as passed in the ch parameter)
+ * will be ignored. All other MIDI messages other than note on, note off
+ * and the "all notes off" controller are simply ignored.
+ *
+ * Macros like MIDI_NOTEON and MIDI_NOTEOFF are defined in soundcard.h.
+ */
+
+static void
+midi_callback (void *context, int category, unsigned char msg,
+ unsigned char ch, unsigned char *parms, int len)
+{
+ switch (msg)
+ {
+ case MIDI_NOTEON:
+ if (new_song)
+ update_song_name ();
+
+ if (ch != 9) /* Avoid percussions */
+ note_on (ch, parms[0], parms[1]);
+ break;
+
+ case MIDI_NOTEOFF:
+ if (ch != 9) /* Avoid percussions */
+ note_off (ch, parms[0], parms[1]);
+ break;
+
+ case MIDI_CTL_CHANGE:
+ if (parms[0] == 120)
+ all_voices_off (ch);
+ if (parms[0] == 123)
+ all_notes_off (ch);
+ break;
+
+ case 0xfa: /* Start */
+ /*
+ * Note that the start message arrives at the moment when the
+ * client side of the loopback device is opened. At that moment
+ * the client has not updated the song name so we should
+ * not try to read it immediately. Instead we have to do it
+ * (for example) at the moment the first note is started.
+ */
+ new_song = 1;
+ break;
+
+ case 0xfc: /* Stop */
+ /*
+ * The stop message arrives after the client side of the
+ * loopback device has been closed. We will just re-draw
+ * the song name (to clear the display field on the screen).
+ */
+ update_song_name ();
+ break;
+
+ }
+}
+
+/*
+ * The handle_midi_input() routine reads all the MIDI input bytes
+ * that have been received by OSS since the last read. Note that
+ * this read will not block.
+ *
+ * Finally the received buffer is sent to the midi parser library which in turn
+ * calls midi_callback (see above) to handle the actual events.
+ */
+
+static void
+handle_midi_input (gpointer data, gint source, GdkInputCondition cond)
+{
+ unsigned char buffer[256];
+ int l, i;
+
+ if ((l = read (midi_fd, buffer, sizeof (buffer))) == -1)
+ {
+ perror ("MIDI read");
+ exit (-1);
+ }
+
+ if (l > 0)
+ midiparser_input_buf (parser, buffer, l);
+}
+
+/*
+ * handle_audio_output() computes a new block of audio and writes it to the
+ * audio device. As you see there is no checking for blocking or available
+ * buffer space because it's simply not necessary with OSS 4.0 any more.
+ * If there is any blocking then the time below our "tolerances".
+ */
+
+static void
+handle_audio_output (gpointer data, gint source, GdkInputCondition cond)
+{
+/*
+ * Ideally the buffer size equals to the fragment size (in samples).
+ * Using different sizes is not a big mistake but the granularity is
+ * defined by the buffer size or the fragment size (depending on which
+ * one is larger),
+ */
+ short buf[256];
+ int i;
+
+ memset (buf, 0, sizeof (buf));
+
+ /* Loop all the active voices */
+ for (i = 0; i < MAX_VOICES; i++)
+ if (voices[i].active)
+ compute_voice (&voices[i], buf, sizeof (buf) / sizeof (*buf));
+
+ if (write (audio_fd, buf, sizeof (buf)) == -1)
+ {
+ perror ("Audio write");
+ exit (-1);
+ }
+}
+
+/*
+ * The mtc_callback() routine updates the SMTPE/MTC time display on the
+ * screen. The quarter frames (qframes) field is not shown.
+ */
+
+static void
+mtc_callback (void *context, oss_mtc_data_t * mtc)
+{
+ char tmp[64];
+
+ if (mtc->qframes != 0)
+ return;
+
+ sprintf (tmp, "%02d:%02d:%02d.%02d\n",
+ mtc->hours, mtc->minutes, mtc->seconds, mtc->frames);
+ gtk_label_set (GTK_LABEL (time_code), tmp);
+}
+
+/*
+ * Finally the main program
+ */
+
+int
+main (int argc, char *argv[])
+{
+ fd_set readfds, writefds;
+/*
+ * Use /dev/dsp as the default device because the system administrator
+ * may select the device using the {!xlink ossctl} program or some other
+ * methods
+ */
+ char *audiodev_name;
+ char *mididev_name;
+
+ int tmp;
+
+/*
+ * It's recommended to provide some method for selecting some other
+ * device than the default. We use command line argument but in some cases
+ * an environment variable or some configuration file setting may be better.
+ */
+ if (argc != 3)
+ {
+ fprintf (stderr, "Usage: %s audio_device midi_device\n", argv[0]);
+ exit (-1);
+ }
+
+ audiodev_name = argv[1];
+ mididev_name = argv[2];
+
+/*
+ * It's mandatory to use O_WRONLY in programs that do only playback. Other
+ * modes may cause increased resource (memory) usage in the driver. It may
+ * also prevent other applications from using the same device for
+ * recording at the same time.
+ */
+ audio_fd = open_audio_device (audiodev_name, O_WRONLY);
+
+/*
+ * Open the MIDI device for read access (only).
+ */
+
+ midi_fd = open_midi_device (mididev_name, O_RDONLY);
+
+/*
+ * Request input of MTC time (25 FPS). This is just for fun.
+ */
+ tmp = 25;
+ ioctl (midi_fd, SNDCTL_MIDI_MTCINPUT, &tmp);
+
+/*
+ * Report the server name to the client side. This name will be reported
+ * by applications that check the device names. It will also be shown
+ * in /dev/sndstat
+ *
+ * SNDCTL_SETNAME is a new ioctl call in OSS 4.0. It doesn't make any
+ * sense to do error checking with it.
+ */
+
+ ioctl (midi_fd, SNDCTL_SETNAME, "OSS user land synth demo");
+
+/*
+ * Init the MIDI input parser (from OSSlib)
+ */
+
+ if ((parser = midiparser_create (midi_callback, NULL)) == NULL)
+ {
+ fprintf (stderr, "Creating a MIDI parser failed\n");
+ exit (-1);
+ }
+
+/*
+ * Register the MTC timecode handler
+ */
+ midiparser_mtc_callback (parser, mtc_callback);
+
+/*
+ * Standard GTK+ initialization.
+ */
+
+ gtk_init (&argc, &argv);
+
+ create_user_interface ();
+
+ gdk_input_add (audio_fd, GDK_INPUT_WRITE, handle_audio_output, NULL);
+ gdk_input_add (midi_fd, GDK_INPUT_READ, handle_midi_input, NULL);
+
+ gtk_main ();
+
+/*
+ * That's all folks!
+ *
+ * This is pretty much all of it. This program can be easily improced by
+ * using some more advanced synthesis algorithm (wave table, sample playback,
+ * physical modelling or whatever else) and by interpreting all the MIDI
+ * messages. You can also add a nice GUI. You have complete freedom to
+ * modify this program and distribute it as your own work (under GPL, BSD
+ * proprietary or whatever license you can imagine) but only AS LONG AS YOU
+ * DON*T DO ANY STUPID CHANGES THAT BREAK THE RELIABILITY AND ROBUSTNESS.
+ *
+ * The point is that regardless of what you do there is no need to touch the
+ * audio/MIDI device related parts. They are already "state of the art".
+ * So you can spend all your time to work on the "payload" code. What you
+ * can do is changing the compute_voice() and midi_callback() routines and
+ * everything called by them.
+ */
+
+ exit (0);
+}