/*
 * Purpose: MIDI support.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

/*
 * NOTE! This is not the development version of this file. Please download the
 * sources of the latest Open Sound System version from 
 * http://developer.opensound.com/sources if you plan to make any changes
 * to the software.
 */

#include "oss_config.h"
#include "midi_core.h"

#define MDB(x)

oss_mutex_t midi_mutex;
static oss_device_t *osscore_osdev = NULL;

/*
 * List of MIDI devices.
 */
mididev_t **midi_devs = NULL;
int num_mididevs = 0;

/*
 * List of MIDI clients (/dev/midi* device files).
 */
int oss_num_midi_clients = 0;
oss_midi_client_t *oss_midi_clients[MAX_MIDI_CLIENTS] = { NULL };

static int
queue_midi_input (mididev_t * mididev, unsigned char *data, int len,
		  oss_native_word * wait_flags)
{
  int ret;

  midi_packet_header_t hdr, *hdrp = NULL;

  if (mididev->working_mode != MIDI_MODE_TRADITIONAL)
    {
      int use_abs = 0;

      hdrp = &hdr;

      memset (&hdr, 0, sizeof (hdr));
      hdr.magic = MIDI_HDR_MAGIC;
      hdr.event_type = MIDI_EV_WRITE;
      hdr.options |= MIDI_OPT_TIMED;

      if (mididev->working_mode == MIDI_MODE_TIMED_ABS)
	{
	  use_abs = 1;
	  hdr.options |= MIDI_OPT_USECTIME;
	}

      hdr.time =
	oss_timer_get_current_time (mididev->timer_dev, mididev->latency,
				    use_abs);
    }

  if ((ret = midi_queue_put (mididev->in_queue, data, len, hdrp)) < 0)
    {
      if (len == 1)
	cmn_err (CE_CONT, "/dev/midi%02d: Input buffer overflow\n",
		 mididev->dev);
      ret = 0;
    }

  oss_wakeup (mididev->in_wq, &mididev->mutex, wait_flags, POLLIN);

  return ret;
}

static void
mtc_generator (void *d)
{
#if 1
  oss_native_word dev = (oss_native_word) d;
  oss_native_word flags;
  unsigned char data, buf[2];
  mididev_p mididev = midi_devs[dev];
  int t;
  int step;
  int h, m, s, f;

  t = (int) (GET_JIFFIES () - mididev->mtc_t0);

  if (mididev->mtc_timebase < 1)
    return;
  mididev->mtc_timeout_id =
    timeout (mtc_generator, (void *) dev, OSS_HZ / 100);

  step = OSS_HZ / (mididev->mtc_timebase * 4);	/* System ticks per quarter frame */
  if (step < 1)
    {
      /* System timer has too low resolution  so do nothing */
      return;
    }

  t /= step;
  if (t <= mididev->mtc_prev_t)	/* Not enough time elapsed yet (for some reason) */
    {
      return;
    }

  mididev->mtc_prev_t = t;

  if (mididev->mtc_phase == 0)
    mididev->mtc_current = t;
  else
    t = mididev->mtc_current;

  t /= 4;			/* Ignore quarter frames */
  f = t % mididev->mtc_timebase;
  t /= mididev->mtc_timebase;

  s = t % 60;
  t /= 60;

  m = t % 60;
  t /= 60;

  h = t % 0x24;

  data = 0x00;

  switch (mididev->mtc_phase)
    {
    case 0:
      data = 0x00 | (f % 0x0f);
      break;
    case 1:
      data = 0x10 | ((f >> 4) % 0x0f);
      break;
    case 2:
      data = 0x20 | (s % 0x0f);
      break;
    case 3:
      data = 0x30 | ((s >> 4) % 0x0f);
      break;
    case 4:
      data = 0x40 | (m % 0x0f);
      break;
    case 5:
      data = 0x50 | ((m >> 4) % 0x0f);
      break;
    case 6:
      data = 0x60 | (h % 0x0f);
      break;
    case 7:
      data = 0x70 | ((h >> 4) % 0x03) | (mididev->mtc_codetype << 1);
      break;
    }

  mididev->mtc_phase = (mididev->mtc_phase + 1) % 8;

#if 0
  cmn_err (CE_CONT, "MTC = %02d:%02d:%02d %02d.x -> %02x\n",
	   h, m, s, f, data);
#endif

  MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);
  buf[0] = 0xf1;
  buf[1] = data;
  queue_midi_input (mididev, buf, 2, &flags);
  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
#endif
}

int
oss_midi_input_byte (int dev, unsigned char data)
{
  oss_native_word flags;
  mididev_t *mididev;
  int ret;

  if (dev < 0 || dev >= num_mididevs)
    cmn_err (CE_PANIC, "MIDI driver bug - bad MIDI device %d\n", dev);

  mididev = midi_devs[dev];

  if (data == 0xf1)
    {
      mididev->mtc_timebase = -1;
    }
  MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);
  ret = queue_midi_input (mididev, &data, 1, &flags);
  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);

  return ret;
}

int
oss_midi_input_buf (int dev, unsigned char *data, int len)
{
  oss_native_word flags;
  mididev_t *mididev;
  int ret, n = 0;

  if (dev < 0 || dev >= num_mididevs)
    cmn_err (CE_PANIC, "MIDI driver bug - bad MIDI device %d\n", dev);

  mididev = midi_devs[dev];

  MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);

  while (len > 0)
    {
      int i, l = len;

      if (l > MIDI_PAYLOAD_SIZE)
	l = MIDI_PAYLOAD_SIZE;


      for (i = 0; i < l; i++)
	{
	  /* Turn off MTC generation if MTC data is received */
	  if (data[i] == 0xf1)
	    {
	      mididev->mtc_timebase = -1;
	    }
	}
      ret = queue_midi_input (mididev, data, l, &flags);
      if (ret < 0)
	{
	  ret = 0;
	  cmn_err (CE_WARN, "MIDI input bytes dropped\n");
	}

      if (ret == 0)		/* Cannot queue more bytes */
	break;

      len -= ret;
      data += ret;
      n += ret;
    }

  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);

  return n;
}

