Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

Ij.c

From Higher Intellect Vintage Wiki
Jump to navigation Jump to search
/*
 * ij.c - device-independent input jacks for VL
 *  
 * WARNING this version has not been tested for all jacks for
 * all devices.
 */

#include "ij.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/* devices ------------------------------------------------------------- */

typedef struct ijdevice
{
  int (*setup)(IJhandle me, VLNode drain_node); /* returns TRUE on success */
  char *(*getjackname)(IJhandle me, int idx);
  int (*getdefaultsource)(IJhandle me);
  int (*mapidx)(IJhandle me, int idx, int *ret_idx_base);
  int (*configurepath)(IJhandle me, int idx,
                       VLNode source_node, VLNode drain_node, VLPath path,
                       int node_number, int *ret_idx); /*returns 0 on success*/
  int (*isjackcontrol)(IJhandle me, VLControlType type);
  void (*teardown)(IJhandle me);
} ijdevice;

/* handle ------------------------------------------------------------- */

typedef struct _IJhandle
{
  VLServer svr;
  VLDevList devlist;
  VLDev device;

  ijdevice *ijd;
  void *dev_specific;

  int num_jacks;
  char *device_name;
  
} _IJhandle;

/* util ------------------------------------------------------------- */

/*
 * get video source node number of the device's default source
 */
int getdefaultsource(IJhandle me)
{
  VLNode devNode;
  VLPath controlPath;
  VLControlValue val;
  val.intVal = -1;
  
  if (-1 != (devNode = vlGetNode(me->svr, VL_DEVICE, 0, VL_ANY)) &&
      -1 != (controlPath = vlCreatePath(me->svr, me->device, 
                                        devNode, devNode))&&
      -1 != vlSetupPaths(me->svr, &controlPath, 1, VL_SHARE, VL_READ_ONLY) &&
      -1 != vlGetControl(me->svr, controlPath, devNode, 
                         VL_DEFAULT_SOURCE, &val) &&
      -1 != vlDestroyPath(me->svr, controlPath))
    return val.intVal;

  return -1;
}

/* vino ------------------------------------------------------------- */

int vinosetup(IJhandle me, VLNode drain_node)
{
  me->num_jacks = 3;
  me->device_name = "VINO Video";
  return TRUE;
}
char *vinogetjackname(IJhandle me, int idx)
{
  switch (idx)
    {
    case 0: return "Composite";
    case 1: return "S-Video";
    case 2: return "Indycam/601 Serial Digital";
    default: return NULL;
    }
}

#define VINO_ANALOG_NODE 1
#define VINO_DIGITAL_NODE 0

#define VINO_COMPOSITE_MUXSWITCH 0
#define VINO_SVIDEO_MUXSWITCH 1

/* map idx to node number.  idx is between 0 and me->num_jacks-1. */
int vinomapidx(IJhandle me, int idx, int *ret_idx_base)
{
  int node_number;
  
  switch (idx)
    {
    case 0:
    case 1:
      node_number = VINO_ANALOG_NODE;  break;
    case 2:
      node_number = VINO_DIGITAL_NODE; break;
    default:
      return -1;
    }

  if (ret_idx_base) *ret_idx_base = 0; /* not used for vino */
  return node_number;
}

int vinoconfigurepath(IJhandle me, int idx,
                      VLNode source_node, VLNode drain_node, VLPath path,
                      int node_number, int *ret_idx)
{
  VLControlValue val;
  int returned_idx;

  /* -- perform node-number-specific operations */
  switch (node_number)
    {
    case VINO_ANALOG_NODE:
      {
        if (idx==VL_ANY)
          {
            if (vlGetControl(me->svr, path, source_node, VL_MUXSWITCH, &val) 
                < 0)
              { vlDestroyPath(me->svr, path); return -1; }
            switch (val.intVal)
              {
              case VINO_COMPOSITE_MUXSWITCH: returned_idx = 0; break;
              case VINO_SVIDEO_MUXSWITCH:    returned_idx = 1; break;
              }
          }
        else
          {
            returned_idx = idx;
            switch (idx)
              {
              case 0: val.intVal = VINO_COMPOSITE_MUXSWITCH; break;
              case 1: val.intVal = VINO_SVIDEO_MUXSWITCH;    break;
              }
            if (vlSetControl(me->svr, path, source_node, VL_MUXSWITCH, &val) 
                < 0)
              { vlDestroyPath(me->svr, path); return -1; }
          }
      }
      break;
      
    case VINO_DIGITAL_NODE:
      {
        /* XXX need to set VL_TIMING differently for 601 option? */
        returned_idx = 2;
      }
      break;
    }

  if (ret_idx) *ret_idx = returned_idx;
  return 0;
}

int vinoisjackcontrol(IJhandle me, VLControlType type)
{
  return (type==VL_MUXSWITCH)||(type==VL_FORMAT);
}

ijdevice vinodev = { vinosetup, vinogetjackname, NULL,
                     vinomapidx, vinoconfigurepath, 
                     vinoisjackcontrol, NULL };

/* ev1 ------------------------------------------------------------- */

/* junior and on-board have 3 analog jacks, ABOB has 8 jacks */
#define JUNIOR_ANALOG 3
#define ABOB_ANALOG   8

/* DBOB has 2 jacks that we support.  we don't support serial or
 * parallel digital in 2 because it is often not accessible 
 * if the user happens to have a cosmo1 board also plugged in--it's
 * not worth the documentation/usability nightmare.
 */
