/*
 * Purpose: MIDI mixer pseudo driver
 *
 * This driver creates a pseudo MIDI port device that can be used for
 * real-time mixer volume changes. The pseudo MIDI device will return MIDI
 * control change messages when a mixer setting changes. Application using the
 * device may also do mixer changes (cross fading, etc) by sending (or
 * playing back) control messages.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#include "oss_midimix_cfg.h"
#include "midi_core.h"
#include "midiparser.h"

#define MAX_INSTANCES	1

typedef struct
{
  oss_device_t *osdev;
  oss_mutex_t mutex;
  int midi_dev;
  int open_mode;
  oss_midi_inputbuf_t inputbuf;
  midiparser_common_p parser;
  int modify_counters[16];
  int update_counters[16][128];
  timeout_id_t timeout_id;
} midimix_devc;

static midimix_devc midimix_devs[MAX_INSTANCES] = {{ 0 }};
static int ndevs = 0;

static void
midimix_close (int dev, int mode)
{
  midimix_devc *devc = midi_devs[dev]->devc;
  oss_native_word flags;

  midiparser_unalloc (devc->parser);
  devc->parser = NULL;
  untimeout (devc->timeout_id);

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  devc->open_mode = 0;
  devc->inputbuf = NULL;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

void
parser_cb (void *context, int category, unsigned char msg, unsigned char ch,
	   unsigned char *parms, int len)
{

  if (category == CAT_CHN && msg == MIDI_CTL_CHANGE)
    {
      oss_mixext *ext;
      oss_mixer_value rec;

      int dev = ch;
      int ctl = parms[0];
      int val = parms[1];

      if ((ext = mixer_find_ext (dev, ctl)) == NULL)
	return;			/* Not found */

      if (!(ext->flags & MIXF_READABLE) || !(ext->flags & MIXF_WRITEABLE))
	return;			/* Not accessible */

      if (ext->maxvalue > 127)
	val = (val * ext->maxvalue + 63) / 127;

      if (val < 0 || val > ext->maxvalue)
	return;			/* Value out of range */

      switch (ext->type)
	{
	case MIXT_MONOSLIDER:
	case MIXT_STEREOSLIDER:
	  val = val & 0xff;
	  val = val | (val << 8);
	  break;

	case MIXT_ONOFF:
	  val = !!val;
	  break;

	case MIXT_ENUM:
	  if (val >= ext->maxvalue)
	    return;		/* Out of range */
	  break;

	case MIXT_VALUE:
	case MIXT_HEXVALUE:
	case MIXT_SLIDER:
	  /* Accept as-is */
	  break;

	default:
	  return;		/* Type not supported */
	}

      memset (&rec, 0, sizeof (rec));
      rec.dev = dev;
      rec.ctrl = ctl;
      rec.value = val;
      rec.timestamp = ext->timestamp;
      oss_mixer_ext (dev, OSS_DEV_MIXER, SNDCTL_MIX_WRITE, (ioctl_arg) & rec);
    }

}

static void
check_for_updates (midimix_devc * devc, int force)
{
  int dev, ctl;

  for (dev = 0; dev < 16 && dev < num_mixers; dev++)
    {
      touch_mixer (dev);

      if (force
	  || (mixer_devs[dev]->modify_counter > devc->modify_counters[dev]))
	{
	  devc->modify_counters[dev] = mixer_devs[dev]->modify_counter;
	  for (ctl = 0; ctl < 128; ctl++)
	    {
	      oss_mixext *ext;
	      oss_mixer_value rec;
	      int val;
	      unsigned char midibuf[3];

	      if ((ext = mixer_find_ext (dev, ctl)) == NULL)
		break;		/* All controls processed I think */
	      if (!(ext->flags & MIXF_READABLE)
		  || !(ext->flags & MIXF_WRITEABLE))
		continue;	/* Not accessible */

	      if (!force
		  && devc->update_counters[dev][ctl] == ext->update_counter)
		continue;	/* No change */
	      devc->update_counters[dev][ctl] = ext->update_counter;

	      switch (ext->type)
		{
		case MIXT_MONOSLIDER:
		case MIXT_STEREOSLIDER:
		case MIXT_ONOFF:
		case MIXT_ENUM:
		case MIXT_VALUE:
		case MIXT_HEXVALUE:
		case MIXT_SLIDER:
		  /* Type OK */
		  break;

		default:
		  continue;	/* Type not supported */
		}

	      memset (&rec, 0, sizeof (rec));
	      rec.dev = dev;
	      rec.ctrl = ctl;
	      rec.timestamp = ext->timestamp;

	      if (oss_mixer_ext
		  (dev, OSS_DEV_MIXER, SNDCTL_MIX_READ,
		   (ioctl_arg) & rec) < 0)
		{
		  continue;	/* Read failed */
		}

	      val = rec.value & 0xff;
	      if (ext->maxvalue > 127)
		val = (val * 127 + (ext->maxvalue / 2)) / ext->maxvalue;

	      if (val < 0)
		val = 0;
	      if (val > 127)
		val = 127;

	      midibuf[0] = 0xb0 | dev;
	      midibuf[1] = ctl;
	      midibuf[2] = val;

	      devc->inputbuf (devc->midi_dev, midibuf, 3);
	    }
	}
    }
}