static void
oss_midi_input_event (int dev, unsigned char *data, int len)
{
  oss_native_word flags;
  mididev_t *mididev;
  int ret, n = 0, i;

  if (dev < 0 || dev >= num_mididevs)
    cmn_err (CE_PANIC, "MIDI driver bug - bad MIDI device %d\n", dev);

  mididev = midi_devs[dev];

  MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);

  for (i = 0; i < len; i += 4)
    {
      int l;
      unsigned char *d = data + i;

      switch (d[0] & 0x0f)
	{
	case 0x0:		/* End marker */
	  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	  return;
	  break;

	case 0x2:		/* Two byte real time messages TODO? */
	  l = 2;
	  break;

	case 0x4:		/* Sysex start/continuation */
	  l = 3;
	  break;

	case 0x5:		/* Sysex termination records */
	case 0x6:
	case 0x7:
	  l = (d[0] & 0x0f) - 4;
	  break;

	case 0x8:
	case 0x9:
	case 0xa:
	case 0xb:
	case 0xe:
	  l = 3;
	  break;

	case 0x0c:
	case 0x0d:
	  l = 2;
	  break;

	case 0xf:		/* System real time messages */
	  l = 1;
	  break;

	default:
	  l = 3;
	}

      if (l == 0)		/* Nothing to send */
	continue;

      if (l > MIDI_PAYLOAD_SIZE)
	l = MIDI_PAYLOAD_SIZE;

      /*
       * Ignore active sensing
       */
      if (d[1] == 0xfe)
	continue;

      ret = queue_midi_input (mididev, d + 1, l, &flags);
      if (ret < 0)
	{
	  ret = 0;
	  cmn_err (CE_WARN, "MIDI input bytes dropped\n");
	}

      if (ret == 0)		/* Cannot queue more bytes */
	break;

      len -= ret;
      data += ret;
      n += ret;
    }

  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);

  return;
}

static void oss_midi_callback (void *arg);
static int setup_tempo (mididev_t * mididev);

void
oss_midi_copy_timer (int dev, int source_dev)
{
  mididev_t *mididev;
  int err;

  if (dev < 0 || dev >= num_mididevs)
    return;
  mididev = midi_devs[dev];

  if (mididev->timer_dev >= 0)	/* Detach the old client */
    oss_timer_detach_client (mididev->timer_dev, mididev);

  mididev->timer_dev = -1;

  if (source_dev < 0 || source_dev >= num_mididevs)
    return;

  if (midi_devs[source_dev]->timer_dev < 0)
    return;

  if ((err =
       oss_timer_attach_client (midi_devs[source_dev]->timer_dev,
				mididev)) < 0)
    {
      cmn_err (CE_CONT, "Cannot attach timer %d to MIDI dev %d, err=%d\n",
	       midi_devs[source_dev]->timer_dev, mididev->dev, err);
      return;
    }

  mididev->timer_dev = midi_devs[source_dev]->timer_dev;
  mididev->timer_driver = midi_devs[source_dev]->timer_driver;
}

int
oss_midi_output_intr_handler (int dev, int callback_flag)
{
  mididev_t *mididev;
  oss_native_word flags;
  oss_midi_time_t time;

  unsigned char *buf;
  int len;
  midi_packet_header_t *hdr;
  MDB (cmn_err (CE_CONT, "oss_midi_output_intr_handler()\n"));

  if (dev < 0 || dev >= num_mididevs)
    cmn_err (CE_PANIC, "oss_midi_output_intr: Bad device %d\n", dev);

  mididev = midi_devs[dev];

  if (callback_flag)
    {
      mididev->out_timeout = 0;
    }

  if (!(mididev->open_mode & OPEN_WRITE))	/* Device may have been closed */
    {
      return 0;
    }

  MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);

  while ((len = midi_queue_find_buffer (mididev->out_queue, &buf, &hdr)) >= 0)
/* TODO: Check time */
    {
      int i, n = 0;

      if (!callback_flag)	/* Called from write() handler */
	if (hdr != NULL && (hdr->options & MIDI_OPT_BUSY))
	  {
	    /*
	     * This buffer is already playing
	     */
	    MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	    return 0;
	  }

      MDB (cmn_err (CE_CONT, "Bytes in buffer=%d\n", len));
      if (buf != NULL)
	MDB (cmn_err (CE_CONT, "First=%02x\n", buf[0]));

      if (hdr == NULL)
	{
	  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	  return 0;
	}

      hdr->options |= MIDI_OPT_BUSY;
      if (mididev->timer_dev >= 0)
	if (hdr->options & MIDI_OPT_TIMED)
	  {
	    if (mididev->d->flush_output)
	      mididev->d->flush_output (mididev->dev);
	    MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	    /* TODO: Does releasing the mutex here cause any race conditions? */
	    if (oss_timer_wait_time
		(mididev->timer_dev, mididev->dev, mididev->latency,
		 hdr->time, oss_midi_callback, hdr->options) <= 0)
	      {
		/*
		 * Not the right time yet. Timer is armed
		 * and the callback function will be called
		 * at the right time. We don't remove the event
		 * so it can be retried later.
		 */
		return 0;
	      }
	    MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);
	  }

      switch (hdr->event_type)
	{
	case MIDI_EV_TEMPO:
	  mididev->tempo = hdr->parm;
	  setup_tempo (mididev);
	  break;

	case MIDI_EV_START:
	  oss_timer_start (mididev->timer_dev);
	  if (mididev->d->timer_setup)
	    mididev->d->timer_setup (mididev->dev);
	  break;

	case MIDI_EV_WRITE:
	  break;
	}

      if (mididev->d->outputc == NULL
	  || (len > 1 && mididev->d->bulk_write != NULL))
	{
	  n = mididev->d->bulk_write (mididev->dev, buf, len);
	  if (n <= 0)		/* Error */
	    {
	      MDB (cmn_err
		   (CE_NOTE, "MIDI device %d - output stalled (%d).\n",
		    mididev->dev, n));
	      n = 0;
	    }
	}
      else
	{
	  for (i = 0; i < len; i++)
	    {
	      MDB (cmn_err (CE_CONT, "Out %d %02x\n", mididev->dev, buf[i]));
	      if (!mididev->d->outputc (mididev->dev, buf[i]))
		{
		  MDB (cmn_err
		       (CE_NOTE, "MIDI device %d - output stalled (%d).\n",
			mididev->dev, n));
		  break;	/* device full */
		}
	      n++;
	    }
	}

      /* Remove the bytes sent out */
      if (n > 0 || len == 0)
	{
	  MDB (cmn_err (CE_CONT, "Remove %d/%d\n", n, len));
	  midi_queue_remove_chars (mididev->out_queue, n);
	  oss_wakeup (mididev->out_wq, &mididev->mutex, &flags, POLLOUT);
	}

      if (n == len)
	{
	  if (mididev->d->flush_output)
	    mididev->d->flush_output (mididev->dev);
	}

      if (n < len)
	{
	  /* Not everything consumed - arm a timeout */

	  MDB (cmn_err (CE_CONT, "Not all consumed %d/%d\n", n, len));
	  if (mididev->out_timeout == 0)	/* Not armed yet */
	    {
	      oss_native_word d = dev;
	      mididev->out_timeout =
		timeout (oss_midi_callback, (void *) d, 1);
	    }
	  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);

	  return 0;		/* Not all buffered bytes were sent */
	}
      MDB (cmn_err (CE_CONT, "Everything consumed\n"));
    }

  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);

  return 1;			/* Everything was done */
}