#define DBOB          2
#define NO_DBOB       0

/* indycam plugs into the same place as DBOB and acts as one input */
#define INDYCAM       1
#define NO_INDYCAM    0

typedef struct ev1data
{
  int num_analog_jacks;  /* JUNIOR_ANALOG or ABOB_ANALOG */
  int num_dbob_jacks;    /* DBOB or NO_DBOB */
  int num_indycam_jacks; /* INDYCAM or NO_INDYCAM */

  int analog_base;
  int dbob_base;
  int indycam_base;
} ev1data;

#include <invent.h>
#include <dmedia/vl_ev1.h>

int ev1setup(IJhandle me, VLNode drain_node)
{
  ev1data *ev1 = me->dev_specific = malloc(sizeof(ev1data));
  inventory_t cpu, vid;
  inv_state_t *foo = NULL;
  inventory_t *inv;

  if (!ev1)
    return FALSE;

  cpu.inv_class = -1;
  vid.inv_class = -1;

  /* search for CPU and video in inventory */

  if (setinvent_r(&foo) < 0)
    return FALSE;
  
  while ((inv = getinvent_r(foo)) &&
         (vid.inv_class == -1 || cpu.inv_class == -1))
    {
      if (inv->inv_class == INV_VIDEO &&
          (inv->inv_type == INV_VIDEO_EXPRESS ||
           inv->inv_type == INV_VIDEO_INDY ||
           inv->inv_type == INV_VIDEO_INDY_601))
        vid = *inv;
      
      else if (inv->inv_class == INV_PROCESSOR &&
               inv->inv_type == INV_CPUBOARD)
        cpu = *inv;
    }
  
  endinvent_r(foo);
  
  if (vid.inv_class == -1 || cpu.inv_class == -1)
    {
      free(ev1);
      me->dev_specific = NULL;
      return FALSE;
    }
  
  if (vid.inv_type == INV_VIDEO_INDY)
    {
      /* Indy Video */
      ev1->num_analog_jacks = JUNIOR_ANALOG;
      ev1->num_dbob_jacks = NO_DBOB;
      ev1->num_indycam_jacks = NO_INDYCAM;
      me->device_name = "Indy Video";
    }
  else if (vid.inv_type == INV_VIDEO_INDY_601)
    {
      /* Indy Video 601 */
      ev1->num_analog_jacks = JUNIOR_ANALOG;
      /* vid.inv_state & INV_GALILEO_DBOB is not set for Indy Video 601 */
      ev1->num_dbob_jacks = DBOB;
      ev1->num_indycam_jacks = NO_INDYCAM;
      me->device_name = "Indy Video 601";
    }
  else if (vid.inv_type == INV_VIDEO_EXPRESS)
    {
      /* an Indigo or Indigo2 ev1 */

      if (cpu.inv_state == INV_IP12BOARD ||
          cpu.inv_state == INV_IP20BOARD)
        {
          /* Indigo Video */
          ev1->num_analog_jacks = ABOB_ANALOG;
          if (vid.inv_state & INV_GALILEO_DBOB)
            ev1->num_dbob_jacks = DBOB;
          if (vid.inv_state & INV_GALILEO_INDY_CAM)
            ev1->num_indycam_jacks = INDYCAM;
          me->device_name = "Indigo Video";
        }
      else
        {
          /* an Indigo2 board */
          
          if (vid.inv_state & INV_GALILEO_JUNIOR)
            {
              /* a junior board -- Indigo2 Video 
               *                or Indigo2 Video for Impact
               */
              ev1->num_analog_jacks = JUNIOR_ANALOG;
              ev1->num_dbob_jacks = NO_DBOB;
              if (vid.inv_state & INV_GALILEO_INDY_CAM)
                ev1->num_indycam_jacks = INDYCAM;
              me->device_name = "Indigo2 Video";
            }
          else
            {
              /* Galileo Video */
              ev1->num_analog_jacks = ABOB_ANALOG;
              if (vid.inv_state & INV_GALILEO_DBOB)
                ev1->num_dbob_jacks = DBOB;
              if (vid.inv_state & INV_GALILEO_INDY_CAM)
                ev1->num_indycam_jacks = INDYCAM;
              me->device_name = "Galileo Video";
            }
        }
    }
  else /* video not found, or unrecognized ev1 board */
    return FALSE;

  me->num_jacks = 
    ev1->num_analog_jacks + ev1->num_dbob_jacks + ev1->num_indycam_jacks;

  ev1->analog_base = 0;
  ev1->dbob_base = ev1->num_analog_jacks;
  ev1->indycam_base = ev1->num_analog_jacks + ev1->num_dbob_jacks;

  return TRUE;
}
char *ev1getjackname(IJhandle me, int idx)
{
  ev1data *ev1 = me->dev_specific;
  
  if (idx+ev1->analog_base < ev1->num_analog_jacks)
    {
      idx -= ev1->analog_base;

      /* analog jack */
      if (ev1->num_analog_jacks == ABOB_ANALOG)
        switch (idx)
          {
          case 0: return "Composite 1";
          case 1: return "Composite 2";
          case 2: return "Composite 3";
          case 3: return "S-Video (Y/C) 1";
          case 4: return "S-Video (Y/C) 2";
          case 5: return "S-Video (Y/C) 3";
          case 6: return "Component (Y, R-Y, B-Y) 1";
          case 7: return "Component (Y, R-Y, B-Y) 2";
          }
      else /* JUNIOR_ANALOG */
        switch (idx)
          {
          case 0: return "Composite 1";
          case 1: return "Composite 2"; 
          case 2: return "S-Video";
          }
      return NULL;
    }
  
  if (idx-ev1->dbob_base < ev1->num_dbob_jacks)
    {
      idx -= ev1->dbob_base;

      /* digital jack */
      switch (idx)
        {
        case 0: return "601 Serial Digital 1";
        case 1: return "601 Parallel Digital 1";
        }
      return NULL;
    }

  if (idx-ev1->indycam_base < ev1->num_indycam_jacks)
    {
      idx -= ev1->indycam_base;
      return "IndyCam";
    }

  return NULL;
}