static void
timer_callback (void *arg)
{
  midimix_devc *devc = arg;
  check_for_updates (devc, 0);
  devc->timeout_id = timeout (timer_callback, devc, OSS_HZ / 10);	/* 0.1s */
}

static int
midimix_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
	      oss_midi_inputbuf_t inputbuf, oss_midi_outputintr_t outputintr)
{
  midimix_devc *devc = midi_devs[dev]->devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (devc->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }
  devc->open_mode = mode;
  devc->inputbuf = inputbuf;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  if ((devc->parser = midiparser_create (parser_cb, devc)) == NULL)
    {
      devc->open_mode = 0;
      devc->inputbuf = NULL;
      cmn_err (CE_WARN, "Cannot create MIDI parser\n");
      return OSS_ENOMEM;
    }

  devc->timeout_id = timeout (timer_callback, devc, OSS_HZ / 10);	/* 0.1s */
  return 0;
}

static int
midimix_ioctl (int dev, unsigned cmd, ioctl_arg arg)
{
  return OSS_EINVAL;
}

static int
midimix_out (int dev, unsigned char midi_byte)
{
  midimix_devc *devc = midi_devs[dev]->devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  midiparser_input (devc->parser, midi_byte);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 1;
}

static int
midimix_bulk_out (int dev, unsigned char *buf, int len)
{
  midimix_devc *devc = midi_devs[dev]->devc;
  int i;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  for (i = 0; i < len; i++)
    {
      midiparser_input (devc->parser, buf[i]);
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return len;
}

static void
midimix_init_device (int dev)
{
  midimix_devc *devc = midi_devs[dev]->devc;

  if (devc->open_mode & OPEN_READ)
    check_for_updates (devc, 1);
}

static midi_driver_t midimix_driver = {
  midimix_open,
  midimix_close,
  midimix_ioctl,
  midimix_out,
  midimix_bulk_out,
  MIDI_PAYLOAD_SIZE,
  NULL,
  NULL,
  NULL,
  midimix_init_device
};

static void
attach_midimix_dev (oss_device_t * osdev)
{
  midimix_devc *devc = &midimix_devs[ndevs++];

  devc->osdev = osdev;

  MUTEX_INIT (osdev, devc->mutex, MH_DRV);

  devc->midi_dev =
    oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "MIX",
			 "Mixer access MIDI device", &midimix_driver,
			 sizeof (midi_driver_t),
			 MFLAG_VIRTUAL | MFLAG_QUIET, devc, devc->osdev);
}

int
oss_midimix_attach (oss_device_t * osdev)
{
  int midimix_instances = 1;
  int i;

  oss_register_device (osdev, "OSS MIDI mixer driver");

  if (midimix_instances > MAX_INSTANCES)
    midimix_instances = MAX_INSTANCES;
  for (i = 0; i < midimix_instances; i++)
    {
      attach_midimix_dev (osdev);
    }

  return 1;
}

int
oss_midimix_detach (oss_device_t * osdev)
{
  int i, err;

  if ((err = oss_disable_device (osdev)) < 0)
    return 0;

  for (i = 0; i < ndevs; i++)
    {
      midimix_devc *devc;

      devc = &midimix_devs[i];
    }

  oss_unregister_device (osdev);

  return 1;
}