int
oss_midi_output_intr (int dev)
{
  MDB (cmn_err (CE_CONT, "oss_midi_output_intr\n"));
  oss_midi_output_intr_handler (dev, 0);
}

static void
oss_midi_callback (void *arg)
{
  int dev;

  dev = (oss_native_word) arg;

  MDB (cmn_err (CE_CONT, "MIDI timer callback\n"));
  oss_midi_output_intr_handler (dev, 1);
}

void
oss_midi_set_defaults (mididev_t * mididev)
{
  char *cmd;

  mididev->prech_timeout = 0;	/* Infinite wait */

  mididev->event_input = oss_midi_input_event;
  mididev->tempo = 120;
  mididev->timebase = OSS_HZ;
}

static void put_output (mididev_t * mididev, unsigned char *buf, int len);

int
oss_midi_open (int dev, int no_worries, struct fileinfo *file, int recursive,
	       int open_flags, int *newdev)
{
/*
 * oss_midi_open opens a MIDI client (/dev/midi##) and binds it directly to
 * the MIDI port with the same number (dev).
 */
  oss_native_word flags;
  oss_midi_client_t *client;
  int ok, err = -EBUSY;
  int mode = file->mode & O_ACCMODE;

  char *cmd;

  if (dev < 0 || dev >= num_mididevs)
    return -ENXIO;

  /* 
   * Don't allow read only access on playback only devices.
   * However R/W access should be allowed so that the application can be
   * informed about (possible) status changes.
   */
  if ((mode == OPEN_READ) && !(midi_devs[dev]->flags & MFLAG_INPUT))
    {
      return -EACCES;
    }

  /*
   * Input only devices cannot do output so don't allow it.
   */
  if ((mode & OPEN_WRITE) && !(midi_devs[dev]->flags & MFLAG_OUTPUT))
    {
      return -EACCES;
    }

  if (!midi_devs[dev]->enabled)
    return -ENXIO;

  if (midi_devs[dev]->unloaded)
    return -ENXIO;

  MUTEX_ENTER_IRQDISABLE (midi_mutex, flags);

  client = oss_midi_clients[dev];

  ok = 1;
  if (client->open_mode != 0)	/* Client busy */
    ok = 0;

  if (midi_devs[dev]->open_mode != 0)	/* Device busy */
    ok = 0;

  if ((cmd = GET_PROCESS_NAME (file)) != NULL)
    strncpy (client->mididev->cmd, cmd, 15);
  client->mididev->pid = GET_PROCESS_PID (file);

  if (ok)			/* Still OK */
    {
      client->open_mode = mode;
      midi_devs[dev]->open_mode = mode;
      /* Release locks before calling device open method */
      MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);
      if ((err = midi_devs[dev]->d->open (dev, mode,
					  oss_midi_input_byte,
					  oss_midi_input_buf,
					  oss_midi_output_intr)) < 0)
	ok = 0;
      /* Reacquire locks */
      MUTEX_ENTER_IRQDISABLE (midi_mutex, flags);
      if (!ok)
	{
	  client->open_mode = 0;
	  midi_devs[dev]->open_mode = 0;
	}
    }

  if (!ok)
    {
      MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);
      return err;
    }

  client->mididev = midi_devs[dev];
  oss_midi_set_defaults (client->mididev);

  client->mididev->is_timing_master = 0;

  client->num = dev;

  MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);

  if (mode & OPEN_READ)
    {
      char name[16];
      sprintf (name, "read%d", dev);
      client->mididev->in_queue =
	midi_queue_alloc (client->mididev->osdev, name);
      if (client->mididev->in_queue == NULL)
	{
	  cmn_err (CE_WARN, "Failed to allocate MIDI input queue\n");
	  midi_devs[dev]->open_mode = 0;
	  client->open_mode = 0;
	  client->mididev = NULL;
	  return -ENOMEM;
	}
    }

  if (mode & OPEN_WRITE)
    {
      char name[16];
      int i;
      unsigned char tmpbuf[4];
      sprintf (name, "write%d", dev);
      client->mididev->out_queue =
	midi_queue_alloc (client->mididev->osdev, name);
/* if (dev==0)midi_queue_debugging(client->mididev->out_queue); */
      if (client->mididev->out_queue == NULL)
	{
	  cmn_err (CE_WARN, "Failed to allocate MIDI output queue\n");
	  midi_devs[dev]->open_mode = 0;
	  client->open_mode = 0;
	  client->mididev = NULL;
	  return -ENOMEM;
	}

/*
 * Reset all MIDI controllers to their default values
 */
      if (!(client->mididev->flags & MFLAG_QUIET))
	for (i = 0; i < 16; i++)
	  {
	    tmpbuf[0] = 0xb0 | i;	/* Control change */
	    tmpbuf[1] = 121;	/* Reset all controllers */
	    tmpbuf[2] = 127;	/* Reset vol/exp/pan too */
	    put_output (client->mididev, tmpbuf, 3);
	  }
      if (client->mididev->d->flush_output)
	client->mididev->d->flush_output (client->mididev->dev);
    }

  if (client->mididev->d->init_device)
    client->mididev->d->init_device (dev);

  return 0;
}