#define EV1_ANALOG_NODE   0
#define EV1_DIGITAL1_NODE 1

#define EV1_ABOB_COMPOSITE1_MUXSWITCH 3
#define EV1_ABOB_COMPOSITE2_MUXSWITCH 4
#define EV1_ABOB_COMPOSITE3_MUXSWITCH 5
#define EV1_ABOB_YC1_MUXSWITCH 0
#define EV1_ABOB_YC2_MUXSWITCH 1
#define EV1_ABOB_YC3_MUXSWITCH 2
#define EV1_ABOB_COMPONENT1_MUXSWITCH 6
#define EV1_ABOB_COMPONENT2_MUXSWITCH 7

#define EV1_JUNIOR_COMPOSITE1_MUXSWITCH 3
#define EV1_JUNIOR_COMPOSITE2_MUXSWITCH 4
#define EV1_JUNIOR_YC1_MUXSWITCH 1

#define EV1_DBOB_SERIAL_DIGITAL 1
#define EV1_DBOB_PARALLEL_DIGITAL 0

/* map idx to node number.  idx is between 0 and me->num_jacks-1. */
int ev1mapidx(IJhandle me, int idx, int *ret_idx_base)
{
  int node_number;
  int idx_base;
  ev1data *ev1 = me->dev_specific;

  if (idx-ev1->analog_base < ev1->num_analog_jacks)
    {
      idx_base = ev1->analog_base;
      node_number = EV1_ANALOG_NODE;
    }
  else if (idx-ev1->dbob_base < ev1->num_dbob_jacks)
    {
      idx_base = ev1->dbob_base;
      node_number = EV1_DIGITAL1_NODE;
    }
  else if (idx-ev1->indycam_base < ev1->num_indycam_jacks)
    {
      idx_base = ev1->indycam_base;
      node_number = EV1_DIGITAL1_NODE;
    }
  else
    return -1;

  if (ret_idx_base) *ret_idx_base = idx_base;
  return node_number;
}

int ev1configurepath(IJhandle me, int idx,
                     VLNode source_node, VLNode drain_node, VLPath path,
                     int node_number, int *ret_idx)
{
  VLControlValue val;
  int returned_idx;
  int idx_base=-1;
  ev1data *ev1 = me->dev_specific;

  /* -- compute idx_base if idx != VL_ANY */
  if (idx != VL_ANY)
    {
      node_number = ev1mapidx(me, idx, &idx_base);
      assert(node_number >= 0);
      assert(idx_base >= 0);
      idx -= idx_base;
    }
  
  /* -- perform node-number-specific operations */
  switch (node_number)
    {
    case EV1_ANALOG_NODE:
      if (idx == VL_ANY)
        {
          idx_base = ev1->analog_base;

          if (vlGetControl(me->svr, path, source_node, VL_MUXSWITCH, &val) < 0)
            { vlDestroyPath(me->svr, path);  return -1; }
          
          if (ev1->num_analog_jacks == ABOB_ANALOG)
            switch (val.intVal)
              {
              case EV1_ABOB_COMPOSITE1_MUXSWITCH: returned_idx = 0; break;
              case EV1_ABOB_COMPOSITE2_MUXSWITCH: returned_idx = 1; break;
              case EV1_ABOB_COMPOSITE3_MUXSWITCH: returned_idx = 2; break;
              case EV1_ABOB_YC1_MUXSWITCH:        returned_idx = 3; break;
              case EV1_ABOB_YC2_MUXSWITCH:        returned_idx = 4; break;
              case EV1_ABOB_YC3_MUXSWITCH:        returned_idx = 5; break;
              case EV1_ABOB_COMPONENT1_MUXSWITCH: returned_idx = 6; break;
              case EV1_ABOB_COMPONENT2_MUXSWITCH: returned_idx = 7; break;
              }
          else /* JUNIOR_ANALOG */
            switch (val.intVal)
              {
              case EV1_JUNIOR_COMPOSITE1_MUXSWITCH: returned_idx = 0; break;
              case EV1_JUNIOR_COMPOSITE2_MUXSWITCH: returned_idx = 1; break;
              case EV1_JUNIOR_YC1_MUXSWITCH:        returned_idx = 2; break;
              }
        }
      else
        {
          returned_idx = idx;

          if (ev1->num_analog_jacks == ABOB_ANALOG)
            switch (idx)
              {
              case 0: val.intVal = EV1_ABOB_COMPOSITE1_MUXSWITCH; break;
              case 1: val.intVal = EV1_ABOB_COMPOSITE2_MUXSWITCH; break;
              case 2: val.intVal = EV1_ABOB_COMPOSITE3_MUXSWITCH; break;
              case 3: val.intVal = EV1_ABOB_YC1_MUXSWITCH; break;
              case 4: val.intVal = EV1_ABOB_YC2_MUXSWITCH; break;
              case 5: val.intVal = EV1_ABOB_YC3_MUXSWITCH; break;
              case 6: val.intVal = EV1_ABOB_COMPONENT1_MUXSWITCH; break;
              case 7: val.intVal = EV1_ABOB_COMPONENT2_MUXSWITCH; break;
              }
          else /* JUNIOR_ANALOG */
            switch (idx)
              {
              case 0: val.intVal = EV1_JUNIOR_COMPOSITE1_MUXSWITCH; break;
              case 1: val.intVal = EV1_JUNIOR_COMPOSITE2_MUXSWITCH; break;
              case 2: val.intVal = EV1_JUNIOR_YC1_MUXSWITCH; break;
              }
          
          if (vlSetControl(me->svr, path, source_node, VL_MUXSWITCH, &val) < 0)
            { vlDestroyPath(me->svr, path); return -1; }
        }
      break;
    case EV1_DIGITAL1_NODE: /* indycam or dbob (not both) */
      if (idx == VL_ANY)
        {
          if (ev1->num_indycam_jacks == INDYCAM)
            {
              idx_base = ev1->indycam_base;
              returned_idx = 0;
            }
          else
            {
              idx_base = ev1->dbob_base;
              if (vlGetControl(me->svr, path, source_node, 
                               VL_EV1_DBOB_INPUT1, &val) < 0)
                { vlDestroyPath(me->svr, path);  return -1; }
              
              switch (val.intVal)
                {
                case EV1_DBOB_SERIAL_DIGITAL:   returned_idx = 0; break;
                case EV1_DBOB_PARALLEL_DIGITAL: returned_idx = 1; break;
                }
            }
        }
      else
        {
          returned_idx = idx;

          /* XXX need to set VL_EV1_DBOB_INPUT1 for indycam? */
          if (ev1->num_indycam_jacks == NO_INDYCAM)
            {
              switch (idx)
                {
                case 0: val.intVal = EV1_DBOB_SERIAL_DIGITAL;   break;
                case 1: val.intVal = EV1_DBOB_PARALLEL_DIGITAL; break;
                }
              if (vlSetControl(me->svr, path, source_node, 
                               VL_EV1_DBOB_INPUT1, &val) < 0)
                { vlDestroyPath(me->svr, path); return -1; }
            }
        }
      break;
    }

  assert(idx_base != -1);
  if (ret_idx) *ret_idx = returned_idx + idx_base;
  return 0;
}
int ev1isjackcontrol(IJhandle me, VLControlType type)
{
  return (type==VL_MUXSWITCH);
}
void ev1teardown(IJhandle me)
{
  ev1data *ev1 = me->dev_specific;
  free(ev1);
  me->dev_specific = NULL;
}

ijdevice ev1dev = { ev1setup, ev1getjackname, NULL,
                    ev1mapidx, ev1configurepath, 
                    ev1isjackcontrol, ev1teardown };

/* impact ------------------------------------------------------------- */

/*
 * "impact" is a single VL device that, underneath, could be:
 *   - Impact Video only
 *   - Impact Compression only
 *   - Impact Video and Impact Compression together
 *
 * Impact Video will show up as input node numbers 0, 1, and 2,
 * and Impact Compression will show up as input node number 4.
 */

#define NO_EV3 0
#define EV3 3

#define NO_COSMO2 0
#define COSMO2 2

typedef struct impactdata
{
  int num_ev3_jacks;     /* Impact Video jacks: NO_EV3 or EV3 */
  int num_cosmo2_jacks;  /* Impact Compression jacks: NO_COSMO2 or COSMO2 */

  int ev3_base;
  int cosmo2_base;
} impactdata;

int impactsetup(IJhandle me, VLNode drain_node)
{
  int dv, nd;
  impactdata *iv = me->dev_specific = malloc(sizeof(impactdata));

  for (dv=0; dv < (int)me->devlist.numDevices; dv++)
    if (me->devlist.devices[dv].dev == me->device)
      break;
  assert(dv != (int)me->devlist.numDevices);

  iv->num_ev3_jacks = NO_EV3;
  iv->num_cosmo2_jacks = NO_COSMO2;

  for(nd=0; nd < me->devlist.devices[dv].numNodes; nd++)
    {
      int node_number = me->devlist.devices[dv].nodes[nd].number;
      if (node_number == 0) /* one of ev3's nodes */
        iv->num_ev3_jacks = EV3;
      else if (node_number == 4) /* one of cosmo2's nodes */
        iv->num_cosmo2_jacks = COSMO2;
    }
  
  me->num_jacks = iv->num_ev3_jacks + iv->num_cosmo2_jacks;
  
  iv->ev3_base = 0;
  iv->cosmo2_base = iv->num_ev3_jacks;

  /* one or the other must be there!! */
  assert(iv->num_ev3_jacks==EV3 || iv->num_cosmo2_jacks==COSMO2);

  if (iv->num_ev3_jacks==EV3 && iv->num_cosmo2_jacks==NO_COSMO2)
    me->device_name = "Impact Video";
  else if (iv->num_ev3_jacks==NO_EV3 && iv->num_cosmo2_jacks==COSMO2)
    me->device_name = "Impact Compression";
  else
    me->device_name = "Impact Video/Impact Compression";

  return TRUE;
}
char *impactgetjackname(IJhandle me, int idx)
{
  impactdata *iv = me->dev_specific;

  if (idx-iv->ev3_base < iv->num_ev3_jacks)
    {
      idx -= iv->ev3_base;
      switch (idx)
        {
        case 0: return "601 Serial Digital 1";
        case 1: return "601 Serial Digital 2";
        case 2: return "601 Serial Digital Dual-Link";
        }
      return NULL;
    }

  if (idx-iv->cosmo2_base < iv->num_cosmo2_jacks)
    {
      idx -= iv->cosmo2_base;
      switch (idx)
        {
        case 0: return "Composite";
        case 1: return "S-Video";
        }
      return NULL;
    }
  
  return NULL;
}