int
oss_vmidi_open (int dev, int dev_type, struct fileinfo *file, int recursive,
		int open_flags, int *newdev);

static void
sync_output (mididev_t * mididev)
{
  int n = 0;
  unsigned int status;
  oss_native_word flags;

  if (mididev->is_killed)
    {
      midi_queue_removeall (mididev->out_queue);
      return;
    }

  if (mididev->timer_dev < 0 || !oss_timer_is_running (mididev->timer_dev))
    {
      midi_queue_removeall (mididev->out_queue);
      return;
    }

  MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);
  if (mididev->d->flush_output)
    mididev->d->flush_output (mididev->dev);

  while (!midi_queue_isempty (mididev->out_queue))
    {
      int tmout;

      if (n++ > 100)
	{
	  cmn_err (CE_NOTE, "MIDI output didn't get drained (sync).\n");
	  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	  return;
	}
      tmout = oss_timer_get_timeout (mididev->timer_dev, mididev->dev);
      if (oss_sleep (mididev->out_wq, &mididev->mutex,
		     tmout, &flags, &status))
	n = 0;			/* Not timeout */
      if (status & WK_SIGNAL)
	{
	  mididev->is_killed = 1;
	  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	  return;
	}
    }

  n = 0;
  if (mididev->d->wait_output)
    while (mididev->d->wait_output (mididev->dev))
      {
	MDB (cmn_err (CE_CONT, "Wait output\n"));
	if (n++ > 10)
	  {
	    cmn_err (CE_NOTE, "MIDI output doesn't get emptied (sync).\n");
	    MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	    return;
	  }
	oss_sleep (mididev->out_wq, &mididev->mutex, OSS_HZ, &flags, &status);
	if (status & WK_SIGNAL)
	  {
	    mididev->is_killed = 1;
	    MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	    return;
	  }
      }
/*
 * Give the device few moments of extra time to flush all buffers.
 */
  oss_sleep (mididev->out_wq, &mididev->mutex, OSS_HZ / 2, &flags, &status);
  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);

}

static void
put_output (mididev_t * mididev, unsigned char *buf, int len)
{
  int i, n;
  int tmout = 1000;		/* Spend here at most 1000 msec */

  if (mididev->d->outputc == NULL
      || (len > 1 && mididev->d->bulk_write != NULL))
    {
      int p = 0;

      while (p < len && tmout-- > 0)
	{
	  n = mididev->d->bulk_write (mididev->dev, buf + p, len - p);
	  if (n < 0)		/* Error */
	    {
	      return;
	    }

	  if (n < (len - p))	/* Not all consumed yet */
	    oss_udelay (1000);

	  p += n;
	}

      return;
    }

  if (mididev->d->outputc)
    for (i = 0; i < len; i++)
      {
	while (!mididev->d->outputc (mididev->dev, buf[i]) && tmout-- > 0)
	  {
	    oss_udelay (1000);
	  }
      }
}

void
oss_midi_release (int dev, struct fileinfo *file)
{
  oss_native_word flags;
  oss_midi_client_t *client;
  int midi_dev;
  mididev_t *mididev;
  int mode;

  MUTEX_ENTER_IRQDISABLE (midi_mutex, flags);
  mode = file->mode & O_ACCMODE;
  client = oss_midi_clients[dev];

  untimeout (mididev->mtc_timeout_id);

  client->open_mode = 0;

  if (client->mididev == NULL)	/* Not bound to a port device */
    {
      MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);
      return;
    }

  mididev = client->mididev;
  midi_dev = mididev->dev;

  if (mididev->open_mode & OPEN_WRITE)	/* Needs to sync */
    {
      MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);
      sync_output (mididev);

      if (mididev->is_timing_master)
	oss_timer_stop (mididev->timer_dev);
      mididev->is_killed = 0;	/* Too young to die */

      /* Shut up possible hanging notes (if any) */
      if (!(mididev->flags & MFLAG_QUIET))
	{
	  unsigned char tmpbuf[4];
	  int i;

	  /* Sending one active sensing message should stop most devices */
	  tmpbuf[0] = 0xfe;
	  put_output (mididev, tmpbuf, 1);

#if 1
	  /*
	   * To make sure we will also send a "all notes off" message to
	   * all MIDI channels.
	   */
	  for (i = 0; i < 16; i++)
	    {
	      tmpbuf[0] = 0xb0 | i;	/* Control change */
	      tmpbuf[1] = 123;	/* All notes off */
	      tmpbuf[2] = 0;
	      put_output (mididev, tmpbuf, 3);
	    }
#endif
	}

      sync_output (mididev);

      MUTEX_ENTER_IRQDISABLE (midi_mutex, flags);
    }
  MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);

  mididev->d->ioctl (midi_dev, SNDCTL_SETSONG, (ioctl_arg) "");
  mididev->d->close (midi_dev, mode);
  MUTEX_ENTER_IRQDISABLE (midi_mutex, flags);
  mididev->open_mode &= ~mode;
  MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);

  if (mididev->timer_dev >= 0)
    oss_timer_detach_client (mididev->timer_dev, mididev);
  mididev->timer_dev = -1;
  mididev->timer_driver = 0;

  if (mididev->in_queue != NULL)
    midi_queue_free (mididev->in_queue);
  mididev->in_queue = NULL;
  mididev->pid = -1;
  mididev->cmd[0] = 0;

  if (mididev->out_queue != NULL)
    midi_queue_free (mididev->out_queue);
  mididev->out_queue = NULL;

  client->mididev = NULL;

  if (mididev->out_timeout != 0)
    untimeout (mididev->out_timeout);
  mididev->out_timeout = 0;
}