#define IMPACT_SERIAL1_NODE 0
#define IMPACT_SERIAL2_NODE 1
#define IMPACT_DUAL_LINK_NODE  2
#define IMPACT_COSMO2_NODE  4

#define IMPACT_COMPOSITE_MUXSWITCH 0
#define IMPACT_SVIDEO_MUXSWITCH 1

/* map idx to node number.  idx is between 0 and me->num_jacks-1. */
int impactmapidx(IJhandle me, int idx, int *ret_idx_base)
{
  int node_number;
  int idx_base;
  impactdata *iv = me->dev_specific;

  if (idx-iv->ev3_base < iv->num_ev3_jacks)
    {
      idx_base = iv->ev3_base;
      idx -= idx_base;
      switch (idx)
        {
        case 0: node_number = IMPACT_SERIAL1_NODE; break;
        case 1: node_number = IMPACT_SERIAL2_NODE; break;
        case 2: node_number = IMPACT_DUAL_LINK_NODE; break;
        }
    }
  else if (idx-iv->cosmo2_base < iv->num_cosmo2_jacks)
    {
      idx_base = iv->cosmo2_base;
      node_number = IMPACT_COSMO2_NODE;
    }
  else
    return -1;

  if (ret_idx_base) *ret_idx_base = idx_base;
  return node_number;
}

int impactconfigurepath(IJhandle me, int idx,
                           VLNode source_node, VLNode drain_node, VLPath path,
                           int node_number, int *ret_idx)
{
  VLControlValue val;
  int returned_idx;
  int idx_base=-1;
  impactdata *iv = me->dev_specific;

  /* -- compute idx_base if idx != VL_ANY */
  if (idx != VL_ANY)
    {
      node_number = impactmapidx(me, idx, &idx_base);
      assert(node_number >= 0);
      assert(idx_base >= 0);
      idx -= idx_base;
    }
  
  /* -- perform node-number-specific operations */
  switch (node_number)
    {
    case IMPACT_SERIAL1_NODE:
      idx_base = iv->ev3_base;
      returned_idx = 0;
      break; 
    case IMPACT_SERIAL2_NODE:
      idx_base = iv->ev3_base;
      returned_idx = 1;
      break; 
    case IMPACT_DUAL_LINK_NODE:
      idx_base = iv->ev3_base;
      returned_idx = 2;
      break; 
    case IMPACT_COSMO2_NODE:
      if (idx == VL_ANY)
        {
          idx_base = iv->cosmo2_base;
          if (vlGetControl(me->svr, path, source_node, VL_MUXSWITCH, &val) < 0)
            { vlDestroyPath(me->svr, path);  return -1; }
          switch (val.intVal)
            {
            case IMPACT_COMPOSITE_MUXSWITCH: returned_idx = 0; break;
            case IMPACT_SVIDEO_MUXSWITCH:    returned_idx = 1; break;
            }
        }
      else
        {
          returned_idx = idx;
          switch (idx)
            {
            case 0: val.intVal = IMPACT_COMPOSITE_MUXSWITCH; break;
            case 1: val.intVal = IMPACT_SVIDEO_MUXSWITCH;    break;
            }
          if (vlSetControl(me->svr, path, source_node, VL_MUXSWITCH, &val) < 0)
            { vlDestroyPath(me->svr, path); return -1; }
        }
      break;
    }

  assert(idx_base != -1);
  if (ret_idx) *ret_idx = returned_idx + idx_base;
  return 0;
}

int impactisjackcontrol(IJhandle me, VLControlType type)
{
  return (type==VL_MUXSWITCH);
}

void impactteardown(IJhandle me)
{
  impactdata *iv = me->dev_specific;
  free(iv);
  me->dev_specific = NULL;
}

ijdevice impactdev = 
{ impactsetup, impactgetjackname, NULL,
  impactmapidx, impactconfigurepath,
  impactisjackcontrol, impactteardown };

/* sirius ------------------------------------------------------------- */

int siriussetup(IJhandle me, VLNode drain_node)
{
  /* probe for serial digital i/o option */
  VLControlInfo *info;
  int i;
  VLNode node = vlGetNode(me->svr, VL_SRC, VL_VIDEO, 0 /* digital in 1 */ );
  VLPath path = vlCreatePath(me->svr, me->device, node, drain_node);
  if (node < 0 || path < 0) return FALSE;

  /* argh! have to set up path for vlGetControlInfo to work! */
  if (vlSetupPaths(me->svr, (VLPathList)&path, 1, 
                   VL_READ_ONLY, VL_READ_ONLY) < 0)
    { vlDestroyPath(me->svr, path); return FALSE; }
  
  /* search the VL_FORMAT parameter for any serial digital setting */
  info = vlGetControlInfo(me->svr, path, node, VL_FORMAT);
  if (!info) { vlDestroyPath(me->svr, path); return FALSE; }
  
  for(i=0; i < info->numItems; i++)
    if (info->itemList[i].value == 8 /* serial 4:2:2:4 */)
      break;
  
  if (i == info->numItems)
    me->num_jacks = 10; /* no serial digital at all */
  else
    me->num_jacks = 14; /* full serial digital (single/dual, 1/2) */
  
  me->device_name = "Sirius Video";

  if (vlFreeControlInfo(me->svr, info) < 0)
    { vlDestroyPath(me->svr, path); return FALSE; }
  
  if (vlDestroyPath(me->svr, path) < 0)
    return FALSE;

  return TRUE;
}
char *siriusgetjackname(IJhandle me, int idx)
{
  switch (idx)
    {
    case 0:  return "RGB";
    case 1:  return "YUV";
    case 2:  return "Betacam";
    case 3:  return "M-II";
    case 4:  return "Composite";
    case 5:  return "S-Video";
    case 6:  return "601 Parallel Digital 1: 4:2:2:4";
    case 7:  return "601 Parallel Digital 2: 4:2:2:4";
    case 8:  return "601 Parallel Digital 1: 4:4:4:4";
    case 9:  return "601 Parallel Digital 2: 4:4:4:4";
    case 10: return "601 Serial Digital 1: 4:2:2:4";
    case 11: return "601 Serial Digital 2: 4:2:2:4";
    case 12: return "601 Serial Digital 1: 4:4:4:4";
    case 13: return "601 Serial Digital 2: 4:4:4:4";
    }
}

#define SIRIUS_DIGITAL1_NODE 0
#define SIRIUS_DIGITAL2_NODE 1
#define SIRIUS_ANALOG_NODE   2

/* map idx to node number.  idx is between 0 and me->num_jacks-1. */
int siriusmapidx(IJhandle me, int idx, int *ret_idx_base)
{
  int node_number;
  int idx_base;

  if (idx < 6) /* analog jack */
    {
      idx_base = 0;
      node_number = SIRIUS_ANALOG_NODE;
    }
  else /* digital jack */
    {
      idx_base = 6;
      idx -= idx_base;
      node_number = (idx % 2 == 0) ? 
        SIRIUS_DIGITAL1_NODE : SIRIUS_DIGITAL2_NODE;
    }

  if (ret_idx_base) *ret_idx_base = idx_base;
  return node_number;
}

int siriusconfigurepath(IJhandle me, int idx,
                        VLNode source_node, VLNode drain_node, VLPath path,
                        int node_number, int *ret_idx)
{
  VLControlValue val;
  int returned_idx;
  int idx_base=-1;

  /* -- compute idx_base if idx != VL_ANY */
  if (idx != VL_ANY)
    {
      node_number = siriusmapidx(me, idx, &idx_base);
      assert(node_number >= 0);
      assert(idx_base >= 0);
      idx -= idx_base;
    }

  /* -- perform node-number-specific operations */
  switch (node_number)
    {
    case SIRIUS_DIGITAL1_NODE:
    case SIRIUS_DIGITAL2_NODE:
      if (idx == VL_ANY)
        {
          idx_base = 6;

          if (vlGetControl(me->svr, path, source_node, VL_FORMAT, &val) < 0)
            { vlDestroyPath(me->svr, path); return -1; }

          switch (val.intVal)
            {
            case VL_FORMAT_DIGITAL_COMPONENT:
              returned_idx=0; break;
            case VL_FORMAT_DIGITAL_COMPONENT_DUAL:        
              returned_idx=2; break;
            case VL_FORMAT_DIGITAL_COMPONENT_SERIAL:      
              returned_idx=4; break;
            case VL_FORMAT_DIGITAL_COMPONENT_DUAL_SERIAL: 
              returned_idx=6; break;
            }

          if (node_number == SIRIUS_DIGITAL2_NODE)
            returned_idx += 1;
        }
      else
        {
          returned_idx = idx;
          
          if (idx == 0 || idx == 1) /* parallel 4:2:2:4 */
            val.intVal = VL_FORMAT_DIGITAL_COMPONENT;
          if (idx == 2 || idx == 3) /* parallel 4:4:4:4 */
            val.intVal = VL_FORMAT_DIGITAL_COMPONENT_DUAL;
          if (idx == 4 || idx == 5) /* serial 4:2:2:4 */
            val.intVal = VL_FORMAT_DIGITAL_COMPONENT_SERIAL;
          if (idx == 6 || idx == 7) /* serial 4:4:4:4 */
            val.intVal = VL_FORMAT_DIGITAL_COMPONENT_DUAL_SERIAL;
          
          if (vlSetControl(me->svr, path, source_node, VL_FORMAT, &val) < 0)
            { vlDestroyPath(me->svr, path); return -1; }
        }
      break;
    case SIRIUS_ANALOG_NODE:
      if (idx == VL_ANY)
        {
          idx_base = 0;

          if (vlGetControl(me->svr, path, source_node, VL_FORMAT, &val) < 0)
            { vlDestroyPath(me->svr, path); return -1; }

          switch (val.intVal)
            {
            case VL_FORMAT_RGB:       returned_idx = 0; break;
            case VL_FORMAT_SMPTE_YUV: returned_idx = 1; break;
            case VL_FORMAT_BETACAM:   returned_idx = 2; break;
            case VL_FORMAT_MII:       returned_idx = 3; break;
            case VL_FORMAT_COMPOSITE: returned_idx = 4; break;
            case VL_FORMAT_SVIDEO:    returned_idx = 5; break;
            }
        }
      else
        {
          returned_idx = idx;

          switch (idx)
            {
            case 0: val.intVal = VL_FORMAT_RGB; break;
            case 1: val.intVal = VL_FORMAT_SMPTE_YUV; break;
            case 2: val.intVal = VL_FORMAT_BETACAM; break;
            case 3: val.intVal = VL_FORMAT_MII; break;
            case 4: val.intVal = VL_FORMAT_COMPOSITE; break;
            case 5: val.intVal = VL_FORMAT_SVIDEO; break;
            }
          
          if (vlSetControl(me->svr, path, source_node, VL_FORMAT, &val) < 0)
            { vlDestroyPath(me->svr, path); return -1; }
        }
      break;
    }
      
  assert(idx_base != -1);
  if (ret_idx) *ret_idx = returned_idx + idx_base;
  return 0;
}