int
oss_midi_write (int dev, struct fileinfo *file, uio_t * buf, int count)
{
  oss_midi_client_t *client = oss_midi_clients[dev];
  mididev_t *mididev;
  int ret;
  unsigned int status;
  int c = 0, l, loops;
  oss_native_word flags;
  midi_packet_header_t hdr;
  int save_header = 0;
  int tmout = OSS_HZ;

  unsigned char *targetbuf;

  MDB (cmn_err (CE_CONT, "MIDI write %d\n", count));
  if (client->mididev == NULL)
    if ((ret = midi_mapper_autobind (dev, client->open_mode)) < 0)
      return ret;
  mididev = client->mididev;

  if (mididev == NULL)
    return -EBUSY;

  MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);

  /*
   * Since the application called write again we can ignore possible
   * earlier received kill signals.
   */
  mididev->is_killed = 0;

  memset (&hdr, 0, sizeof (hdr));
  if (mididev->working_mode != MIDI_MODE_TRADITIONAL)
    {
      if (count < sizeof (midi_packet_header_t))
	{
	  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	  cmn_err (CE_WARN, "Too short MIDI write (no header)\n");
	  return -EINVAL;
	}

      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
      if (uiomove (&hdr, sizeof (midi_packet_header_t), UIO_WRITE, buf) != 0)
	{
	  cmn_err (CE_WARN, "uiomove (header) failed\n");
	  return -EFAULT;
	}

      if (hdr.magic != MIDI_HDR_MAGIC)
	{
	  cmn_err (CE_WARN, "Bad MIDI write packet header (%04x)\n",
		   hdr.magic);
	  return -EINVAL;
	}
      count -= sizeof (midi_packet_header_t);
      c += sizeof (midi_packet_header_t);

      /* Force save of the header if no other data was written */
      if (count <= 0)
	save_header = 1;

      MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);
    }

  loops = 0;
  while (save_header || count > 0)
    {
      MDB (cmn_err (CE_CONT, "Write bytes left %d\n", count));
      l = count;
      save_header = 0;

      if (loops++ > 10)
	{
	  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	  cmn_err (CE_CONT, "MIDI Output buffer %d doesn't drain %d/%d\n",
		   mididev->dev, c, count);
	  return -EIO;
	}

      if (l > MIDI_PAYLOAD_SIZE)
	l = MIDI_PAYLOAD_SIZE;

      if ((ret =
	   midi_queue_alloc_record (mididev->out_queue, &targetbuf, l,
				    &hdr)) < 0)
	{
	  if (ret == -ENOSPC)	/* Buffers full */
	    {
	      MDB (cmn_err (CE_CONT, "*** Buffers full ***\n"));
	      tmout =
		oss_timer_get_timeout (mididev->timer_dev, mididev->dev);
	      if (!oss_sleep
		  (mididev->out_wq, &mididev->mutex, tmout, &flags, &status))
		{
		  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
		  if (c > 0)
		    return c;
		  return -EIO;	/* Timeout - why */
		}
	      else
		loops = 0;	/* Restart loop limiter */

	      if (status & WK_SIGNAL)
		{
		  mididev->is_killed = 1;
		  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
		  return -EINTR;
		}

	      if (loops > 95)
		cmn_err (CE_NOTE, "MIDI Output buffers full\n");
	      continue;		/* Try again later */
	    }

	  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
	  return ret;
	}

      if (l > ret)
	l = ret;

      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
/* cmn_err(CE_CONT, "Copy %08x (%d)\n", targetbuf, l); */

      loops = 0;

      if (l > 0)
	{
	  MDB (cmn_err (CE_CONT, "Store %d bytes\n", l));
	  if (uiomove (targetbuf, l, UIO_WRITE, buf) != 0)
	    {
	      cmn_err (CE_WARN, "uiomove (write) failed\n");
	      return -EFAULT;
	    }

	  count -= l;
	  c += l;
	}

      MDB (cmn_err (CE_CONT, "From oss_midi_write\n"));
      oss_midi_output_intr_handler (mididev->dev, 0);
      MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);
    }

  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);

  /* midi_queue_trace(mididev->out_queue); */
  MDB (cmn_err (CE_CONT, "MIDI write done %d\n\n", c));

  return c;
}

static int
do_read (mididev_t * mididev, uio_t * buf, unsigned char *data, int c,
	 int max_bytes, midi_packet_header_t * hdr)
{
  int l = 0;

  if (mididev->working_mode != MIDI_MODE_TRADITIONAL)
    {
      if (max_bytes <= sizeof (*hdr))
	{
	  cmn_err (CE_WARN, "MIDI read too small for packet header\n");
	  return -E2BIG;
	}

      hdr->magic = MIDI_HDR_MAGIC;
      if (uiomove (hdr, sizeof (*hdr), UIO_READ, buf) != 0)
	{
	  cmn_err (CE_WARN, "uiomove (read) failed\n");
	  return -EFAULT;
	}

      l += sizeof (*hdr);
    }

  if (l + c > max_bytes)
    c = max_bytes - l;

  if (c <= 0)
    return -E2BIG;

  if (uiomove (data, c, UIO_READ, buf) != 0)
    {
      cmn_err (CE_WARN, "uiomove (read) failed\n");
      return -EFAULT;
    }

  midi_queue_remove_chars (mididev->in_queue, c);
  l += c;

  return l;
}