int siriusisjackcontrol(IJhandle me, VLControlType type)
{
  return (type==VL_FORMAT);
}

ijdevice siriusdev = 
{ siriussetup, siriusgetjackname, NULL,
  siriusmapidx, siriusconfigurepath, 
  siriusisjackcontrol, NULL };

/* mvp ------------------------------------------------------------- */

typedef struct mvpjack
{
  char *name;
  int number;
} mvpjack;

typedef struct mvpdata
{
  mvpjack *jacks;
  int camera_present;
} mvpdata;

/* this is actually defined in /usr/include/dmedia/vl_mvp.h,
 * but since that include file has only been shipped on IRIX 6.3,
 * and we want ij to be buildable on as many platforms as
 * possible, we do this.
 */
#define VL_MVP_VIDEO_SOURCE_CAMERA 1

int mvpsetup(IJhandle me, VLNode drain_node)
{
  mvpdata *mvp = me->dev_specific = malloc(sizeof(mvpdata));
  int i,j;

  if (!mvp)
    return FALSE;

  me->device_name = "O2 Video";

  me->num_jacks = 0;

  /* count the video input jacks and look for the camera.
   *
   * for some reason, if the camera node is present, the digital node
   * is always also present, even though the driver knows full well that
   * the camera is plugged in and not the digital dongle.  so if we see
   * the camera node, we will not use the digital node.
   */
  mvp->camera_present = 0;
  for (i = 0; i < me->devlist.devices[me->device].numNodes; i++) 
    {
      if ((me->devlist.devices[me->device].nodes[i].type == VL_SRC) &&
          (me->devlist.devices[me->device].nodes[i].kind == VL_VIDEO))
        {
          me->num_jacks++;
          if (me->devlist.devices[me->device].nodes[i].number == 
              VL_MVP_VIDEO_SOURCE_CAMERA)
            mvp->camera_present = 1;
        }
    }

  mvp->jacks = malloc(sizeof(mvpjack) * me->num_jacks);

  /* proceed through list again querying each jack,
   * and skipping the digital node if the camera is present.
   */
  j = 0;
  for (i = 0; i < me->devlist.devices[me->device].numNodes; i++) 
    {
      if ((me->devlist.devices[me->device].nodes[i].type == VL_SRC) &&
          (me->devlist.devices[me->device].nodes[i].kind == VL_VIDEO))
        {
          mvp->jacks[j].name = 
            me->devlist.devices[me->device].nodes[i].name;
          mvp->jacks[j].number = 
            me->devlist.devices[me->device].nodes[i].number;
          
          /* see above for this hack */
          if (mvp->camera_present &&
              !strcmp(mvp->jacks[j].name, "Digital Video Input"))
            continue; 

          /* fixup some lame names */
          if (!strcmp(mvp->jacks[j].name, "Loopback E Video Input"))
            mvp->jacks[j].name = "Video Output Looped Back to Input";
          else if (!strcmp(mvp->jacks[j].name, "Loopback F Video Input"))
            mvp->jacks[j].name = "Alpha Video Output Loopped Back to Input";
          
          j++;
        }
    }

  me->num_jacks = j;

  return TRUE;
}
char *mvpgetjackname(IJhandle me, int idx)
{
  mvpdata *mvp = me->dev_specific;
  return mvp->jacks[idx].name;
}

int mvpgetdefaultsource(IJhandle me)
{
  mvpdata *mvp = me->dev_specific;
  
  int node_number = getdefaultsource(me);
  
  /* mvp sometimes returns the camera node as the default
   * source node even though it's not really available.
   * this is because the mvp driver detects no camera and
   * so doesn't include it in the node list, but when vcp
   * starts up and reads the saved system defaults, it 
   * just blindly uses the saved value for default input
   * source, even if that source is no longer available.
   *
   * work around this by choosing a node that is available
   * if this situation comes up.
   *
   * XXX we should probably also set the default source
   * on the device node so that vcp gets out of this bad
   * state.
   */
  if (node_number == VL_MVP_VIDEO_SOURCE_CAMERA && !mvp->camera_present)
    node_number = mvp->jacks[0].number;
}

/* map idx to node number.  idx is between 0 and me->num_jacks-1. */
int mvpmapidx(IJhandle me, int idx, int *ret_idx_base)
{
  mvpdata *mvp = me->dev_specific;
  return mvp->jacks[idx].number;
}