int
oss_midi_read (int dev, struct fileinfo *file, uio_t * buf, int count)
{
  oss_midi_client_t *client = oss_midi_clients[dev];
  mididev_t *mididev;
  oss_native_word flags;
  oss_midi_time_t time;
  unsigned char *data;

  unsigned int status;
  int tmout = 0;
  int c = 0;
  midi_packet_header_t *hdr;

  int err;

  if (client->mididev == NULL)
    if ((err = midi_mapper_autobind (dev, client->open_mode)) < 0)
      return err;
  mididev = client->mididev;

  if (mididev == NULL)
    return -EBUSY;

  MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);

  if ((c = midi_queue_find_buffer (mididev->in_queue, &data, &hdr)) < 0)
    {
      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
      return c;
    }

  if (c > 0
      || (mididev->working_mode != MIDI_MODE_TRADITIONAL && c >= 0
	  && hdr != NULL))
    {
      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
      return do_read (mididev, buf, data, c, count, hdr);
    }

  if (mididev->prech_timeout < 0)	/* Non blocking mode */
    {
      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
      return -EWOULDBLOCK;
    }

  if (!oss_sleep
      (mididev->in_wq, &mididev->mutex, mididev->prech_timeout, &flags,
       &status))
    {
      /* Timeout */

      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
      return 0;
    }

  if (status & WK_SIGNAL)
    {
      mididev->is_killed = 1;
      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
      return -EINTR;
    }

  if ((c = midi_queue_get (mididev->in_queue, &data, count, &hdr)) < 0
      || (c == 0 && hdr == NULL))
    {
      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
      return c;
    }

  MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
  if (c > 0)
    return do_read (mididev, buf, data, c, count, hdr);

  return c;
}

static int
setup_working_mode (mididev_t * mididev)
{
  int timer_dev;
  int err;

  if (mididev->timer_dev < 0)	/* No timer yet */
    {
      /*
       * Setup a timer
       */

      if (mididev->timer_driver < 0
	  || mididev->timer_driver >= oss_num_timer_drivers)
	{
	  cmn_err (CE_WARN, "Invalid MIDI timer %d selected\n",
		   mididev->timer_driver);
	  return -ENXIO;
	}

      if ((timer_dev =
	   oss_timer_create_instance (mididev->timer_driver, mididev)) < 0)
	return timer_dev;
      mididev->is_timing_master = 1;

      if ((err = oss_timer_attach_client (timer_dev, mididev)) < 0)
	{
	  oss_timer_detach_client (timer_dev, mididev);
	  return err;
	}

      mididev->timer_dev = timer_dev;
      if (mididev->d->timer_setup)
	mididev->d->timer_setup (mididev->dev);
    }
  else
    timer_dev = mididev->timer_dev;

  if (timer_dev < 0 || timer_dev >= oss_num_timers ||
      oss_timer_devs[timer_dev] == NULL)
    {
      cmn_err (CE_WARN, "Failed to allocate a timer instance\n");
      return -ENXIO;
    }


  return 0;
}

static int
setup_timebase (mididev_t * mididev)
{
  if (mididev->timer_dev < 0)	/* No timer yet */
    return 0;

  return oss_timer_set_timebase (mididev->timer_dev, mididev->timebase);
}

static int
setup_tempo (mididev_t * mididev)
{
  if (mididev->timer_dev < 0)	/* No timer yet */
    return 0;

  return oss_timer_set_tempo (mididev->timer_dev, mididev->tempo);
}

int
oss_midi_ioctl (int dev, struct fileinfo *file,
		unsigned int cmd, ioctl_arg arg)
{
  oss_midi_client_t *client = oss_midi_clients[dev];
  mididev_t *mididev;
  int err, val;
  oss_native_word d;

  if (client->mididev == NULL)
    if ((err = midi_mapper_autobind (dev, client->open_mode)) < 0)
      return err;
  mididev = client->mididev;
  if (mididev == NULL)
    return -EBUSY;

  dev = mididev->dev;

  switch (cmd)
    {
    case SNDCTL_MIDI_PRETIME:
      val = *arg;
      if (val < -1)
	return -EINVAL;

      if (val != -1)
	val = (OSS_HZ * val) / 100;
      mididev->prech_timeout = val;
      return 0;
      break;

    case SNDCTL_MIDI_MTCINPUT:
      val = *arg;
      switch (val)
	{
	case 0:
	case -1:
	  break;
	case 24:
	  mididev->mtc_codetype = 0;
	  break;
	case 25:
	  mididev->mtc_codetype = 1;
	  break;
	case 29:
	  mididev->mtc_codetype = 2;
	  break;
	case 30:
	  mididev->mtc_codetype = 3;
	  break;
	  break;
	default:
	  return -EINVAL;
	}
      mididev->mtc_timebase = val;
      mididev->mtc_t0 = GET_JIFFIES ();
      mididev->mtc_current = 0;
      mididev->mtc_prev_t = -1;
      mididev->mtc_phase = 0;
      d = dev;
      if (mididev->mtc_timebase != -1)
	mididev->mtc_timeout_id =
	  timeout (mtc_generator, (void *) d, OSS_HZ / 100);
      return *arg = val;
      break;

    case SNDCTL_MIDI_SETMODE:
      val = *arg;
      if (val != MIDI_MODE_TRADITIONAL && val != MIDI_MODE_TIMED
	  && val != MIDI_MODE_TIMED_ABS)
	return -EINVAL;
      mididev->working_mode = val;
      *arg = val;
      if ((err = setup_working_mode (mididev)) < 0)
	return err;
      if ((err = setup_tempo (mididev)) < 0)
	return err;
      if ((err = setup_timebase (mididev)) < 0)
	return err;
      return 0;
      break;

    case SNDCTL_MIDI_TIMEBASE:
      val = *arg;
      if (val < 1 || val > 1000)
	return -EINVAL;
      mididev->timebase = val;
      return setup_timebase (mididev);
      break;

    case SNDCTL_MIDI_TEMPO:
      val = *arg;
      if (val < 1 || val > 200)
	return -EINVAL;
      mididev->tempo = val;
      return setup_tempo (mididev);
      break;

    default:
      return mididev->d->ioctl (mididev->dev, cmd, arg);
    }
}

#ifdef ALLOW_SELECT
int
oss_midi_chpoll (int dev, struct fileinfo *file, oss_poll_event_t * ev)
{
  short events = ev->events;
  oss_midi_client_t *client;
  mididev_t *mididev;
  oss_native_word flags;
  int err;

  if (dev < 0 || dev >= oss_num_midi_clients)
    return -ENXIO;
  client = oss_midi_clients[dev];
  if (client->mididev == NULL)
    if ((err = midi_mapper_autobind (dev, client->open_mode)) < 0)
      return err;
  mididev = client->mididev;
  if (mididev == NULL)
    return -EIO;

  if ((events & (POLLOUT | POLLWRNORM)) && (mididev->open_mode & OPEN_WRITE))
    {
      MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);
      if (mididev->out_queue == NULL
	  || midi_queue_spaceleft (mididev->out_queue) < 10)
	{			/* No space yet */
	  oss_register_poll (mididev->out_wq, &mididev->mutex, &flags, ev);
	}
      else
	{
	  ev->revents |= (POLLOUT | POLLWRNORM) & events;
	}
      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
    }

  if ((events & (POLLIN | POLLRDNORM)) && (mididev->open_mode & OPEN_READ))
    {
      MUTEX_ENTER_IRQDISABLE (mididev->mutex, flags);
      if (mididev->in_queue == NULL || midi_queue_isempty (mididev->in_queue))
	{			/* No space yet */
	  oss_register_poll (mididev->in_wq, &mididev->mutex, &flags, ev);
	}
      else
	{
	  ev->revents |= (POLLIN | POLLRDNORM) & events;
	}
      MUTEX_EXIT_IRQRESTORE (mididev->mutex, flags);
    }

  return 0;
}
#endif

static oss_cdev_drv_t vmidi_cdev_drv = {
  oss_vmidi_open,
  oss_midi_release,
  oss_midi_read,
  oss_midi_write,
  oss_midi_ioctl,
#ifdef ALLOW_SELECT
  oss_midi_chpoll
#else
  NULL
#endif
};

static oss_cdev_drv_t midi_cdev_drv = {
  oss_midi_open,
  oss_midi_release,
  oss_midi_read,
  oss_midi_write,
  oss_midi_ioctl,
#ifdef ALLOW_SELECT
  oss_midi_chpoll
#else
  NULL
#endif
};

int
oss_vmidi_open (int dev, int dev_type, struct fileinfo *file, int recursive,
		int open_flags, int *newdev)
{
/*
 * oss_vmidi_open is another version of oss_midi_open. Instead of binding
 * the client directly with the device port this routine creates just a
 * client (device file). The binding operation is left to be done later
 * using SNDCTL_MIDI_BIND. If no binding is made then the first
 * read/write/ioctl/etc operation will automatically bind the client
 * with the first available (lowest number) MIDI port.
 *
 * oss_vmidi_open() drives the /dev/midi device file.
 */

  oss_native_word flags;
  oss_midi_client_t *client = NULL;
  int ok, err = -EBUSY, d;
  int mode = file->mode & O_ACCMODE;

  MUTEX_ENTER_IRQDISABLE (midi_mutex, flags);

/*
 * Find the first midi client number that is free. Start from the client 
 * numbers after the last existing MIDI port.
 */
  for (dev = num_mididevs; dev < oss_num_midi_clients; dev++)
    {
      if (oss_midi_clients[dev] != NULL &&
	  oss_midi_clients[dev]->open_mode == 0)
	{
	  client = oss_midi_clients[dev];
	  break;
	}
    }

  if (client == NULL)
    {
      /*
       * No earlier allocated clients were free so create a new entry.
       * Also create an anonymous character device.
       */
      MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);
      client = PMALLOC (osdev, sizeof (*client));
      MUTEX_ENTER_IRQDISABLE (midi_mutex, flags);

      if (oss_num_midi_clients >= MAX_MIDI_CLIENTS)
	{
	  cmn_err (CE_WARN, "Too many MIDI clients\n");
	  MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);
	  return -ENXIO;
	}

      if (client == NULL)
	{
	  cmn_err (CE_WARN, "Out of memory\n");
	  MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);
	  return -ENOMEM;
	}

      memset (client, 0, sizeof (*client));
      oss_midi_clients[dev = oss_num_midi_clients++] = client;

#ifndef MIDI_DISABLED
      MDB (cmn_err (CE_CONT, "Installing /dev/midi%02d\n", dev));
      oss_install_chrdev (osscore_osdev, NULL, OSS_DEV_MIDI, dev,
			  &midi_cdev_drv, 0);
#endif
    }

  client->open_mode = mode;
  client->num = dev;
  client->mididev = NULL;	/* Not bound yet */
  MUTEX_EXIT_IRQRESTORE (midi_mutex, flags);

  if ((d = oss_find_minor (OSS_DEV_MIDI, dev)) < 0)
    {
      oss_midi_release (dev, file);
      return d;
    }
  *newdev = d;

  return dev;
}

void
oss_midi_init (oss_device_t * osdev)
{
  static int already_done = 0;

  if (already_done)
    return;
  already_done = 1;

  if (sizeof (midi_packet_header_t) != 32)
    {
      cmn_err (CE_WARN, "sizeof(midi_packet_header_t) != 16 (%d)\n",
	       sizeof (midi_packet_header_t));
      cmn_err (CE_CONT, "MIDI subsystem not activated\n");
      return;
    }

  osscore_osdev = osdev;

  MUTEX_INIT (osdev, midi_mutex, MH_FRAMEW);

  midi_mapper_init (osdev);
  oss_init_timers (osdev);
  attach_oss_default_timer (osdev);
}