int mvpconfigurepath(IJhandle me, int idx,
                     VLNode source_node, VLNode drain_node, VLPath path,
                     int node_number, int *ret_idx)
{
  mvpdata *mvp = me->dev_specific;
  VLControlValue val;
  
  /* -- compute idx if idx == VL_ANY */
  if (idx == VL_ANY)
    {
      for(idx=0; idx < me->num_jacks; idx++)
        if (mvp->jacks[idx].number = node_number)
          break;
      assert(idx < me->num_jacks);
    }
  
  if (ret_idx) *ret_idx = idx;
  return 0;
}
int mvpisjackcontrol(IJhandle me, VLControlType type)
{
  return FALSE;
}
void mvpteardown(IJhandle me)
{
  mvpdata *mvp = me->dev_specific;
  free(mvp->jacks);
  free(mvp);
  me->dev_specific = NULL;
}

ijdevice mvpdev = { mvpsetup, mvpgetjackname, mvpgetdefaultsource,
                    mvpmapidx, mvpconfigurepath, 
                    mvpisjackcontrol, mvpteardown };


/* devicename ---------------------------------------------------------- */

ijdevice *vldevname_to_ijdevice(char *vl_device_name)
{
  if (!strcmp(vl_device_name, "vino"))
    return &vinodev;
  else if (!strcmp(vl_device_name, "ev1"))
    return &ev1dev;
  else if (!strcmp(vl_device_name, "impact"))
    return &impactdev;
  else if (!strcmp(vl_device_name, "Sirius"))
    return &siriusdev;
  else if (!strcmp(vl_device_name, "mvp"))
    return &mvpdev;
  else
    return NULL;
}

/* API ------------------------------------------------------------- */

IJhandle ijOpenHandle(VLServer svr,
                      VLDev device,
                      VLNode drain_node)
{
  IJhandle me = malloc(sizeof(_IJhandle));
  
  if (!me) return NULL;

  me->svr = svr;
  me->device = device;

  /* --- get a specific device */

  if (me->device == VL_ANY)
    {
      VLNode src_node;
      VLPath path;
      /* the VL is SO ANNOYING!! the only way to determine which device
       * would be used is to create an actual path!
       */
      if ((src_node = vlGetNode(me->svr, VL_SRC, VL_VIDEO, VL_ANY)) < 0)
        { free(me); return NULL; }
      if ((path = vlCreatePath(me->svr, VL_ANY, src_node, drain_node)) < 0)
        { free(me); return NULL; }
      if (vlSetupPaths(me->svr, (VLPathList)&path, 1, 
                       VL_READ_ONLY, VL_READ_ONLY) < 0)
        { free(me); return NULL; }
      if ((me->device = vlGetDevice(me->svr, path)) < 0)
        { free(me); return NULL; }
      if (vlDestroyPath(me->svr, path) < 0)
        { free(me); return NULL; }
    }
  assert(me->device != VL_ANY);

  /* --- map VL device to IJ device */

  me->ijd = NULL;
  {
    int dv;
    if (vlGetDeviceList(me->svr, &me->devlist) < 0)
      { free(me); return NULL; }
    for (dv=0; dv < (int)me->devlist.numDevices; dv++)
      {
        if (me->devlist.devices[dv].dev == me->device)
          {
            me->ijd = vldevname_to_ijdevice(me->devlist.devices[dv].name);
            break;
          }
      }
  }
  if (!me->ijd)  { free(me); return NULL; }

  /* --- do device-specific setup (inventory etc.) */

  me->num_jacks = -1;
  me->device_name = NULL;

  if (!(*me->ijd->setup)(me, drain_node))  /* this will set me->num_jacks */
    {
      free(me);
      return NULL;
    }

  assert(me->num_jacks != -1);
  assert(me->device_name != NULL);
  
  return me;
}

void ijCloseHandle(IJhandle me)
{
  if (me->ijd->teardown)
    (*me->ijd->teardown)(me);
  /* you are not supposed to free a VLDevList */
}

char *ijGetDeviceName(IJhandle me)
{
  return me->device_name;
}

int ijGetVLDev(IJhandle me)
{
  return me->device;
}

int ijGetNumJacks(IJhandle me)
{
  return me->num_jacks;
}

char *ijGetJackName(IJhandle me, int idx)
{
  if (idx < 0 || idx >= me->num_jacks)
    return NULL;

  return (*me->ijd->getjackname)(me, idx);
}

int ijGetNodeNumber(IJhandle me, int idx)
{
  int node_number;

  if (idx != VL_ANY && (idx < 0 || idx >= me->num_jacks))
    return -1;

  /* -- map idx to node number, or find default node for idx==VL_ANY */
  if (idx == VL_ANY)
    {
      if (me->ijd->getdefaultsource)
        node_number = (*me->ijd->getdefaultsource)(me);
      else
        node_number = getdefaultsource(me);
    }
  else
    node_number = (*me->ijd->mapidx)(me, idx, NULL);

  return node_number;
}

int ijConfigurePath(IJhandle me, int idx,
                    VLNode source_node, VLNode drain_node, VLPath path,
                    int node_number, int *ret_idx)
{
  if (idx != VL_ANY && (idx < 0 || idx >= me->num_jacks))
    return -1;

  if (node_number < 0)
    return -1;
  
  return (*me->ijd->configurepath)(me, idx,
                                   source_node, drain_node, path,
                                   node_number, ret_idx);
}

int ijDoesControlAffectInputJack(IJhandle me, VLControlType type) 
{
  return (*me->ijd->isjackcontrol)(me, type);
}