void
oss_midi_uninit (void)
{
  int dev;

  midi_mapper_uninit ();
  detach_oss_default_timer ();
  oss_uninit_timers ();

  for (dev = 0; dev < num_mididevs; dev++)
    {
      oss_remove_wait_queue (midi_devs[dev]->out_wq);
      oss_remove_wait_queue (midi_devs[dev]->in_wq);
      MUTEX_CLEANUP (midi_devs[dev]->mutex);
    }
  MUTEX_CLEANUP (midi_mutex);
}

void
install_vmidi (oss_device_t * osdev)
{
#ifndef MIDI_DISABLED
#ifdef VDEV_SUPPORT
  oss_install_chrdev (osdev, "midi", OSS_DEV_VMIDI, 0, &vmidi_cdev_drv,
		      CHDEV_VIRTUAL);
#endif
#endif
}

int
oss_install_mididev (int version,
		     char *id, char *name,
		     midi_driver_t * d, int driver_size,
		     struct synth_operations *cnv,
		     unsigned int flags, void *devc, oss_device_t * osdev)
{
  char tmp[30];
  int curr_midi_dev;
  mididev_t *op;
  oss_midi_client_t *client;
  int i;
  int old = 0;

/* 
 * Old style drivers are always input&output even they don't report that.
 * Fix the permissions automatically.
 */
  if (!(flags & (MFLAG_INPUT | MFLAG_OUTPUT)))
    flags |= MFLAG_INPUT | MFLAG_OUTPUT;

  if (driver_size > sizeof (midi_driver_t))
    driver_size = sizeof (midi_driver_t);

  if (midi_devs == NULL)
    {
      midi_devs = PMALLOC (osdev, sizeof (mididev_p) * MAX_MIDI_DEV);
    }

  if (midi_devs == NULL)
    {
      cmn_err (CE_WARN, "oss_install_mididev: Out of memory\n");
      return -ENOMEM;
    }

  for (i = 0; i < num_mididevs; i++)
    if (midi_devs[i]->unloaded && midi_devs[i]->os_id == oss_get_osid (osdev))
      {
	old = 1;
	op = midi_devs[i];
	curr_midi_dev = i;
	MDB (cmn_err (CE_CONT, "Reincarnation of MIDI device %d\n", i));
	break;
      }

  if (!old)
    {
      if (num_mididevs >= MAX_MIDI_DEV - 1)
	{
	  cmn_err (CE_WARN, "Too many MIDI devices in the system\n");
	  return -EIO;
	}

      op = midi_devs[num_mididevs] = PMALLOC (osdev, sizeof (mididev_t));
      if (op == NULL)
	{
	  cmn_err (CE_WARN,
		   "oss_install_mididev: Failed to allocate memory\n");
	  return -ENOMEM;
	}
      memset (op, 0, sizeof (*op));
      curr_midi_dev = num_mididevs++;
    }

  op->devc = devc;
  op->dev = op->real_dev = curr_midi_dev;
  op->pid = -1;
  op->osdev = osdev;
  op->os_id = oss_get_osid (osdev);
  op->latency = -1;		/* Not known */

  if (!old)
    {
      MUTEX_INIT (op->osdev, op->mutex, MH_FRAMEW + 1);

      if ((op->out_wq =
	   oss_create_wait_queue (op->osdev, "midi_out")) == NULL)
	cmn_err (CE_PANIC, "Cannot create MIDI output wait queue\n");

      if ((op->in_wq = oss_create_wait_queue (op->osdev, "midi_in")) == NULL)
	cmn_err (CE_PANIC, "Cannot create MIDI input wait queue\n");

      op->d = PMALLOC (osdev, sizeof (midi_driver_t));
      memset (op->d, 0, sizeof (op->d));
      memcpy (op->d, d, driver_size);
      sprintf (op->handle, "%s-md%02d", osdev->handle, ++osdev->num_mididevs);
      op->port_number = osdev->num_mididevs;
    }

  op->flags = flags;
  op->enabled = 1;
  op->unloaded = 0;
  op->mtc_timebase = -1;
  op->card_number = osdev->cardnum;
  op->timer_driver = 0;		/* Point to the default timer */
  op->timer_dev = -1;		/* No timer instance created yet */

  strncpy (op->name, name, sizeof (op->name) - 1);
  op->name[sizeof (op->name) - 1] = 0;

  op->caps = 0;
  if (flags & MFLAG_INPUT)
    op->caps |= MIDI_CAP_INPUT;
  if (flags & MFLAG_OUTPUT)
    op->caps |= MIDI_CAP_OUTPUT;


  if (oss_midi_clients[curr_midi_dev] == NULL)
    {
      client = PMALLOC (osdev, sizeof (*client));
      if (client == NULL)
	cmn_err (CE_PANIC, "OSS install MIDI: Out of memory.\n");
      memset (client, 0, sizeof (*client));
      oss_midi_clients[curr_midi_dev] = client;
      oss_num_midi_clients = num_mididevs;
    }


#ifndef MIDI_DISABLED
/*
 * Create the device node.
 */

  {
    char name[10];
#ifdef NEW_DEVICE_NAMING
# ifdef USE_DEVICE_SUBDIRS
    sprintf (name, "oss/%s/mid%d", osdev->nick, osdev->num_mididevs - 1);
# else
    sprintf (name, "%s_mid%d", osdev->nick, osdev->num_mididevs - 1);
# endif
#else
    sprintf (name, "midi%02d", curr_midi_dev);
#endif
    oss_install_chrdev (osdev, name, OSS_DEV_MIDI, curr_midi_dev,
			&midi_cdev_drv, 0);
    sprintf (op->devnode, "/dev/%s", name);
  }
#endif

  return curr_midi_dev;
}
