/*---------------------------------------------------------------------------*\

    AUTHOR..: John Kostogiannis and David Rowe
    DATE....: 4/3/98
    AUTHOR..: Ron Lee
    DATE....: 4/10/06

    This file contains the implementation of several API functions, and
    also quite a few "helper" functions that are used by API functions
    in other files.  

    Can be considered the "main" of the VPBAPI library.
	

         Voicetronix Voice Processing Board (VPB) Software
         Copyright (C) 1999-2008 Voicetronix www.voicetronix.com.au

         This library is free software; you can redistribute it and/or
         modify it under the terms of the GNU Lesser General Public
         License as published by the Free Software Foundation; either
         version 2.1 of the License, or (at your option) any later version.

         This library is distributed in the hope that it will be useful,
         but WITHOUT ANY WARRANTY; without even the implied warranty of
         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
         Lesser General Public License for more details.

         You should have received a copy of the GNU Lesser General Public
         License along with this library; if not, write to the Free Software
         Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

#include "adpcm.h"
#include "config.h"
#include "mess.h"
#include "mapdev.h"
#include "apifunc.h"
#include "playrec.h"
#include "digits.h"
#include "call.h"
#include "vpbdial.h"
#include "vpbtoned.h"
#include "vpbtimer.h"
#include "vpbvox.h"
#include "objtrack.h"
#include "coff.h"
#include "vtcore.h"
#include "alawmulaw.h"
#include "scopedmutex.h"

#ifndef __FreeBSD__
  #include "loopmon.h"
#endif

#ifdef _OPENPRI
 #include "openpri.h"
#endif

#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <csetjmp>
#include <csignal>
#include <fcntl.h>
#include <unistd.h>


#define MESSEVENTDELAY	20		// how often VPBs are polled by driver
#define APIQUEUESIZE	128		// default max num events in API Q

#define	SIZE_OF_VPB_EVENT_WORDS	(sizeof(VPB_EVENT)/sizeof(uint16_t))

static size_t global_apiq_size  = APIQUEUESIZE * SIZE_OF_VPB_EVENT_WORDS;
static size_t channel_apiq_size = APIQUEUESIZE * SIZE_OF_VPB_EVENT_WORDS;


//! @defgroup VPBapiImpl VPBapi implementation detail
//! @ingroup InternalImpl
//! @brief Implementation support found in @c vpbapi.cpp
//!@{
//{{{

struct soft_bridge_info
{ //{{{
	int                 h1;
	int                 h2;
	Fifo               *txdf1;
	Fifo               *txdf2;
	soft_bridge_info   *next;
}; //}}}

//! Device state flags
enum DevState
{ //{{{
	VPB_OFF,    //!< The device is not open.
	VPB_ON	    //!< The device is open.
}; //}}}

//! Container type for device information.
//
//! An array of this type with length @c Totalchans is created during
//! device initialisation.
struct VPB_DEV
{ //{{{
	//! Flag to indicate if this device has been opened or not.
	DevState devstate;

	//! The current event mask
	unsigned long evtmask;

	//! The current tone event mask
	unsigned long tonemask;

	//! Optional event callback
	vpb_event_callback event_callback;

	//! User defined context info for @c event_callback
	void *context;

	//! Per channel API event Q object
	HostFifo APIQ;

	//! Mutex for atomic access to @c event_callback and @c context.
	pthread_mutex_t callback_mutex;


    //! @name Constructors
    //@{ //{{{

	//! Default constructor
	VPB_DEV()
	    : devstate( VPB_OFF )
	    , evtmask( VPB_MALL_EVENTS )
	    , tonemask( VPB_MALL_TONES )
	    , event_callback( NULL )
	    , context( NULL )
	    , APIQ( channel_apiq_size )
	{
		pthread_mutex_init(&callback_mutex, NULL);
	}

	//! Default destructor
	~VPB_DEV()
	{
		pthread_mutex_destroy(&callback_mutex);
	}

    //@} //}}}

}; //}}}


  //! @name Global variables
  //@{

    Comm                 *vpb_c;	//!< Global @c Comm object
    static VPB_DEV       *vpb_dev;      //!< Global @c VPB_DEV array
    static
    soft_bridge_info     *soft_bridges; //!< Global soft bridge list head
    static HostFifo      *APIQ;		//!< Global API event Q object
    static int            Init;		//!< Device initialisation flag
    static unsigned short numboards;	//!< Total number of boards
    static unsigned short Totalchans;	//!< Total number of ports on all boards
    static unsigned short sleepms;	//!< sleep period for threads

  //@}

//!@} //}}}


static pthread_t mmq_thread;


GENERIC_CRITICAL_SECTION	SoftBridgeSect;

// Critical section for put_evt, necessary because the driver via the
// polling timer may call putevt at the same time as the application.

GENERIC_CRITICAL_SECTION	PutEvtSect;


// Tone detector params describing Australian/Studio 308/US dial tone 
// {{{
static VPB_DETECT toned_dial = {
	2,			// number of cadence states
	VPB_DIAL,		// tone id
	1,			// number of tones
	400,			// freq1
	100,			// bandwidth1
	0,			// freq2, N/A
	0,			// bandwidth2, N/A
	-40,			// level1
	0,			// level2, N/A
	0,			// twist, N/A
	10,			// SNR
	40,			// glitchs of 40ms ignored

	{
		{
			VPB_RISING,
			0,
			0,
			0
		},
		{
			VPB_TIMER,		// state 1
			3000,                   // 2s to avoid mix-up with US
			0,                      // US ringback
			0
		}
	}
};
// Aust. Ringback
static VPB_DETECT toned_ringback_aust = {
	4,		  // number of cadence states
	VPB_RINGBACK_AUS, // tone id
	1,		// number of tones
	425,		// freq1
	100,		// bandwidth1
	0,		// freq2, N/A
	0,	        // bandwidth2, N/A
	-20,		// level1
	0,		// level2, N/A
	0,		// twist, N/A
	10,		// SNR
	40,		// glitchs of 40ms ignored

	{
		{
			VPB_RISING,	// state 0			
			0,
			0,
			0
		},

		{
			VPB_FALLING,	// state 1			
			0,
			360,		// ton min		
			440		// ton max		
		},
		{
			VPB_RISING,	// state 2			
			0,
			180,
			220
		},

		{
			VPB_FALLING,	// state 3			
			0,
			360,		// ton min		
			440		// ton max		
		}
	}
};

// Studio 308 ringback

static VPB_DETECT toned_ringback_308 = {
	2,		  // number of cadence states
	VPB_RINGBACK_308, // tone id
	1,		// number of tones
	425,		// freq1
	100,		// bandwidth1
	0,		// freq2, N/A
	0,	        // bandwidth2, N/A
	-20,		// level1
	0,		// level2, N/A
	0,		// twist, N/A
	10,		// SNR
	40,		// glitchs of 40ms ignored

	{
		{
			VPB_RISING,	// state 0			
			0,
			0,
			0
		},

		{
			VPB_FALLING,	// state 1			
			0,
			800,		// ton min		
			1200		// ton max		
		}
	}
};

// message describing state transitions for Studio 308 busy 

static VPB_DETECT toned_busy_308 = {
	3,		// number of states		
	VPB_BUSY_308,	// tone id
	1,		// number of tones		
	425,		// f1:  centre frequency	
	100,		// bw1: bandwidth		
	0,		// f2: N/A			
	0,		// bw2: N/A			
	-20,		// A1: -10 dbm0			
	0,		// A2: N/A 			
	0,		// twist: N/A			
	10,		// SNR: 10dB			
	40,		// glitchs of 40ms ignored

	{
		{
			VPB_RISING,	// state 0			
			0,
			0,
			0
		},

		{
			VPB_FALLING,	// state 1			
			0,
			450,		// 450ms ton min		
			550		// 550ms ton max		
		},

		{
			VPB_RISING,	// state 2			
			0,
			450,		// 450ms toff min		
			550		// 550ms toff max		
		}
	}
};

// message describing state transitions for Australian Busy/Disconnect

static VPB_DETECT toned_busy_aust = {
	3,		 // number of states		
	VPB_BUSY_AUST,   // tone id
	1,		 // number of tones		
	425,		 // f1:  centre frequency	
	100,		 // bw1: bandwidth		
	0,		 // f2: N/A			
	0,		 // bw2: N/A			
	-40,		 // A1: 			
	0,		 // A2: N/A 			
	0,		 // twist: N/A			
	10,		 // SNR: 10dB			
	40,		 // glitchs of 40ms ignored

	{
		{
			VPB_RISING,	 // state 0			
			0,
			0,
			0
		},

		{
			VPB_FALLING,	 // state 1			
			0,
			300,
			450
		},

		{
			VPB_RISING,	 // state 2			
			0,
			300,
			450
		},
	}
};

// grunt detector, looks for wide band energy between 500 and 3000 Hz
// we want it to reliably fire on speech, but not typical call
// progress tones that are below 500Hz.

static VPB_DETECT toned_grunt = {
	3,			// number of states		
	VPB_GRUNT,		// tone id
	1,			// number of tones		
	2000,			// f1:  centre frequency	
	3000,			// bw1: bandwidth		
	0,			// f2: N/A			
	0,			// bw2: N/A			
	-40,			// A1: dbM0		
	0,			// A2: N/A 			
	0,			// twist: N/A			
	0,			// SNR: 10dB			
	40,			// glitchs of 40ms ignored

	{
		{
			VPB_DELAY,		// state 0		
			100,
			0,
			0
		},
		{
			VPB_RISING,		// state 1
			0,
			0,
			0
		},
		{
			VPB_TIMER,	       // state 2
			50,
			0,
			0
		}
	}

};

// US PSTN ringback

static VPB_DETECT toned_ringback_us = {
	2,		// number of cadence states
	VPB_RINGBACK,	// tone id
	1,		// number of tones
	425,		// freq1
	200,		// bandwidth1
	0,		// freq2, N/A
	0,	        // bandwidth2, N/A
	-20,		// level1
	0,		// level2, N/A
	0,		// twist, N/A
	10,		// SNR
	40,		// glitchs of 40ms ignored

	{
		{
			VPB_RISING,	// state 0			
			0,
			0,
			0
		},

		{
			VPB_FALLING,	// state 1			
			0,
			1600,		// ton min		
			2400		// ton max		
		}
	}
};

// US busy tone

static VPB_DETECT toned_busy_us = {
	3,		 // number of states		
	VPB_BUSY,        // tone id
	2,		 // number of tones		
	480,		 // f1:  centre frequency	
	100,		 // bw1: bandwidth		
	620,		 // f2: N/A			
	100,		 // bw2: N/A			
	-20,		 // A1: -10 dbm0			
	-20,		 // A2: N/A 			
	10,		 // twist: N/A			
	10,		 // SNR: 10dB			
	40,		 // glitchs of 40ms ignored

	{
		{
			VPB_RISING,	 // state 0			
			0,
			0,
			0
		},

		{
			VPB_FALLING,	 // state 1			
			0,
			450,
			550
		},

		{
			VPB_RISING,	 // state 2			
			0,
			450,
			550
		},
	}
};

// Tone detector params describing Fax CNG/preamble

static VPB_DETECT toned_fax = {
	2,			// number of cadence states
	VPB_FAX,		// tone id
	1,			// number of tones
	1100,			// freq1
	400,			// bandwidth1
	0,			// freq2, N/A
	0,			// bandwidth2, N/A
	-40,			// level1
	0,			// level2, N/A
	0,			// twist, N/A
	10,			// SNR
	40,			// glitchs of 40ms ignored

	{
		{
			VPB_RISING,	 // state 0			
			0,
			0,
			0
		},

		{
			VPB_FALLING,	 // state 1			
			0,
			450,
			550
		}
	}

};
//}}}


// Predefined tone generator settings for each supported locale
// {{{
static VPB_TONE au_ring2        = {400, 425, 450, -13, -13, -13,  400, 2000, &au_ring2};
static VPB_TONE au_ring         = {400, 425, 450, -13, -13, -13,  400,  200, &au_ring2};

static VPB_TONE au_callwaiting2 = {425,   0,   0, -18,   0,   0,  200, 4400, &au_callwaiting2};
static VPB_TONE au_callwaiting  = {425,   0,   0, -18,   0,   0,  200,  200, &au_callwaiting2};

static VPB_TONE au_tones[] = {
	{ 400, 425, 450, -13, -13, -13, 0, 0, NULL }, // VPB_TONE_DIAL
	au_ring,
	{ 400,   0,   0, -13,   0,   0,  375,  375, &au_tones[VPB_TONE_BUSY] },
	{ 400,   0,   0, -23,   0,   0,  375,  375, &au_tones[VPB_TONE_BUSY] }, // CONGESTION
	{ 400,   0,   0, -13,   0,   0, 2500,  500, &au_tones[VPB_TONE_UNOBTAINABLE] },
	au_callwaiting,
	{ 400, 425, 450, -13, -13, -13,  100,   40, &au_tones[VPB_TONE_STUTTERDIAL] }
};


static VPB_TONE jp_callwaiting2 = {392, 408, 0, -18, -18, 0, 100, 3000, &jp_callwaiting2};
static VPB_TONE jp_callwaiting  = {392, 408, 0, -18, -18, 0, 100,  100, &jp_callwaiting2};

static VPB_TONE jp_tones[] = {
	{ 400,   0,   0, -13,   0,   0,    0,    0, NULL },// VPB_TONE_DIAL
	{ 390, 410,   0, -13, -13,   0, 1000, 2000, &jp_tones[VPB_TONE_RINGING] },
	{ 400,   0,   0, -13,   0,   0,  500,  500, &jp_tones[VPB_TONE_BUSY] },
	jp_tones[VPB_TONE_BUSY],			   // VPB_TONE_CONGESTION
	jp_tones[VPB_TONE_BUSY],			   // VPB_TONE_UNOBTAINABLE
	jp_callwaiting,
	{ 400,   0,   0, -13,   0,   0,  125,  100, &jp_tones[VPB_TONE_STUTTERDIAL] },
};


static VPB_TONE uk_ring2       = {400, 450, 0, -13, -13, 0, 400, 2000, &uk_ring2};
static VPB_TONE uk_ring        = {400, 450, 0, -13, -13, 0, 400,  200, &uk_ring2};

static VPB_TONE uk_congestion2 = {400,   0, 0, -13,   0, 0, 225,  525, &uk_congestion2};
static VPB_TONE uk_congestion  = {400,   0, 0, -13,   0, 0, 400,  350, &uk_congestion2};

static VPB_TONE uk_tones[] = {
	{ 350, 440,   0, -13, -13,   0,   0,    0, NULL },// VPB_TONE_DIAL
	uk_ring,
	au_tones[VPB_TONE_BUSY],
	uk_congestion,
	{ 400,   0,   0, -13,   0,   0,   0,    0, NULL },// VPB_TONE_UNOBTAINABLE
	{ 400,   0,   0, -13,   0,   0, 100, 2000, &uk_tones[VPB_TONE_CALLWAITING] },
	{ 350, 440,   0, -13, -13,   0, 750,  750, &uk_tones[VPB_TONE_STUTTERDIAL] }
};


static VPB_TONE sg_dial         = {425,   0, 0, -15,   0, 0,    0,   0, NULL};

static VPB_TONE sg_ring2        = {400, 450, 0, -10, -10, 0, 400, 2000, &sg_ring2};
static VPB_TONE sg_ring         = {400, 450, 0, -10, -10, 0, 400,  200, &sg_ring2};

static VPB_TONE sg_callwaiting2 = {425,   0, 0, -18,   0, 0,  300, 3200, &sg_callwaiting2};
static VPB_TONE sg_callwaiting  = {425,   0, 0, -18,   0, 0,  300,  200, &sg_callwaiting2};

static VPB_TONE sg_stutter8     = {425,   0, 0, -15,   0, 0, 100, 100, &sg_dial};
static VPB_TONE sg_stutter7     = {425,   0, 0, -15,   0, 0, 600, 200, &sg_stutter8};
static VPB_TONE sg_stutter6     = {425,   0, 0, -15,   0, 0, 200, 200, &sg_stutter7};
static VPB_TONE sg_stutter5     = {425,   0, 0, -15,   0, 0, 600, 200, &sg_stutter6};
static VPB_TONE sg_stutter4     = {425,   0, 0, -15,   0, 0, 200, 200, &sg_stutter5};
static VPB_TONE sg_stutter3     = {425,   0, 0, -15,   0, 0, 600, 200, &sg_stutter4};
static VPB_TONE sg_stutter2     = {425,   0, 0, -15,   0, 0, 200, 200, &sg_stutter3};
static VPB_TONE sg_stutter1     = {425,   0, 0, -15,   0, 0, 600, 200, &sg_stutter2};
static VPB_TONE sg_stutter      = {425,   0, 0, -15,   0, 0, 200, 200, &sg_stutter1};

static VPB_TONE sg_tones[] = {
	sg_dial,
	sg_ring,
	{ 425,   0, 0, -10,   0, 0,  750, 750, &sg_tones[VPB_TONE_BUSY] },
	{ 425,   0, 0, -10,   0, 0,  250, 250, &sg_tones[VPB_TONE_CONGESTION] },
	{ 425,   0, 0, -10,   0, 0, 2500, 550, &sg_tones[VPB_TONE_UNOBTAINABLE] },
	sg_callwaiting,
	sg_stutter
};


static VPB_TONE us_dial     = { 350, 440, 0, -13, -13, 0,   0,   0, NULL };

static VPB_TONE us_sit2     = {1800,   0, 0, -13,   0, 0, 330, 0, NULL};
static VPB_TONE us_sit1     = {1400,   0, 0, -13,   0, 0, 330, 0, &us_sit2};
static VPB_TONE us_sit      = { 950,   0, 0, -13,   0, 0, 330, 0, &us_sit1};

static VPB_TONE us_stutter2 = { 350, 440, 0, -13, -13, 0, 100, 100, &us_dial };
static VPB_TONE us_stutter1 = { 350, 440, 0, -13, -13, 0, 100, 100, &us_stutter2 };
static VPB_TONE us_stutter  = { 350, 440, 0, -13, -13, 0, 100, 100, &us_stutter1 };

static VPB_TONE us_tones[] = {
	us_dial,
	{ 440, 480,   0, -19, -19,   0, 2000,  4000, &us_tones[VPB_TONE_RINGING] },
	{ 480, 620,   0, -24, -24,   0,  500,   500, &us_tones[VPB_TONE_BUSY] },
      // use the 'reorder' tone instead of this one?
      //{ 480, 620,   0, -24, -24,   0,  300,   200, &us_tones[VPB_TONE_CONGESTION] },
	{ 480, 620,   0, -24, -24,   0,  250,   250, &us_tones[VPB_TONE_CONGESTION] },
	us_sit,						    // VPB_TONE_UNOBTAINABLE
	{ 440,   0,   0, -13,   0,   0,  300, 10000, &us_tones[VPB_TONE_CALLWAITING] },
	us_stutter
};
//}}}


static Country Countries[] =
{ //{{{
	{ name:          "AUSTRALIA",
	  code:          61,
	  ring_cadence:  2,
	  flash_time:    120,
	  flash_min:     40,
	  flash_max:     140,
	  lv_onhook:     20,
	  lv_offhook:    11,
	  drop_time:     500,
	  drop_min:      100,
	  drop_max:      800,
	  fsk_mark:      0.942477769,   // 1200Hz
	  fsk_space:     1.72787596,    // 2200Hz
	  tone_gen:      au_tones
	},
	{ name:          "JAPAN",
	  code:          81,
	  ring_cadence:  10,
	  flash_time:    120,
	  flash_min:     40,
	  flash_max:     140,
	  lv_onhook:     20,
	  lv_offhook:    11,
	  drop_time:     500,
	  drop_min:      100,
	  drop_max:      800,
	  fsk_mark:      1.021017612,   // 1300Hz
	  fsk_space:     1.649336143,   // 2100Hz
	  tone_gen:      jp_tones
	},
	{ name:          "SINGAPORE",
	  code:          65,
	  ring_cadence:  2,
	  flash_time:    120,
	  flash_min:     40,
	  flash_max:     140,
	  lv_onhook:     20,
	  lv_offhook:    11,
	  drop_time:     500,
	  drop_min:      100,
	  drop_max:      800,
	  fsk_mark:      1.021017612,
	  fsk_space:     1.649336143,
	  tone_gen:      sg_tones
	},
	{ name:          "UK",
	  code:          44,
	  ring_cadence:  2,
	  flash_time:    120,
	  flash_min:     40,
	  flash_max:     140,
	  lv_onhook:     20,
	  lv_offhook:    11,
	  drop_time:     500,
	  drop_min:      100,
	  drop_max:      800,
	  fsk_mark:      1.021017612,
	  fsk_space:     1.649336143,
	  tone_gen:      uk_tones
	},
	{ name:          "USA",
	  code:          1,
	  ring_cadence:  10,
	  flash_time:    600,
	  flash_min:     400,
	  flash_max:     1600,
	  lv_onhook:     20,
	  lv_offhook:    11,
	  drop_time:     500,
	  drop_min:      100,
	  drop_max:      2500,
	  fsk_mark:      0.942477769,
	  fsk_space:     1.72787596,
	  tone_gen:      us_tones
	}
}; //}}}

static const Country *default_country = &Countries[0];


/* Soft bridge functions */
static soft_bridge_info* WINAPI soft_bridge_new(void);
static soft_bridge_info* WINAPI soft_bridge_find(int h1, int h2);
static soft_bridge_info* WINAPI soft_bridge_find(int h1);
static void WINAPI soft_bridge_delete(soft_bridge_info *bp);


VpbException::VpbException(const char *format, ...)
{ //{{{
	va_list arglist;
	char   *buf;

	va_start( arglist, format );

	int len = vasprintf( &buf, format, arglist );
	if( len >= 0 ) {
		if( len > 0 ) m_msg = buf;
		free( buf );
	}

	va_end( arglist );
} //}}}


// This function should be preferred to using ValidHandleCheck in a try catch wrapper.
void CheckHandle(VPBPortHandle handle, const char *where)
{ //{{{
	if(handle < 0 || handle >= Totalchans || vpb_dev[handle].devstate == VPB_OFF)
		throw VpbException("invalid_handle (%d) from %s", handle, where);
} //}}}

// XXX Don't use this in new code, see above.
void ValidHandleCheck(VPBPortHandle handle)
{ //{{{
	if(handle < 0 || handle >= Totalchans || vpb_dev[handle].devstate == VPB_OFF)
		throw Wobbly(VPBAPI_INVALID_HANDLE);
} //}}}


const char* WINAPI vpb_get_driver_version()
{ //{{{
	return VT_VERSION;
} //}}}

void WINAPI vpb_get_driver_version(int *major, int *minor, int *patchlevel)
{ //{{{
	char s[VPB_MAX_STR];
	char *pstart, *p;

	sprintf(s, VT_VERSION);

	pstart = s;
	p      = strchr(pstart, '.');
	*major = atoi(pstart);
	if (p == NULL){
		*minor      = -1;
		*patchlevel = -1;
		return;
	}

	*p     = 0;
	pstart = p + 1;
	p      = strchr(pstart, '.');
	if (p != NULL){
		*p          = 0;
		*minor      = atoi(pstart);
		pstart      = p+1;
		*patchlevel = atoi(pstart);
	} else {
		*minor      = -1;
		*patchlevel = -1;
	}
} //}}}


void WINAPI vpb_set_global_apiq_size( int size )
{ //{{{
	if(APIQ) throw VpbException("Cannot set the global APIQ size after init");
	global_apiq_size = size * SIZE_OF_VPB_EVENT_WORDS;
} //}}}

void WINAPI vpb_set_channel_apiq_size( int size )
{ //{{{
	if(vpb_dev) throw VpbException("Cannot set the channel APIQ size after init");
	channel_apiq_size = size * SIZE_OF_VPB_EVENT_WORDS;
} //}}}


// For internal use.
void set_country(int board, int port, const Country *country)
{ //{{{
	VPBREG	&reg = *vpb_c->vpbreg(board);
	reg.country[port] = country;
	reg.hostdsp->SetCountry(port, country);

	mprintf("[%d/%d] port country %s\n", board, port, country->name);
} //}}}

// For internal use, does not validate the handle.
static void set_country(VPBPortHandle handle, const Country *country)
{ //{{{
	unsigned short  bd, ch;

	maphndletodev(handle, &bd, &ch);
	set_country(bd, ch, country);
} //}}}

// For internal use
const Country *get_country(int board, int port)
{ //{{{
	const Country *c = vpb_c->vpbreg(board)->country[port];

	return c ? c : default_country;
} //}}}

// For internal use, does not validate the handle.
static const Country *get_country(VPBPortHandle handle)
{ //{{{
	unsigned short  bd, ch;
	maphndletodev(handle, &bd, &ch);

	const Country *c = vpb_c->vpbreg(bd)->country[ch];

	return c ? c : default_country;
} //}}}

void WINAPI vpb_set_country(const Country *country)
{ //{{{
	default_country = country ? country : &Countries[0];

	for( int b = 0, be = vpb_c->GetBoardCount(); b < be; ++b ) {
		VPBREG	&reg = *vpb_c->vpbreg(b);
		for( int p = 0, pe = reg.numch; p < pe; ++p )
		{
			if( reg.country[p] == NULL ) {
				reg.hostdsp->SetCountry(p, default_country);
				mprintf("[%d/%d] default country %s\n",
					b, p, default_country->name);
			}
		}
	}
} //}}}

int WINAPI vpb_set_country(VPBPortHandle handle, const Country *country)
{ //{{{
	CheckHandle(handle, "vpb_set_country");
	set_country(handle, country);

	return VPB_OK;
} //}}}

const Country* WINAPI vpb_get_port_country(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_get_port_country");
	return get_country(handle);
} //}}}

const Country* WINAPI vpb_get_country_data(const std::string &name)
{ //{{{
	for( size_t i = 0; i < sizeof(Countries)/sizeof(*Countries); ++i )
		if( name == Countries[i].name ) return &Countries[i];

	return NULL;
} //}}}

const Country* WINAPI vpb_get_country_data(int code)
{ //{{{
	for( size_t i = 0; i < sizeof(Countries)/sizeof(*Countries); ++i )
		if( code == Countries[i].code ) return &Countries[i];

	return NULL;
} //}}}

// For internal use.
void set_ps_impedance(int board, int port, int impedance)
{ //{{{
	VPBREG	&reg = *vpb_c->vpbreg(board);
	reg.hostdsp->SetPSImpedance(port, impedance);
} //}}}

void set_vdaa_impedance(int board, int port, int impedance)
{ //{{{
	VPBREG	&reg = *vpb_c->vpbreg(board);
	reg.hostdsp->SetVDAAImpedance(port, impedance);
} //}}}


// Given a message from a VPB, this function determines how to process it.
static void ProcessVPBMessage(uint16_t mess[], unsigned short board)
{ //{{{
	VPB_EVENT   e;

	//mprintf("Copped an event mess[1] %d\n", mess[1]);

	switch( mess[1] )
	{
	    case DSP_PONG:
		mprintf("PONG");
		break;

            #if 0
	    case DSP_ERROR:
    		assert(0);
		break;
            #endif

	    case DSP_TONED:
		assert(mess[0] == DSP_LTONED);
		e.type = VPB_TONEDETECT;
		e.handle = objtrack_id_to_handle(CPOBJ, mess[2], board);
		e.data = mess[3];
		// Check if it is masked
		if( (1<<e.data) & vpb_dev[e.handle].tonemask ){
			putevt(&e, VPB_MTONEDETECT);
			// send tone info to call progress detector
			call_new_tone(e.handle, e.data);
		}
		break;

            #if 0
	    case DSP_TONEDOK:
		mprintf("tone OK\n");
		break;
            #endif

	    case DSP_TONEG:
	    {
		VPBREG	&reg = *vpb_c->vpbreg(board);
		if( reg.model == VPB_V4PCI ) {
			unsigned short brd;
			unsigned short prt;
			maphndletodev(objtrack_id_to_handle(TONEOBJ,mess[2],board), &brd, &prt);
			reg.toneg[prt]->SignalCompletion();
		} else
			reg.toneg[mess[2]]->SignalCompletion();
		break;
	    }
	    case DSP_CODEC_BKEN:
	    {
		VPBREG	&reg = *vpb_c->vpbreg(board);

		// OpenLine sends this signal before the line has settled
		// wait for it to do so before we signal the user code that
		// it is ok to proceed with other operations.
		if( reg.model == VPB_V4PCI ) usleep( 60 * 1000 );

		reg.toneg[mess[2]]->SignalCompletion();
		break;
	    }

	    case DSP_CONFIG_AF:
		break;

	    case DSP_DTMF:
		assert(mess[0] == DSP_LDTMF);
		e.handle = objtrack_id_to_handle(DTMFOBJ, mess[2], board);
		e.type = VPB_DTMF;
		e.data = mess[3];
		putevt(&e, VPB_MDTMF);

		/* Note we use this event (DTMF key released) to
		   record new DTMFs as DTMF detection is more accurate
		   than with the DSP_DTMF_DOWN event, as we have had longer
		   to analyse the DTMF digit. */
		digits_new_digit(e.handle, mess[3]);
		break;

	    case DSP_DTMF_DOWN:
		assert(mess[0] == DSP_LDTMF_DOWN);
		e.handle = objtrack_id_to_handle(DTMFOBJ, mess[2], board);
		e.type = VPB_DTMF_DOWN;
		e.data = mess[3];
		putevt(&e, VPB_MDTMF_DOWN);

		/* Note we use this event to terminate rather than DSP_DTMF.
		   This is because there is a small, known delay between when the
		   key is pressed and when we get this event.  This makes removing
		   the DTMF samples from the recorded audio straight forward.  If
		   we used DSP_DTMF event to terminate record, there would be an
		   unknown delay between when the tone started and when the event
		   occurred.
		*/
		playrec_new_digit_record(e.handle, mess[3]);
		playrec_new_digit_play(e.handle, mess[3]);
		break;

	    case DSP_CONFIG_FUO:
		// Not processed anywhere.
	      #if 0
		assert(mess[0] == DSP_LCONFIG_FDU);
		e.type = VPB_RECORD_OVERFLOW;
		e.handle = objtrack_id_to_handle(FIFOUPOBJ, mess[2], board);
		e.data = 0;
		putevt(&e, VPB_MRECORD_OVERFLOW);
	      #endif  //XXX
		break;

	    case DSP_CONFIG_FDU:
		// Not processed anywhere.
	      #if 0
		assert(mess[0] == DSP_LCONFIG_FDU);
		e.type = VPB_PLAY_UNDERFLOW;
		e.handle = objtrack_id_to_handle(FIFODNOBJ, mess[2], board);
		e.data = 0;
		if (playrec_underflow_valid(e.handle))
			putevt(&e, VPB_MPLAY_UNDERFLOW);
	      #endif  //XXX
		break;

	    case DSP_COMM_MOF:
		break;

	    case DSP_CODEC_RING:
		assert(mess[0] == DSP_LCODEC_RING);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_RING;
		e.data = 0;
		putevt(&e, VPB_MRING);
		break;

	    case DSP_RING_OFF:
		assert(mess[0] == DSP_LRING_OFF);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_RING_OFF;
		e.data = 0;
		putevt(&e, VPB_MRING_OFF);
		break;

	    case DSP_DROP:
		assert(mess[0] == DSP_LDROP);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_DROP;
		e.data = 0;
		putevt(&e, VPB_MDROP);
		break;

	    case DSP_TONED_LOG:
		mprintf("toned debug log\n");
		toned_debug_log(board, mess);
		break;

	    case DSP_DEBUG_LONG:
		// A 16 bit value shifted by 16 bits isn't exactly long anymore
		mprintf("debug long[%d] %d\n", mess[2], (mess[3]<<16) + mess[4]);
		break;

	    case DSP_DEBUG_ARRAY:
		mprintf("\ndebug array[%d]:",mess[2]);
		for(int i=0; i < mess[3]; ++i)
			mprintf("0x%04x ",mess[4+i]);
		break;

	    case DSP_DEBUG_STACK:
		mprintf("debug stack[%d] 0x%x\n", mess[2], mess[3]);
		break;

	    case DSP_VOXON:
		assert(mess[0] == DSP_LVOXON);
		e.type = VPB_VOXON;
		e.handle = objtrack_id_to_handle(VOXOBJ, mess[2], board);
		e.data = GenerictimeGetTime();
		putevt(&e, VPB_MVOXON);
		break;

	    case DSP_VOXOFF:
		assert(mess[0] == DSP_LVOXOFF);
		e.type = VPB_VOXOFF;
		e.handle = objtrack_id_to_handle(VOXOBJ, mess[2], board);
		e.data = GenerictimeGetTime();
		putevt(&e, VPB_MVOXOFF);
		break;

	    case DSP_SPI_LOAD:
	    {
		float scale = 40.0 / mess[10];
		long  sum   = mess[6];
		sum <<= 16;
		sum += mess[7];
		long num = mess[8];
		num <<= 16;
		num += mess[9];
		mprintf("[%02d] sam: %3.2f MIPs max: %3.2f MIPs av: %3.2f MIPs\n",
			board, scale*mess[3], scale*mess[5], scale*(float)sum/num);
		break;
	    }
	#if 0
	    case DSP_FIFODIS:
		//mprintf("DSP_FIFODIS");
		assert(mess[0] == DSP_LFIFODIS);
		h = objtrack_id_to_handle(FIFOUPOBJ, mess[2], board);
		// DR 10/3/02 - not used naymore
		// playrec_fifo_disabled(h);
		break;
	#endif
	    case DSP_CODEC_HKOFF:
		assert(mess[0] == DSP_LCODEC_HKOFF);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_STATION_OFFHOOK;
		e.data = 0;
		putevt(&e, VPB_MSTATION_OFFHOOK);
		break;

	    case DSP_CODEC_HKON:
		assert(mess[0] == DSP_LCODEC_HKON);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_STATION_ONHOOK;
		e.data = 0;
		putevt(&e, VPB_MSTATION_ONHOOK);
		break;

	    case DSP_CODEC_FLASH:
		assert(mess[0] == DSP_LCODEC_FLASH);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_STATION_FLASH;
		e.data = 0;
		putevt(&e, VPB_MSTATION_FLASH);
		break;

	    case DSP_ISDN_RING:
		assert(mess[0] == DSP_LISDN_RING);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_RING;
		e.data = 0;
		putevt(&e);
		break;

	    case DSP_ISDN_PROCEEDING:
		assert(mess[0] == DSP_LISDN_PROCEEDING);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_ISDN_PROCEEDING;
		e.data = 0;
		putevt(&e);
		break;

	    case DSP_ISDN_RINGING:
		assert(mess[0] == DSP_LISDN_RINGING);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_ISDN_RINGING;
		e.data = 0;
		putevt(&e);
		break;

	    case DSP_ISDN_ANS:
		assert(mess[0] == DSP_LISDN_ANS);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_ISDN_ANS;
		e.data = 0;
		putevt(&e);
		break;

	    case DSP_ISDN_BUSY:
		assert(mess[0] == DSP_LISDN_BUSY);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_ISDN_BUSY;
		e.data = mess[3];
		putevt(&e);
		break;

	    case DSP_ISDN_GET_CINFO:
		assert(mess[0] == DSP_LISDN_GET_CINFO);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_ISDN_CINFO;
		e.data = 0;
		putevt(&e);
		break;

	    case DSP_ISDN_CALL_FAIL:
		assert(mess[0] == DSP_LISDN_CALL_FAIL);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_ISDN_CALL_FAIL;
		e.data = mess[3];
		putevt(&e);
		break;

	    case DSP_ISDN_ANS_FAIL:
		assert(mess[0] == DSP_LISDN_ANS_FAIL);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_ISDN_ANS_FAIL;
		e.data = mess[3];
		putevt(&e);
		break;

	    case DSP_LOOP_OFFHOOK:
		assert(mess[0] == DSP_LLOOP_OFFHOOK);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_LOOP_OFFHOOK;
		e.data = mess[3];
		putevt(&e);
		break;

	    case DSP_LOOP_ONHOOK:
		assert(mess[0] == DSP_LLOOP_ONHOOK);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_LOOP_ONHOOK;
		e.data = mess[3];
		putevt(&e);
		break;

	    case DSP_LOOP_POLARITY:
		assert(mess[0] == DSP_LLOOP_POLARITY);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_LOOP_POLARITY;
		e.data = mess[3];
		putevt(&e);
		break;

	    case DSP_LOOP_FLASH:
		assert(mess[0] == DSP_LLOOP_FLASH);
		e.handle = mapdevtohndle(board, mess[2]);
		e.type = VPB_LOOP_FLASH;
		e.data = 0;
		putevt(&e, VPB_MLOOP_FLASH);
		break;

	    default:
                throw VpbException("ProcessVPBMessage: bad message %d", mess[1]);
	} // switch
} //}}}

// This function monitors the message queues for each board in
// the system.  This function is called periodically by the operating
// system, it then polls each VPB for messages and processes the events.
static void *MonitorMessageQueue(void *unused)
{ //{{{
	(void)unused;

	mprintf("MonitorMessageQueue thread started %#lx\n", pthread_self());
	try {
		for(;;) {
			for(int i=0; i < numboards; ++i)
			{
				// message from DSP to PC
				uint16_t  mess[COMM_MAX_MESSDSPPC];

				while(vpb_c->GetMessageVPB(i,mess) == VPB_OK)
					ProcessVPBMessage(mess, i);
			}

			vpbtimer_check_active_timers();

			// check async get digits for time outs
			digits_check_timers();

			// check call progress timers
			call_check_timer();

			GenericSleep(MESSEVENTDELAY);
		}
	}
	catch(const Wobbly &w) {
		char s[MAX_STR];
		w.translate(s);

		if (w.file[0] != 0)
			mprintf("exception caught: %s, %s, line = %d\n",
				s, w.file, w.line);
		else	mprintf("exception caught: %s\n",s);
	}

	// We should never get here under normal circumstances
	mprintf("MMQ thread: abnormal exit\n");
	return NULL;
} //}}}


// Configures all channels of a 4 channel VPB card.
static void ConfigVPB4( Comm *c, int b )
{ //{{{
 	int	 ch;			// channel
	int	 obj = 0;		// object counter
	int	 handle;		// handle of this channel
	int	 stobj;			// start object for this channel
	VPBREG	 *v = c->vpbreg(b);     // reg info for this card
	VPB_MODEL model = get_board_type(b);

	for(ch=0; ch < v->numch; ++ch) {
		handle = mapdevtohndle(b,ch);

//		mprintf("Configuring dev: %d  brd: %d  ch: %d\n",handle,b,ch);

		// Up (channel to PC) signal processing objects

//		mprintf(" dev: %d :Up channel objects, obj:%d\n",handle, obj);

		objtrack_add_object(UPOBJ, obj, handle, b);
		config_create_object(c, b, CODEC_AD , obj++, ch, 0);	// 0
		config_create_object(c, b, ALAW2LIN , obj++, ch, 0);	// 1
		objtrack_add_object(ECHOOBJ, obj, handle, b);
		config_create_object(c, b, ECHO     , obj++, ch, 0);	// 2
		objtrack_add_object(DTMFOBJ, obj, handle, b);
		config_create_object(c, b, DTMF     , obj++, ch, 0);	// 3
		objtrack_add_object(CPOBJ, obj, handle, b);
		config_create_object(c, b, TONED    , obj++, ch, 0);	// 4
		config_create_object(c, b, LIN2ALAW , obj++, ch, 0);	// 5
		config_create_object(c, b, LIN2MULAW, obj++, ch, 0);	// 6
		config_create_object(c, b, LIN2ADPCM, obj++, ch, 0);	// 7
		objtrack_add_object(VOXOBJ, obj, handle, b);
		config_create_object(c, b, VOX	    , obj++, ch, 0);	// 8
		config_create_object(c, b, PACK	    , obj++, ch, 0);	// 9
		objtrack_add_object(FIFOUPOBJ, obj, handle, b);
		config_create_object(c, b, PACKED_FIFO_UP, obj++, ch, v->szrxdf[ch]);	// 10

		// Down (PC to channel) signal processing objects

		mprintf(" dev: %d :Down channel objects, obj:%d\n",handle,obj);

		objtrack_add_object(FIFODNOBJ, obj, handle, b);
		objtrack_add_object(DNOBJ, obj, handle, b);
		config_create_object(c, b, PACKED_FIFO_DOWN, obj++, ch,
				     v->sztxdf[ch]);	                // 0
		config_create_object(c, b, UNPACK, obj++, ch, 0);	// 1
		objtrack_add_object(TONEOBJ, obj, handle, b);
		config_create_object(c, b, TONEG    , obj++, ch, 0);	// 2
		config_create_object(c, b, MULAW2LIN , obj++, ch, 0);	// 3

		/* DR 8/3/02 - loose this, replace with adder to keep obj #
		   the same
		config_create_object(c, b, ADPCM2LIN , obj++, ch, 0);	// 5
		*/
		config_create_object(c, b, ADDER , obj++, ch, 0);	// 4

		config_create_object(c, b, LIN2ALAW , obj++, ch, 0);	// 5
		config_create_object(c, b, CODEC_DA , obj++, ch, 0);	// 6
		config_create_object(c, b, ALAW2LIN , obj++, ch, 0);	// 7
		objtrack_add_object(DELAYOBJ, obj, handle, b);
		config_create_object(c, b, DELAY    , obj++, ch, 0);	// 8

		// Up side wiring 

		stobj = objtrack_handle_to_id(UPOBJ, handle);
		config_create_wire(c, b, stobj+0, stobj+1);
		config_create_wire(c, b, stobj+1, stobj+2);

		// DSP based echo canceller
		config_create_wire(c, b, stobj+2, stobj+3);
		config_create_wire(c, b, stobj+2, stobj+4);
		config_create_wire(c, b, stobj+2, stobj+5);
		config_create_wire(c, b, stobj+2, stobj+6);
		config_create_wire(c, b, stobj+2, stobj+7);
		config_create_wire(c, b, stobj+2, stobj+8);
		config_create_wire(vpb_c, b, stobj+2, stobj+10);

		config_packrate(vpb_c, b, stobj+10, 1);

		// Down side wiring

		stobj = objtrack_handle_to_id(DNOBJ, handle);
		config_create_wire(c, b, stobj+0, stobj+1);    // FIFO->UNPACK
		config_create_wire(c, b, stobj+1, stobj+3);
		config_create_wire(c, b, stobj+5, stobj+7);
		// DR 8/3/02 config_create_wire(c, b, stobj+1, stobj+5);
		config_create_wire(c, b, stobj+4, stobj+8);
		//config_create_wire(c, b, stobj+5, stobj+8);
		/* DR 8/3/02
		config_connect_to_zerobuf(c, b, stobj+6);
		config_connect_to_zerobuf(c, b, stobj+8);
		*/
		// DR 8/3/02 - set up linear
		#ifdef LIN1
		config_create_wire(vpb_c, b, stobj+0, stobj+6);
		config_create_wire(vpb_c, b, stobj+0, stobj+8);
		config_packrate(vpb_c, b, stobj, 1);
		#else
		config_create_wire(vpb_c, b, stobj+0, stobj+4);
		config_create_wire(vpb_c, b, stobj+2, stobj+4);
		config_create_wire(vpb_c, b, stobj+4, stobj+5);
		config_create_wire(vpb_c, b, stobj+5, stobj+6);
		config_packrate(vpb_c, b, stobj, 1);
		#endif

		// connect echo canceller

		int dest = objtrack_handle_to_id(ECHOOBJ, handle);
		stobj = objtrack_handle_to_id(DELAYOBJ, handle);
		config_create_wire(c, b, stobj, dest);

		// Configure call progress detector for this channel

		mprintf(" dev: %d :call Progress state machines\n",handle);

		// set default prog tone dets,

		settonedet(handle, toned_dial);
		settonedet(handle, toned_ringback_us);
		settonedet(handle, toned_busy_us);
		settonedet(handle, toned_grunt);
		settonedet(handle, toned_ringback_308);
		settonedet(handle, toned_busy_308);
		settonedet(handle, toned_fax);
		settonedet(handle, toned_busy_aust);

		// look for tones specified by environment variables
		read_tones_from_env(handle);

		// Disable UP objects

		stobj = objtrack_handle_to_id(UPOBJ, handle);
		#define OLD1
		#ifdef OLD1
		//config_disable_object(c, b, stobj+4);	// TONED
		config_disable_object(c, b, stobj+5);	// ALAW
		config_disable_object(c, b, stobj+6);	// MULAW
		config_disable_object(c, b, stobj+7);	// ADPCM

		if(model != VPB_V4LOG)
			config_disable_object(c, b, stobj+8);	// VOX

		config_disable_object(c, b, stobj+9);	// PACK
		//config_disable_object(c, b, stobj+10);	// FIFO
		#endif

		// Disable down objects except D/A, LIN2ALAW, and DELAY
		#define OLD2
		#ifdef OLD2
		stobj = objtrack_handle_to_id(DNOBJ, handle);
		//config_disable_object(c, b, stobj);	// FIFO
		config_disable_object(c, b, stobj+1);	// UNPACK
		//config_disable_object(c, b, stobj+2);	// TONEG
		config_disable_object(c, b, stobj+3);	// ALAW
		//config_disable_object(c, b, stobj+4);	// ADDER
		config_disable_object(c, b, stobj+7);	// MULAW
		//config_disable_object(c, b, stobj+5);	// ADPCM
		#endif
	}
} //}}}

/*-------------------------------------------------------------------------*\

	FUNCTION:	ConfigureVPB 
	AUTHOR..:	David Rowe
	DATE....:	6/2/98
	
	This function is called during system initialisation to create and
	connect the configuration objects that perform the signal processing
	operations for each channel.

	This function should be called before the timer VPB polling function
	MonitorMessageQueue() is started, as this function directly reads the
	VPB message queue.

	Comm    *c;		ptr to comm object for VPB(s)
	unsigned short  numboards;	number of boards in system

\*-------------------------------------------------------------------------*/

static void ConfigureVPB(Comm *c, unsigned short numboards)
{ //{{{
	objtrack_open();

	mprintf("Init tone detector for %d channels...\n", Totalchans);
	vpbtoned_open(Totalchans);

	for(int b=0; b < numboards; ++b)
	{
		VPBREG	*v = c->vpbreg(b);

		switch(v->ddmodel) {
		    case DD_PCI:
			ConfigVPB4(c, b);
			mprintf("[%d/-] Starting config manager\n",b);
			config_run(c, b);
			mprintf("[%d/-] napping while the board starts...\n",b);
			GenericSleep(1000);
			break;

		    case DD_VTCORE:
		    case DD_PRI:
			for(int ch=0; ch < v->numch; ++ch)
			{
				int handle = mapdevtohndle(b,ch);

				settonedet(handle, toned_dial);
				settonedet(handle, toned_ringback_us);
				settonedet(handle, toned_busy_us);
				settonedet(handle, toned_grunt);
				settonedet(handle, toned_ringback_308);
				settonedet(handle, toned_ringback_aust);
				settonedet(handle, toned_busy_308);
				settonedet(handle, toned_fax);
				settonedet(handle, toned_busy_aust);

				read_tones_from_env(handle);

				//mprintf("[%d/%d] mapped to handle %d\n",
				//	b, ch, handle);
			}
			break;

		    default:
			assert(0);
		}
	}
} //}}}

// internal driver initialisation function
static void init_vpb()
{ //{{{
	static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
	ScopedMutex		lock(&mutex);

	if(Init) return;

	mess_init();

	mprintf("Initialising Driver...\n");

	// Initialise alaw and mulaw conversion lookup tables
	init_alaw_luts();
	init_mulaw_luts();

	// Open comms, this:
	// 1. boots DSP on each VPB card
	// 2. reads parameters from the windows registry into the
	//    VPB registry structure.
	// 3. downloads vpb reg parameters to each DSP.

	// We do two stage initialisation of the Comm object
	// so that the global instance and VPBRegister are
	// available to the boards during their initialisation.
	vpb_c = new Comm;
	vpb_c->InitBoards();

	Totalchans = 0;
	numboards = vpb_c->GetBoardCount();

	for(int i=0; i < numboards; ++i)
		Totalchans += vpb_c->vpbreg(i)->numch;

	vpb_dev = new VPB_DEV[Totalchans];

	// Now configure each channel of each VPB.
	// This sets up the signal processing modules such as
	// FIFOs, codec I/O, dtmf and call progress detection etc

	ConfigureVPB(vpb_c, numboards);

	playrec_open(Totalchans);

	// Init tone generator
	vpbdial_open();

	// Init timer module
	vpbtimer_open();

	// DR 24/2/03 - enable VOX for OpenLine4
	for(int i=0; i < numboards; ++i) {
		if( vpb_c->vpbreg(i)->ddmodel == DD_PCI )
		{
			vpbvox_open(Totalchans);
			break;
		}
	}

	sleepms = 20;

	digits_open(Totalchans);
	call_open(Totalchans);

	APIQ = new HostFifo(global_apiq_size);

	GenericInitializeCriticalSection(&PutEvtSect);
	GenericInitializeCriticalSection(&SoftBridgeSect);

      #ifndef __FreeBSD__
	//XXX this can be bogus, but will probably be gone
	//    before its really an issue.
	switch( get_board_type(0) )
	{
	    case VPB_V4PCI:
	    case VPB_V4LOG:
		StartLoopSenseThread(Totalchans);

	    default: ;
	}
      #endif

	int ret = pthread_create(&mmq_thread, NULL, MonitorMessageQueue, NULL);
	if( ret )
		throw VpbException("Failed to start MMQ thread: %s", strerror(ret));

	mprintf("Driver initialised OK\n");
	Init = 1;
} //}}}

VPBPortHandle WINAPI vpb_open(unsigned int board,
			      unsigned int port,
			      VPBOpenMode  flags)
{ //{{{
	try {
		init_vpb();

		if(board >= numboards)
			throw VpbException("vpb_open: invalid board number %u (of %u)",
					   board, numboards);

		switch( vpb_c->vpbreg(board)->model )
		{
		    case VPB_V4LOG:
		    case VPB_V4PCI:
			if(port >= 4)
				throw VpbException("vpb_open: invalid port number %u (of 4)", port);
			break;

		    case VPB_OSW:
			if(port >= 12)
				throw VpbException("vpb_open: invalid port number %u (of 12)", port);
			break;

		    case VPB_OPCI:
			if(port >= vpb_c->vpbreg(board)->numch)
				throw VpbException("vpb_open: invalid port number %u (of %u)",
						   port, vpb_c->vpbreg(board)->numch);
			break;

		    case VPB_PRI:
			if(port >= 32)
				throw VpbException("vpb_open: invalid port number %u (of 32)", port);
			break;

		    case VPB_MODEL_UNKNOWN:
			throw VpbException("vpb_open: invalid model type %u",
					   vpb_c->vpbreg(board)->model);
		}

		VPBPortHandle h = mapdevtohndle(board, port);
		if (vpb_dev[h].devstate == VPB_ON)
			throw VpbException("vpb_open: board %u, port %u, is already open",
					   board, port);

		vpb_dev[h].devstate = VPB_ON;

		mprintf("[%d/%d] vpb_open: handle = %d\n",
			board, port, h);

		if( flags & VPB_OPEN_RESET ) vpb_reset(h);
		return h;
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_open");
	}
} //}}}

void WINAPI vpb_reset(VPBPortHandle handle)
{ //{{{
	if(handle == -1) {
		for(int i=0; i < Totalchans; ++i)
			if(vpb_dev[i].devstate == VPB_ON) vpb_reset(i);

		return;
	}

	CheckHandle( handle, "vpb_reset" );

	unsigned short  b, ch;
	maphndletodev(handle, &b, &ch);

	vpb_tone_terminate(handle);
	vpb_play_terminate(handle);
	vpb_record_terminate(handle);

	// This one will throw if it wasn't active, ignore that here.
	try { vpb_log_terminate(handle); } catch(...) {}
	vpb_monitor_terminate(handle);

	vpb_flush_events(handle);
	vpb_flush_digits(handle);

	vpb_c->vpbreg(b)->hostdsp->Reset(ch);

	switch( vpb_get_port_type(handle) )
	{
	    case VPB_FXO:
		vpb_sethook_async(handle, VPB_ONHOOK);
		break;

	    case VPB_FXS:
		vpb_ring_station_async(handle, VPB_RING_STATION_OFF);
		break;

	    case VPB_PORT_UNKNOWN:
		throw VpbException("vpb_reset: handle %d has invalid port type %d",
				   handle, vpb_get_port_type(handle));
	}
} //}}}

static void close_port(VPBPortHandle handle)
{ //{{{
	// Check if it has a softbridge open
	//mprintf("vpb_close: Checking for softbridge on (%d)\n",handle);
	soft_bridge_info *bp = soft_bridge_find(handle);
	if (bp != NULL){
		mprintf("Closing soft bridge for %d\n",
			handle);
		vpb_soft_bridge(bp->h1,bp->h2,VPB_BRIDGE_OFF);
	}
	// close device
	vpb_dev[handle].devstate = VPB_OFF;
} //}}}

/*-------------------------------------------------------------------------*\

	FUNCTION:	CloseVPB
	AUTHOR..:	David Rowe
	DATE....:	10/2/98
	
	This function is called during system shutdown to free all of the
	memory allocated for DSP FIFOs during ConfigureVPB().

	Comm    *c;		   ptr to comm object for VPB(s)
	unsigned short  numboards;	   number of boards in system

\*-------------------------------------------------------------------------*/

static void CloseVPB(Comm *c, unsigned short numboards)
{ //{{{
	for(int b=0; b < numboards; ++b)
	{
		VPBREG  *v = c->vpbreg(b);

		switch(v->model){
			case VPB_PRI:
			case VPB_OSW:
			case VPB_OPCI:
				break;
			default:
				config_stop(vpb_c, b);
				for(int ch=0; ch < v->numch; ++ch) {
					int handle = mapdevtohndle(b,ch);
					mprintf("Closing FIFO: %d  brd: "
						"%d  ch: %d\n",handle,b,ch);
					delete v->txdf[ch];
					delete v->rxdf[ch];
				}
				break;
		}
	}
} //}}}

int WINAPI vpb_close(VPBPortHandle handle)
{ //{{{
	try {
		if( handle >= 0 ) {
			mprintf("vpb_close: handle %d\n", handle);

			CheckHandle( handle, "vpb_close" );
			close_port(handle);

			for(int i=0; i < Totalchans; ++i)
				if(vpb_dev[i].devstate == VPB_ON) return VPB_OK;
		} else {
			for(int i=0; i < Totalchans; ++i)
				if(vpb_dev[i].devstate == VPB_ON) close_port(i);
		}

		mprintf("Closing down driver\n");

		/* Check if any soft bridges are still happening */
		if (soft_bridges != NULL){
			mprintf("Closing down soft bridges!\n");
			soft_bridge_info *bp = soft_bridges;
			while (bp != NULL){
				mprintf("Closing down soft bridge "
					"between %d and %d\n",
					bp->h1, bp->h2);
				vpb_soft_bridge(bp->h1,bp->h2,VPB_BRIDGE_OFF);
				bp=bp->next;
			}
		}

	      #ifndef __FreeBSD__
		//XXX this can be bogus, but will probably be gone
		//    before its really an issue.
		switch( get_board_type(0) )
		{
		    case VPB_V4PCI:
		    case VPB_V4LOG:
			KillLoopSenseThread();

		    default: ;
		}
	      #endif

		mprintf("Stopping MMQ thread\n");
		pthread_cancel(mmq_thread);
		pthread_join(mmq_thread, NULL);

		CloseVPB(vpb_c, numboards);
		mprintf("Stopping tone detector\n");
		vpbtoned_close();
		objtrack_close();
		vpbvox_close();
		vpbdial_close();
		vpbtimer_close();
		playrec_close();
		digits_close();
		call_close();

		delete APIQ;       APIQ    = NULL;
		delete [] vpb_dev; vpb_dev = NULL;
		delete vpb_c;      vpb_c   = NULL;

		GenericDeleteCriticalSection(&PutEvtSect);
		GenericDeleteCriticalSection(&SoftBridgeSect);

		mprintf("Driver closed down OK!\n");
		mess_mprintf_off();
		Init = 0;
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_close");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_set_event_callback(VPBPortHandle handle,
				  vpb_event_callback callback,
				  void *context)
{ //{{{
	CheckHandle( handle, "vpb_set_event_callback" );

	VPB_DEV &dev = vpb_dev[handle];

	pthread_mutex_lock( &dev.callback_mutex );
	dev.event_callback = callback;
	dev.context        = context;
	pthread_mutex_unlock( &dev.callback_mutex );

	return VPB_OK;
} //}}}

int WINAPI vpb_set_event_mask(VPBPortHandle handle, unsigned long mask)
{ //{{{
	CheckHandle( handle, "vpb_set_event_mask" );
	vpb_dev[handle].evtmask = mask;

	return VPB_OK;
} //}}}

unsigned long WINAPI vpb_get_event_mask(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_get_event_mask");
	return vpb_dev[handle].evtmask;
} //}}}

int WINAPI vpb_enable_event(VPBPortHandle handle, unsigned long mask)
{ //{{{
	CheckHandle(handle, "vpb_enable_event");
	vpb_dev[handle].evtmask |= mask;

	return VPB_OK;
} //}}}

int WINAPI vpb_disable_event(VPBPortHandle handle, unsigned long mask)
{ //{{{
	CheckHandle(handle, "vpb_disable_event");
	vpb_dev[handle].evtmask &= ~mask;

	return VPB_OK;
} //}}}

int WINAPI vpb_set_tone_event_mask(VPBPortHandle handle, unsigned long mask)
{ //{{{
	CheckHandle(handle, "vpb_set_tone_event_mask");
	vpb_dev[handle].tonemask = mask;

	return VPB_OK;
} //}}}

unsigned long WINAPI vpb_get_tone_event_mask(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_get_tone_event_mask");
	return vpb_dev[handle].tonemask;
} //}}}

int WINAPI vpb_enable_tone_event(VPBPortHandle handle, unsigned long mask)
{ //{{{
	CheckHandle(handle, "vpb_enable_tone_event");
	vpb_dev[handle].tonemask |= mask;

	return VPB_OK;
} //}}}

int WINAPI vpb_disable_tone_event(VPBPortHandle handle, unsigned long mask)
{ //{{{
	CheckHandle(handle, "vpb_disable_tone_event");
	vpb_dev[handle].tonemask &= ~mask;

	return VPB_OK;
} //}}}

int WINAPI vpb_put_event(VPB_EVENT *e)
{ //{{{
	if( ! APIQ )
		throw VpbException("vpb_put_event: no global event queue?");

	try {
		// prevent App and poll timer calling at the same time
		GenericEnterCriticalSection(&PutEvtSect);

		// otherwise place event on Q
		APIQ->Write((unsigned short*)e, SIZE_OF_VPB_EVENT_WORDS);
		vpb_dev[e->handle].APIQ.Write((unsigned short*)e,
					      SIZE_OF_VPB_EVENT_WORDS);

		GenericLeaveCriticalSection(&PutEvtSect);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_put_event");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_get_event_count_ch(VPBPortHandle handle)
{ //{{{
	return vpb_dev[handle].APIQ.HowFull() / SIZE_OF_VPB_EVENT_WORDS;
} //}}}

int WINAPI vpb_get_event_count()
{ //{{{
	if( ! APIQ )
		throw VpbException("vpb_get_event_count: no global event queue?");

	return APIQ->HowFull() / SIZE_OF_VPB_EVENT_WORDS;
} //}}}

void WINAPI vpb_flush_events(VPBPortHandle handle)
{ //{{{
	CheckHandle( handle, "vpb_flush_events" );
	vpb_dev[handle].APIQ.Flush();
} //}}}

void WINAPI vpb_flush_events()
{ //{{{
	if( ! APIQ )
		throw VpbException("vpb_flush_events: no global event queue?");

	APIQ->Flush();
} //}}}

int putevt(VPB_EVENT *e, unsigned long mask)
{ //{{{

	if( ! APIQ )
		throw VpbException("putevt: no global event queue?");

	VPB_DEV &dev = vpb_dev[e->handle];

	if( (mask == 0 || (mask & dev.evtmask)) && dev.devstate == VPB_ON)
	{
		pthread_mutex_lock( &dev.callback_mutex );
		vpb_event_callback  callback = dev.event_callback;
		void               *context  = dev.context;
		pthread_mutex_unlock( &dev.callback_mutex );


		// determine if callback function to be used
		if( callback ) callback(e, context);
		else {
			// ring a few alarm bells if API queue is nearly full
			const int headroom = 2 * SIZE_OF_VPB_EVENT_WORDS;

			// prevent App and poll timer calling at the same time
			GenericEnterCriticalSection(&PutEvtSect);

			size_t words = APIQ->HowFull();

			// if event queue fills it will just have no
			// more events added

			if( words > (global_apiq_size - headroom) ) {
				mprintf("[Global]Oops - Event Q full!\n");
			}
			words = dev.APIQ.HowFull();
			if( words > (channel_apiq_size - headroom) ) {
				mprintf("[%.02d]Oops - Event Q full!\n",e->handle);
			}

			// otherwise place event on Q

			int ret;
			ret = APIQ->Write((unsigned short*)e, SIZE_OF_VPB_EVENT_WORDS);
			if (ret != Fifo::OK) {
				//mprintf("Oops - problem writing to global Event Q!\n");
			}
			ret = dev.APIQ.Write((unsigned short*)e, SIZE_OF_VPB_EVENT_WORDS);
			if (ret != Fifo::OK) {
				mprintf("Oops - problem writing to ch Event Q!\n");
				mprintf("h = %d type = %d ret = %d \n",
					e->handle, e->type, ret);
			}
			GenericLeaveCriticalSection(&PutEvtSect);
		}
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_get_event_async(VPB_EVENT *e)
{ //{{{
	if( ! APIQ )
		throw VpbException("vpb_get_event_async: no global event queue?");

	if( APIQ->Read((unsigned short*)e, SIZE_OF_VPB_EVENT_WORDS) == Fifo::OK )
		return VPB_OK;

	e->type = VPB_EVT_NONE;
	return VPB_NO_EVENTS;
} //}}}

int WINAPI vpb_get_event_sync(VPB_EVENT *e, unsigned int time_out)
{ //{{{
	unsigned int tick = 0;

	if( ! APIQ )
		throw VpbException("vpb_get_event_sync: no global event queue?");

	while( APIQ->Read((unsigned short*)e, SIZE_OF_VPB_EVENT_WORDS) != Fifo::OK )
	{
		GenericSleep(sleepms);
		if( time_out && (tick += sleepms, tick >= time_out) ) {
			e->type = VPB_EVT_NONE;
			return VPB_TIME_OUT;
		}
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_get_event_ch_sync(VPBPortHandle h, VPB_EVENT *e, unsigned int time_out)
{ //{{{
	CheckHandle( h, "vpb_get_event_ch_sync" );

	unsigned int tick = 0;
	while( vpb_dev[h].APIQ.Read((unsigned short*)e, SIZE_OF_VPB_EVENT_WORDS) != Fifo::OK )
	{
		GenericSleep(sleepms);
		if( time_out && (tick += sleepms, tick >= time_out) ) {
			e->type = VPB_EVT_NONE;
			return VPB_TIME_OUT;
		}
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_get_event_ch_async(VPBPortHandle h, VPB_EVENT *e)
{ //{{{
	CheckHandle( h, "vpb_get_event_ch_async" );
	if( vpb_dev[h].APIQ.Read((unsigned short*)e, SIZE_OF_VPB_EVENT_WORDS) == Fifo::OK )
		return VPB_OK;

	e->type = VPB_EVT_NONE;
	return VPB_NO_EVENTS;
} //}}}

void WINAPI vpb_sethook_async(VPBPortHandle handle, HookState hookstate)
{ //{{{
	CheckHandle( handle, "vpb_sethook_async" );

	unsigned short	b,ch;
	maphndletodev(handle, &b, &ch);

	VPBREG	&reg = *vpb_c->vpbreg(b);
	reg.hostdsp->SetHookState(ch, hookstate);
} //}}}

void WINAPI vpb_sethook_sync(VPBPortHandle handle, HookState hookstate)
{ //{{{
	vpb_sethook_async(handle, hookstate);

	// delay determined by experiment to give hardware enough time
	// to take line on/off hook.  This is a bit slow due to speed
	// of link through codecs on some hardware, and due to line
	// calibration on others.
	GenericSleep(hookstate == VPB_FASTOFFHOOK ? 42 : 420);
} //}}}

void WINAPI vpb_set_flashtime(VPBPortHandle handle, uint16_t min, uint16_t max)
{ //{{{
	CheckHandle( handle, "vpb_set_flashtime" );

	unsigned short	b,ch;
	maphndletodev(handle, &b, &ch);

	VPBREG	&reg = *vpb_c->vpbreg(b);
	reg.hostdsp->SetFlashTime(ch, min, max);
} //}}}


void ring_station_async(int board, int port, int cadence)
{ //{{{
	if( cadence == -1 )
		cadence = get_country(board,port)->ring_cadence;

	if(cadence == VPB_RING_STATION_OFF) {
		uint16_t mess[PC_LRING_OFF] = { PC_LRING_OFF,
						PC_RING_OFF,
						uint16_t(port),
						uint16_t(cadence) };
		vpb_c->PutMessageVPB(board,mess);
	} else {
		uint16_t mess[PC_LRING_ON]  = { PC_LRING_ON,
						PC_RING_ON,
						uint16_t(port),
						uint16_t(cadence) };
		vpb_c->PutMessageVPB(board,mess);
	}
} //}}}

void WINAPI vpb_ring_station_async(VPBPortHandle handle, int cadence)
{ //{{{
	CheckHandle( handle, "vpb_ring_station_async" );

	unsigned short  b,ch;
	maphndletodev(handle, &b, &ch);

	ring_station_async(b, ch, cadence);
} //}}}

void WINAPI vpb_sleep(long time) { GenericSleep(time); }




/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_adpcm_open
	AUTHOR..: David Rowe
	DATE....: 20/6/98

	Sets up the state variables for ADPCM opertaions.  Must be called before 
	any other ADPCM functions.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_adpcm_open(void **pv)
{
	try {
		adpcm_open(pv);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_adpcm_open");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_adpcm_close
	AUTHOR..: David Rowe
	DATE....: 20/6/98

	Closes down the ADPCM state variables, freeing memory.  Call after all
	ADPCM operations have finished.

\*--------------------------------------------------------------------------*/

void WINAPI vpb_adpcm_close(void *pv)
{
	adpcm_close(pv);
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_adpcm_reset
	AUTHOR..: David Rowe
	DATE....: 7/8/01

	Resets the ADPCM state variables.

\*--------------------------------------------------------------------------*/

void WINAPI vpb_adpcm_reset(void *pv)
{
	adpcm_reset(pv);
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_adpcm_decode
	AUTHOR..: David Rowe
	DATE....: 20/6/98

	Converts a buffer of compressed OKI ADPCM samples to a buffer of 16 bit
	linear samples.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_adpcm_decode(
	void			*pv,	   // ADPCM state variables
	short			linear[],  // linear output samples
	unsigned short  *size_lin,	   // number of 16 bit samples out
	char			adpcm[],   // packed ADPCM samples
	unsigned short	size_adpcm	   // number of bytes in (must be even)
)
{
	unsigned short	*codes;

	try {
		if (size_adpcm%2)
			throw Wobbly(VPBAPI_ADPCM_SIZE_ADPCM_MUST_BE_EVEN);
		codes = new uint16_t[size_adpcm*2];

		adpcm_unpack(codes, (unsigned short*)adpcm, size_adpcm/2);
		adpcm_decode(pv, linear, codes, size_adpcm*2);
		*size_lin = size_adpcm*2;

		delete [] codes;
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_adpcm_decode");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_adpcm_encode
	AUTHOR..: David Rowe
	DATE....: 20/6/98

	Converts a buffer of 16 bit linear samples to a buffer of compressed 
	OKI ADPCM samples.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_adpcm_encode(
	void		*pv,	     // ADPCM state variables
	char		adpcm[],     // output packed ADPCM samples
	unsigned short	*size_adpcm, // number of bytes out
	short		linear[],    // linear input samples
	unsigned short  size_linear  // number of 16 bit samples in
)
{
	unsigned short	*codes;

	try {
		codes = new uint16_t[size_linear];

		adpcm_encode(pv, codes, linear, size_linear);
		adpcm_pack(codes, (unsigned short*)adpcm, size_linear/4);
		*size_adpcm = size_linear/2;

		delete [] codes;
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_adpcm_encode");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_comp_load
	AUTHOR..: David Rowe
	DATE....: 25/6/98

	Interrogates the DSP to determine the computational loading.
	Causes a comp load statement to be printed if mprintf's are
	enabled, for example via "verbose(1)" call.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_comp_load(unsigned short board)
{
	uint16_t	m[PC_LSPI_LOAD];

	try {
		m[0] = PC_LSPI_LOAD;
		m[1] = PC_SPI_LOAD;
		vpb_c->PutMessageVPB(board, m);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_comp_load");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_echo_canc_force_adapt_on
	AUTHOR..: David Rowe
	DATE....: 25/6/98

	Forces the echo cancellers to adapt all of the time, ie switches
	off near end speech detector.  Used to determine maximum 
	computational load.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_echo_canc_force_adapt_on()
{
	uint16_t	m[PC_LECHO_FORCEON];
	int		i,id;
	unsigned short	b,ch;		// board and channel for this handle

	try {
		m[0] = PC_LECHO_FORCEON;
		m[1] = PC_ECHO_FORCEON;

		for(i=0; i<Totalchans; i++) {
			maphndletodev(i, &b, &ch);
			id = objtrack_handle_to_id(ECHOOBJ, i);
			m[2] = id;
			vpb_c->PutMessageVPB(b, m);
		}
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_echo_canc_force_adapt_on");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_echo_canc_force_adapt_off
	AUTHOR..: David Rowe
	DATE....: 25/6/98

	Stops forced adaptation.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_echo_canc_force_adapt_off()
{
	uint16_t	m[PC_LECHO_FORCEOFF];
	int		i,id;
	unsigned short	b,ch;		// board and channel for this handle

	try {
		m[0] = PC_LECHO_FORCEOFF;
		m[1] = PC_ECHO_FORCEOFF;

		for(i=0; i<Totalchans; i++) {
			maphndletodev(i, &b, &ch);
			id = objtrack_handle_to_id(ECHOOBJ, i);
			m[2] = id;
			vpb_c->PutMessageVPB(b, m);
		}
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_echo_canc_force_adapt_off");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_echo_canc_enable
	AUTHOR..: David Rowe
	DATE....: 9/9/98

	Enables the echo cancellers.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_echo_canc_enable()
{
	uint16_t	m[PC_LECHO_ENABLE];
	int		i,id;
	unsigned short	b,ch;		// board and channel for this handle

	try {
		m[0] = PC_LECHO_ENABLE;
		m[1] = PC_ECHO_ENABLE;

		for(i=0; i<Totalchans; i++) {
			maphndletodev(i, &b, &ch);
			id = objtrack_handle_to_id(ECHOOBJ, i);
			m[2] = id;
			vpb_c->PutMessageVPB(b, m);
		}
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_echo_canc_enable");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_echo_canc_disable
	AUTHOR..: David Rowe
	DATE....: 9/9/98

	Disables the echo cancellers, but keeps adaptation on.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_echo_canc_disable()
{
	uint16_t	m[PC_LECHO_DISABLE] = {PC_LECHO_DISABLE, PC_ECHO_DISABLE};
	unsigned short	b,ch;		// board and channel for this handle

	try {
		for(int i=0; i < Totalchans; ++i) {
			maphndletodev(i, &b, &ch);
			m[2] = objtrack_handle_to_id(ECHOOBJ, i);
			vpb_c->PutMessageVPB(b, m);
		}
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_echo_canc_disable");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_echo_canc_get_sup_thresh
	AUTHOR..: David Rowe
	DATE....: 24/9/01

	Gets the current echo suppressor threshold.  This threshold is
	the same for all channels on all cards.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_echo_canc_get_sup_thresh(int handle, short *thresh)
{
	unsigned short bd,ch;
	maphndletodev(handle, &bd, &ch);

	//XXX should this really be silently ignored on other hardware?
	if( vpb_c->vpbreg(bd)->ddmodel != DD_PCI ) return VPB_OK;

	try {
		uint32_t  agthres;

		// just read value from first card as all cards have same value

		coff_get_address(V4PCI_FIRMWARE, "_gthres", &agthres);
		V4PCI_DSP::GetHip()->ReadDspSram(bd, (uint16_t)agthres,
						 1, (uint16_t*)thresh);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_echo_canc_get_sup_thresh");
	}
	return VPB_OK;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_echo_canc_set_sup_thresh
	AUTHOR..: David Rowe
	DATE....: 24/9/01

	Sets the current echo suppressor threshold.  This threshold is
	the same for all channels on all cards.

	0x1000 (4096) -18dB
	0x800  (2048) -24dB
	0      0      no supressor

\*--------------------------------------------------------------------------*/

int WINAPI vpb_echo_canc_set_sup_thresh(int handle, short *thresh)
{
	unsigned short bd,ch;
	maphndletodev(handle, &bd, &ch);

	//XXX should this really be silently ignored on other hardware?
	if( vpb_c->vpbreg(bd)->ddmodel != DD_PCI ) return VPB_OK;

	try {
		uint32_t  agthres;

		coff_get_address(V4PCI_FIRMWARE, "_gthres", &agthres);
		V4PCI_DSP::GetHip()->WriteDspSram(bd, (uint16_t)agthres,
						  1, (uint16_t*)thresh);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_echo_canc_set_sup_thresh");
	}
	return VPB_OK;
}


// internal worker function
int get_driver_type( int board )
{ //{{{
	return vpb_c->vpbreg(board)->ddmodel;
} //}}}

// internal worker function
VPB_MODEL get_board_type( int board )
{ //{{{
	return vpb_c->vpbreg(board)->model;
} //}}}

VPB_MODEL WINAPI vpb_get_card_type(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_get_card_type");

	unsigned short bd,ch;
	maphndletodev(handle, &bd, &ch);

	return get_board_type(bd);
} //}}}

// This function is deprecated.
int WINAPI vpb_get_type(int handle)
{ //{{{
	return vpb_get_card_type(handle);
} //}}}

HookState WINAPI vpb_get_hookstate(VPBPortHandle handle)
{ //{{{
	CheckHandle( handle, "vpb_get_hookstate" );

	unsigned short  bd, ch;
	VTCore	       *VTCoreDSP;

	maphndletodev(handle, &bd, &ch);
	switch( vpb_c->vpbreg(bd)->model )
	{
	    case VPB_OPCI:
	    case VPB_OSW:
		VTCoreDSP = (VTCore*)vpb_c->vpbreg(bd)->hostdsp;
		return VTCoreDSP->hook_state(ch);
	    case VPB_V4LOG:
	    case VPB_V4PCI:
	    case VPB_PRI:
	    default:
		mprintf("WARNING: cannot get hookstate for board %d, port %d\n",
			bd, ch);
		throw VpbException("vpb_get_hookstate: unknown board type");
	}
} //}}}


// internal worker function.
VPB_PORT get_port_type(int board, int port)
{ //{{{
	switch( vpb_c->vpbreg(board)->model )
	{
	    case VPB_OPCI:
	    case VPB_OSW:
		return static_cast<VTCore*>(vpb_c->vpbreg(board)->hostdsp)
			->chan_type(port);

	    case VPB_V4LOG:
	    case VPB_V4PCI:
	    case VPB_PRI:
	    default:
		return VPB_FXO;
	}
} //}}}

VPB_PORT WINAPI vpb_get_port_type(VPBPortHandle handle)
{ //{{{
	CheckHandle( handle, "vpb_get_port_type" );

	unsigned short bd,ch;
	maphndletodev(handle, &bd, &ch);

	return get_port_type(bd, ch);
} //}}}


// internal worker function.
void get_board_model( int board, char *s )
{ //{{{
	switch( vpb_c->vpbreg(board)->model )
       	{
	    case VPB_OPCI:
		strcpy(s, "OPCI");
		break;

	    case VPB_OSW:
		strcpy(s, "OSW");
		break;

	    case VPB_V4LOG:
		strcpy(s, "V4LOG");
		break;

	    case VPB_V4PCI:
		strcpy(s, "V4PCI");
		break;

	    case VPB_PRI:
		strcpy(s, "PRI");
		break;

	    default:
		assert(0);
	}
} //}}}

std::string WINAPI vpb_get_model(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_get_model");

	char           s[MAX_STR];
	unsigned short bd,ch;

	maphndletodev(handle, &bd, &ch);
	get_board_model(bd, s);

	return s;
} //}}}

int WINAPI vpb_get_model(VPBPortHandle handle, char *s)
{ //{{{
	CheckHandle(handle, "vpb_get_model");

	unsigned short bd,ch;

	maphndletodev(handle, &bd, &ch);
	get_board_model(bd, s);

	return VPB_OK;
} //}}}

int WINAPI vpb_get_model(char *s)
{ //{{{
	get_board_model(0, s);
	return VPB_OK;
} //}}}

int WINAPI vpb_get_num_cards()
{ //{{{
	try {
		init_vpb();
		return numboards;
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_get_num_cards");
	}
} //}}}

int WINAPI vpb_get_ports_per_card(int board)
{ //{{{
	try {
		init_vpb();
		return vpb_c->vpbreg(board)->numch;
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_get_ports_per_card");
	}
} //}}}

// This function is deprecated.
int WINAPI vpb_get_ports_per_card()
{ //{{{
	return vpb_get_ports_per_card( 0 );
} //}}}


void set_codec_reg(int handle, unsigned short addr, unsigned short data, Comm *c )
{ //{{{
	unsigned short	b,ch;
	uint16_t	m[PC_LCODEC_GEN];

	maphndletodev(handle, &b, &ch);
	m[0] = PC_LCODEC_GEN;
	m[1] = PC_CODEC_GEN;
	m[2] = ch;
	m[3] = addr;
	m[4] = data;
	c->PutMessageVPB(b,m);
} //}}}

void WINAPI vpb_set_codec_reg(int chdev, unsigned short addr, unsigned short data)
{
	set_codec_reg(chdev, addr, data, vpb_c);
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_bridge
	AUTHOR..: David Rowe
	DATE....: 27/2/02

	Bridges two ports using the driver, to allow 2-way conferencing. The
	bridging is implemented at a low level (firmware and/or hardware) by 
	the driver to ensure a low delay connection between the ports.  Note
	that only ports on the same physical card can be bridged using this
	method on the V4PCI and ISA boards.

	Two bridging resources are available on the V4PCI (1 & 2).

	V4PCI: For 3 or more way conferencing (or bridging between ports on 
	different cards) use "software bridging" using the play and record 
	buf API functions.  This results in a higher delay (20ms or so), 
	however this does not cause a problem due to the echo canceller on 
	each VPB port.

	OpenSwitch: This will auto-magically switch to a kernel-mode driver
	version of bridging when bridging across boards. For more information
	on this look at the vpb_listen function.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_bridge( VPBPortHandle h1,  // handle of first channel
		       VPBPortHandle h2,  // handle of second channel
		       BridgeMode mode)   // VPB_BRIDGE_ON | VPB_BRIDGE_OFF
{ //{{{
	unsigned short	b1,ch1,b2,ch2;
	uint16_t	m[PC_LSPI_BRIDGE];
	int model1,model2;

	try {
		// can't bridge a port to itself
		if(h1 == h2)
			throw Wobbly(VPBAPI_BRIDGE_HANDLES_IDENTICAL);

		maphndletodev(h1, &b1, &ch1);
		maphndletodev(h2, &b2, &ch2);

		model1 = vpb_c->vpbreg(b1)->model;
		model2 = vpb_c->vpbreg(b2)->model;

		if (model1 != model2){
			if( (model1 == VPB_OPCI || model1 == VPB_OSW)
			 && (model2 == VPB_OPCI || model2 == VPB_OSW) )
			{
				mprintf("Bridging VTCore\n");
				vpb_listen(h1,h2,mode);
				vpb_listen(h2,h1,mode);
			}
			else if( vpb_soft_bridge(h1,h2,mode) != VPB_OK )
				throw Wobbly(VPBAPI_BRIDGE_DIFFERENT_BOARDS);
		} else {
			switch(model1) {
			    case VPB_V4PCI:
				mprintf("Bridging VPB_V4PCI\n");
				// can't bridge across boards (yet!)
				if(b1 != b2) {
					if( vpb_soft_bridge(h1,h2,mode) == VPB_OK )
						break;
					throw Wobbly(VPBAPI_BRIDGE_DIFFERENT_BOARDS);
				}
				if(mode == VPB_BRIDGE_ON) {
					m[0] = PC_LSPI_BRIDGE;
					m[1] = PC_SPI_BRIDGE;
				} else {
					m[0] = PC_LSPI_BRIDGE_OFF;
					m[1] = PC_SPI_BRIDGE_OFF;
				}
				m[2] = ch1;
				m[3] = ch2;
				m[4] = (ch1 == 0 || ch2 == 0) ? 1 : 2;
				// only two bridge resources available on V4PCI
				vpb_c->PutMessageVPB(b1,m);
				break;

			    case VPB_PRI:
				mprintf("Bridging PRI\n");
				// cant bridge across boards 
				if(b1 != b2){
					if( vpb_soft_bridge(h1,h2,mode) == VPB_OK )
						break;
					throw Wobbly(VPBAPI_BRIDGE_DIFFERENT_BOARDS);
				}

				if(mode == VPB_BRIDGE_ON) {
					m[0] = PC_LSPI_BRIDGE;
					m[1] = PC_SPI_BRIDGE;
				} else {
					m[0] = PC_LSPI_BRIDGE_OFF;
					m[1] = PC_SPI_BRIDGE_OFF;
				}
				m[2] = ch1;
				m[3] = ch2;
				vpb_c->PutMessageVPB(b1,m);
				break;

			    case VPB_OSW:
			    case VPB_OPCI:
				mprintf("Bridging VTCore\n");
				vpb_listen(h1,h2,mode);
				vpb_listen(h2,h1,mode);
				break;

			    default:
				assert(0);
			}
		}

	} catch(const Wobbly &w){
		return RunTimeError(w,"vpb_bridge");
	}
	return VPB_OK;
} //}}}

// This function is deprecated.
int WINAPI vpb_bridge( int h1, int h2, BridgeMode mode, int resource )
{ //{{{
	(void)resource;
	return vpb_bridge( h1, h2, mode );
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_soft_bridge
	AUTHOR..: Ben Kramer
	DATE....: 23/02/2005

	Software bridge for different Voicetronix boards.  Starts a thread that
	copies the audio between two ports. 

\*--------------------------------------------------------------------------*/
int WINAPI vpb_soft_bridge(
		       int h1,      // handle of first channel
		       int h2,      // handle of second channel
		       int mode    // VPB_BRIDGE_ON | VPB_BRIDGE_OFF
)
{
	unsigned short	b1,ch1,b2,ch2;
	soft_bridge_info *bp;

	maphndletodev(h1, &b1, &ch1);
	maphndletodev(h2, &b2, &ch2);

	/* You frog in a blender! */
	if (h1 == h2){
		mprintf("vpb_soft_bridge: You cant bridge the same channels!!\n");
		return -1;
	}

	GenericEnterCriticalSection(&SoftBridgeSect);
	if (mode == VPB_BRIDGE_ON){
		mprintf("vpb_soft_bridge: Bridging [%d] to [%d]\n",h1,h2);

		/* Check if already bridged */
		bp = soft_bridge_find(h1,h2);
		if (bp != NULL){
			GenericLeaveCriticalSection(&SoftBridgeSect);
			mprintf("vpb_soft_bridge: Channels[%d/%d] are already bridged!\n",h1,h2);
			return VPB_OK;
		}

		/* Get a soft bridge record */
		bp = soft_bridge_new();

		// store channel handles for identifying bridge
		bp->h1 = h1;
		bp->h2 = h2;

		// save original transmit data fifo pointers
		bp->txdf1 = vpb_c->vpbreg(b1)->txdf[ch1];
		bp->txdf2 = vpb_c->vpbreg(b2)->txdf[ch2];

		// point transmit data fifos at bridged receive data fifos
		vpb_c->vpbreg(b1)->txdf[ch1] = vpb_c->vpbreg(b2)->rxdf[ch2];
		vpb_c->vpbreg(b2)->txdf[ch2] = vpb_c->vpbreg(b1)->rxdf[ch1];

		// flush the data fifos
		vpb_c->vpbreg(b1)->rxdf[ch1]->Flush();
		vpb_c->vpbreg(b2)->rxdf[ch2]->Flush();
	}
	else if(mode == VPB_BRIDGE_OFF){
		mprintf("vpb_soft_bridge: Un-bridging [%d] to [%d]\n",h1,h2);
		/* Find the soft bridge record */
		bp = soft_bridge_find(h1,h2);
		if (bp == NULL){
			mprintf("vpb_soft_bridge: Already Un-bridged [%d] to [%d]\n",h1,h2);
			GenericLeaveCriticalSection(&SoftBridgeSect);
			return VPB_OK;
		}
		mprintf("vpb_soft_bridge: Found bridge for [%d] to [%d]\n",h1,h2);

		// restore original transmit data fifo pointers
		vpb_c->vpbreg(b1)->txdf[ch1] = bp->txdf1;
		vpb_c->vpbreg(b2)->txdf[ch2] = bp->txdf2;

		/* remove the record */
		mprintf("vpb_soft_bridge: Deleting bridge for [%d] to [%d]\n",h1,h2);
		soft_bridge_delete(bp);
		mprintf("vpb_soft_bridge: Done bridge for [%d] to [%d]\n",h1,h2);
	}
	else {
		GenericLeaveCriticalSection(&SoftBridgeSect);
		mprintf("vpb_soft_bridge: Invalid mode!\n");
		return -1;
	}
	GenericLeaveCriticalSection(&SoftBridgeSect);
	return VPB_OK;
}

static soft_bridge_info* WINAPI soft_bridge_new(void)
{
	soft_bridge_info *bp1, *bp2;

	if (soft_bridges == NULL){
		bp1 = new soft_bridge_info;
		bp1->next = NULL;
		soft_bridges = bp1;
	}
	else {
		bp1 = soft_bridges;
		while (bp1 != NULL){
			bp2=bp1;
			bp1=bp1->next;
		}
		bp1 = new soft_bridge_info;
		bp1->next = NULL;
		bp2->next = bp1;
	}
	return bp1;
}

static soft_bridge_info* WINAPI soft_bridge_find(int h1, int h2)
{
	soft_bridge_info *bp1;
	bp1 = soft_bridges;
	while (bp1 != NULL){
		if (((bp1->h1 == h1)&&(bp1->h2 == h2))||
			((bp1->h2 == h1)&&(bp1->h1 == h2))){
			return bp1;
		}
		mprintf("soft_bridge_find: looking for h1[%d]h2[%d]\n",h1,h2);
		bp1=bp1->next;
	}
	return NULL;
}

static soft_bridge_info* WINAPI soft_bridge_find(int h1)
{
	soft_bridge_info *bp1;
	bp1 = soft_bridges;
	while (bp1 != NULL){
		if ((bp1->h1 == h1)||(bp1->h2 == h1)) {
			return bp1;
		}
		mprintf("soft_bridge_find: looking for h1[%d]\n",h1);
		bp1=bp1->next;
	}
	return NULL;
}

static void WINAPI soft_bridge_delete(soft_bridge_info *bp)
{
	soft_bridge_info *bp1, *bp2=NULL;
	bp1 = soft_bridges;
	if ((bp1 == NULL)||(bp==NULL)){
		/* might have already been deleted */
		return;
	}
	while (bp1 != bp){
		bp2=bp1;
		bp1=bp1->next;
	}
	/* Check if there was only the one record or the root element */
	if (bp2 == NULL){
		soft_bridges = bp->next;
		delete bp;
	}
	else {
		/* Check if it was the last record */
		if (bp1->next == NULL){
			bp2->next = NULL;
		}
		else {
			bp2->next = bp1->next;
		}
		delete bp;
	}
	return;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_listen
	AUTHOR..: Ben Kramer
	DATE....: 27/02/03

	Software bridge/listen for OpenSwitch boards.  Starts a half duplex bridge
	between two handles. For a full duplex bridge, both handles will need to
	listen between each other.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_listen( VPBPortHandle h1, // handle of first channel
		       VPBPortHandle h2, // handle of second channel
		       BridgeMode mode ) // VPB_BRIDGE_ON | VPB_BRIDGE_OFF
{ //{{{
	unsigned short	b1,ch1,b2,ch2;
	uint16_t	m[PC_LSPI_SBRIDGE];

	try {
		// can't bridge a port to itself  XXX

		maphndletodev(h1, &b1, &ch1);
		maphndletodev(h2, &b2, &ch2);

		switch(vpb_c->vpbreg(b1)->model) {

		    case VPB_PRI:
			if(b1 != b2)
				throw Wobbly(VPBAPI_BRIDGE_DIFFERENT_BOARDS);

			if(mode == VPB_BRIDGE_ON) {
				vpb_c->vpbreg(b1)->listen_info[ch1].board=b2;
				vpb_c->vpbreg(b1)->listen_info[ch1].chan=ch2;
				m[0] = PC_LSPI_SBRIDGE;
				m[1] = PC_SPI_SBRIDGE;
			}
			else {
				if( vpb_c->vpbreg(b1)->listen_info[ch1].board != -1
				 && vpb_c->vpbreg(b1)->listen_info[ch1].board != b2 )
				{
					mprintf("Trying to unlisten from somebody we are not listened with!\n");
					break;
				}
				if( vpb_c->vpbreg(b1)->listen_info[ch1].chan != -1
				 && vpb_c->vpbreg(b1)->listen_info[ch1].chan != ch2 )
				{
					mprintf("Trying to unlisten from somebody we are not listened with!\n");
					break;
				}
				m[0] = PC_LSPI_SBRIDGE_OFF;
				m[1] = PC_SPI_SBRIDGE_OFF;
			}
			m[2] = ch1;
			m[3] = b2;
			m[4] = ch2;
			vpb_c->PutMessageVPB(b1,m);
			break;

		    case VPB_OSW:
		    case VPB_OPCI:
			{
				int model2 = vpb_c->vpbreg(b2)->model;

				if (model2 != VPB_OPCI && model2 != VPB_OSW)
					throw Wobbly(VPBAPI_BRIDGE_DIFFERENT_BOARDS);
			}

			if(mode == VPB_BRIDGE_ON) {
				vpb_c->vpbreg(b1)->listen_info[ch1].board=b2;
				vpb_c->vpbreg(b1)->listen_info[ch1].chan=ch2;
				m[0] = PC_LSPI_SBRIDGE;
				m[1] = PC_SPI_SBRIDGE;
			} else {
				if( vpb_c->vpbreg(b1)->listen_info[ch1].board != -1
				 && vpb_c->vpbreg(b1)->listen_info[ch1].board != b2 )
				{
					mprintf("Trying to unlisten from somebody we are not listened with!\n");
					break;
				}
				if( vpb_c->vpbreg(b1)->listen_info[ch1].chan != -1
				 && vpb_c->vpbreg(b1)->listen_info[ch1].chan != ch2 )
				{
					mprintf("Trying to unlisten from somebody we are not listened with!\n");
					break;
				}
				m[0] = PC_LSPI_SBRIDGE_OFF;
				m[1] = PC_SPI_SBRIDGE_OFF;
			}
			m[2] = ch1;
			m[3] = vpb_c->vpbreg(b2)->cardtypnum;
			m[4] = ch2;
			vpb_c->PutMessageVPB(b1,m);
			break;

		    default:
			assert(0);
		}
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_listen");
	}
	return VPB_OK;
} //}}}


/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_conf
	AUTHOR..: Ben Kramer
	DATE....: 26/02/03

	Multi party conferencing. Can be used across any number of OpenSwitch
       	boards.  There is a maximum of 10 conferences with a max of 10 people
	each.
	You may need to reduce the HW-record gain if you experiance excesive
	feed back.

\*--------------------------------------------------------------------------*/
int WINAPI vpb_conf( int h,        // Handle of channel/port
		     int resource, // Conference/party resource to use (0-9)
		     int mode )    // VPB_CONF_JOIN,VPB_VONF_LEAVE
{ //{{{
    (void)h;
    (void)resource;
    (void)mode;
    //XXX
    #if 0
	unsigned short	b,ch;
	uint16_t	m[PC_LCONF_JOIN];

	try {

	  maphndletodev(h, &b, &ch);

//	  printf("model = %d\n",model);
	  switch(vpb_c->vpbreg(b)->model) {

	      case VPB_V12PCI:

		if (mode && VPB_CONF_JOIN) {
//	  printf("Joining conf %d to %d\n",ch,resource);
			m[0] = PC_LCONF_JOIN;
			m[1] = PC_CONF_JOIN;
			m[2] = ch;
			m[3] = resource;
		}
		else {
//	  printf("Leaving conf %d \n",ch);
			m[0] = PC_LCONF_LEAVE;
			m[1] = PC_CONF_LEAVE;
			m[2] = ch;
			m[3] = 0;
		}
		vpb_c->PutMessageVPB(b,m);
		break;

	      default:
		throw Wobbly(VPBAPI_CONF_BOARD_NOT_SUPPORTED);
		break;
	  }

	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_conf");
	}
    #endif
	return VPB_OK;
} //}}}


int WINAPI vpb_get_card_info( int board, VPB_CARD_INFO *detail )
{ //{{{
    #ifdef FREEBSD
    // DR 20/2/03 - trap use under OS other than Linux and Windows
    assert(0);
    #endif

    assert(detail);

    get_board_model( board, detail->model );
    strcpy(detail->date, vpb_c->vpbreg(board)->mdate);
    strcpy(detail->rev, vpb_c->vpbreg(board)->revision);
    strcpy(detail->sn, vpb_c->vpbreg(board)->serial_n);

    return VPB_OK;
} //}}}

int WINAPI vpb_loopback_on(int h)
{ //{{{
	try {
		unsigned short  b, ch;
		maphndletodev(h, &b, &ch);

		uint16_t mess[PC_LLOOPBACK_ON] = { PC_LLOOPBACK_ON,
						   PC_LOOPBACK_ON,
						   ch };
		vpb_c->PutMessageVPB(b, mess);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_loopback_on");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_loopback_off(int h)
{ //{{{
	try {
		unsigned short  b, ch;
		maphndletodev(h, &b, &ch);

		uint16_t mess[PC_LLOOPBACK_OFF] = { PC_LLOOPBACK_OFF,
						    PC_LOOPBACK_OFF,
						    ch };
		vpb_c->PutMessageVPB(b, mess);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_loopback_off");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_hostecho_on(int h)
{ //{{{
	try {
		unsigned short  b, ch;
		maphndletodev(h, &b, &ch);

		uint16_t mess[PC_LHOSTECHO_ON] = { PC_LHOSTECHO_ON,
						   PC_HOSTECHO_ON,
						   ch };
		vpb_c->PutMessageVPB(b, mess);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_HOSTECHO_on");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_hostecho_off(int h)
{ //{{{
	try {
		unsigned short  b, ch;
		maphndletodev(h, &b, &ch);

		uint16_t mess[PC_LHOSTECHO_OFF] = { PC_LHOSTECHO_OFF,
						    PC_HOSTECHO_OFF,
						    ch };
		vpb_c->PutMessageVPB(b, mess);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_HOSTECHO_off");
	}
	return VPB_OK;
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_isdn_call
	AUTHOR..: Ben Kramer
	DATE....: xx/12/2004

	Make a call on an isdn channel. Set layer1 and transcap to -1 to use
	defaults.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_isdn_call(int h, char *number, char *cid, int layer1,
			 int transcap, unsigned char *lowlayercompatibility)
{ //{{{
	try {
	      #ifdef _OPENPRI
		unsigned short  b,ch;

		maphndletodev(h, &b, &ch);
		OpenPri *PriDsp = (OpenPri *)vpb_c->vpbreg(b)->hostdsp;
		if( PriDsp->make_isdn_call_api(ch,number,cid,layer1,transcap,
					       lowlayercompatibility) != 0 )
			throw Wobbly(VPBAPI_ISDN_CALL_FAILED);
	      #else
		(void)h;
		(void)number;
		(void)cid;
		(void)layer1;
		(void)transcap;
		(void)lowlayercompatibility;
		throw Wobbly(VPBAPI_INVALID_HANDLE);
	      #endif
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_ISDN_CALL");
	}
	return VPB_OK;
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_isdn_get_cnum
	AUTHOR..: Ben Kramer
	DATE....: xx/12/2004

	Get the call information. You need to pass it a pointer to
	a call info structure. When it has filled in the information
	it will produce an event.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_isdn_get_cinfo(int h, VPB_CALL_INFO *cinfo)
{
	try {
	      #ifdef _OPENPRI
		unsigned short  b,ch;

		maphndletodev(h, &b, &ch);
		VPBREG  *pvpbreg = vpb_c->vpbreg(b);
		OpenPri *PriDsp  = (OpenPri*)pvpbreg->hostdsp;
		if( PriDsp->chan_cinfo(ch, cinfo) != 0 )
			throw Wobbly(VPBAPI_ISDN_CALLINFO_FAILED);
	      #else
		(void)h;
		(void)cinfo;
		throw Wobbly(VPBAPI_INVALID_HANDLE);
	      #endif
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_ISDN_GET_CINFO");
	}
	return VPB_OK;

	//PriDsp=(OpenPri *)pvpbreg->hostdsp;
	//ch_state = PriDsp->chan_state(ch);
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_isdn_get_layer1
	AUTHOR..: Ben Kramer
	DATE....: 04/07/2005

	Get the layer 1 protocol, this can then be passed to the out bound leg.
	Look for PRI_LAYER_1 in libpri.h for different values.

\*--------------------------------------------------------------------------*/
int WINAPI vpb_isdn_get_layer1(int h)
{
	int ulayer1 = 0;

      #ifdef _OPENPRI
	unsigned short  b,ch;

	maphndletodev(h, &b, &ch);
	VPBREG  *pvpbreg = vpb_c->vpbreg(b);
	OpenPri *PriDsp  = (OpenPri*)pvpbreg->hostdsp;
	ulayer1          = PriDsp->chan_layer1(ch);
      #else
	(void)h;
      #endif

	return ulayer1;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_isdn_get_transcap
	AUTHOR..: Ben Kramer
	DATE....: 11/07/2005

	Get the transmission capabalities , this can then be passed to the out bound leg.
	Look for PRI_TRANS in libpri.h for different values.

\*--------------------------------------------------------------------------*/
int WINAPI vpb_isdn_get_transcap(int h)
{
	int transcap = 0;

      #ifdef _OPENPRI
	unsigned short  b,ch;

	maphndletodev(h, &b, &ch);
	VPBREG  *pvpbreg = vpb_c->vpbreg(b);
	OpenPri *PriDsp  = (OpenPri*)pvpbreg->hostdsp;
	transcap         = PriDsp->chan_transcap(ch);
      #else
	(void)h;
      #endif

	return transcap;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_isdn_get_lowlayercompatibility
	AUTHOR..: Ben Kramer
	DATE....: 22/07/2005

	Get the low-layer compatibility , this can then be passed to the out bound leg.

\*--------------------------------------------------------------------------*/
int WINAPI vpb_isdn_get_lowlayercompatibility(int h,unsigned char *lowlayercompatibility)
{
      #ifdef _OPENPRI
	unsigned short  b,ch;

	maphndletodev(h, &b, &ch);
	VPBREG  *pvpbreg = vpb_c->vpbreg(b);
	OpenPri *PriDsp = (OpenPri*)pvpbreg->hostdsp;
	return PriDsp->chan_lowlayercompatibility(ch,lowlayercompatibility);
      #else
	(void)h;
	(void)lowlayercompatibility;
	return -1;
      #endif
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_isdn_get_cause
	AUTHOR..: Ben Kramer
	DATE....: 05/07/2005

	Get the cause code from the last call attempt on the channel.

\*--------------------------------------------------------------------------*/
int WINAPI vpb_isdn_get_cause(int h)
{
	int cause = 0;

      #ifdef _OPENPRI
	unsigned short  b,ch;

	maphndletodev(h, &b, &ch);
	VPBREG  *pvpbreg = vpb_c->vpbreg(b);
	OpenPri *PriDsp  = (OpenPri*)pvpbreg->hostdsp;
	cause = PriDsp->chan_cause(ch);
      #else
	(void)h;
      #endif

	return cause;
}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_isdn_proceeding
	AUTHOR..: Ben Kramer
	DATE....: 11/07/2005

	Sends a call proceeding message

\*--------------------------------------------------------------------------*/
int WINAPI vpb_isdn_proceeding(int h)
{
	int ret = 0;

      #ifdef _OPENPRI
	unsigned short  b,ch;

	maphndletodev(h, &b, &ch);
	VPBREG  *pvpbreg = vpb_c->vpbreg(b);
	OpenPri *PriDsp  = (OpenPri*)pvpbreg->hostdsp;
	ret = PriDsp->call_proceeding(ch);
      #else
	(void)h;
      #endif

	return ret;
}

#ifdef linux
// This function is deprecated from the public api.
int WINAPI vpb_is_station(int h)
{ //{{{
	return vpb_get_port_type(h) == VPB_FXS ? 1 : 0;
} //}}}
#endif

#ifndef __FreeBSD__
/*---------------------------------------------------------------------------*\

        FUNCTION: vpb_loopvolt_get
        AUTHOR..: Peter Wintulich
        DATE....: 

        Read the current line voltage including polarity.
	The units are not scaled but are ADC units.
        The sign represents the line polarity.

	Return Value of the function indicates success or failure of the call
	If the Device is busy the call is aborted.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_loopvolt_get(int h, short *volts)
{
	return loopvolt_get( h, volts);
}

/*---------------------------------------------------------------------------*\

        FUNCTION: vpb_loopvolt_get_threshold
        AUTHOR..: Peter Wintulich
        DATE....: 

        Read the current line voltage threshold.
	The units are not scaled but are ADC units.
        valid range 0 to 127, usable range 8 to 100

	Return Value of the function indicates success or failure of the call
	If the Device is busy the call is aborted.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_loopvolt_get_threshold(int h, short *volts)
{
	return loopvolt_get_threshold( h, volts);
}

/*---------------------------------------------------------------------------*\

        FUNCTION: vpb_loopvolt_set_threshold
        AUTHOR..: Peter Wintulich
        DATE....: 

        Set a new line voltage threshold.
	The units are not scaled but are ADC units.
        The sign represents the line polarity.

	volts:	Range 0 to 127

	Return Value of the function indicates success or failure of the call.
	Failure may mean the IIC bus was busy on another call, if so try again.
	If the Device is busy the call is aborted.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_loopvolt_set_threshold(int h, short volts)
{
	return loopvolt_set_threshold(h, volts);
}

/*---------------------------------------------------------------------------*\
        FUNCTION: vpb_loopvolt_get_lowlimit
        AUTHOR..: Peter Wintulich
        DATE....: 

        Read the current line voltage low limit threshold.
	The units are not scaled but are ADC units.
        valid range 0 to 127, usable range 0 to 10, default 2

	Return Value of the function indicates success or failure of the call
	If the Device is busy the call is aborted.

\*--------------------------------------------------------------------------*/

int WINAPI vpb_loopvolt_get_lowlimit(int h, short *volts)
{
	return loopvolt_get_lowlimit( h, volts);
}

/*---------------------------------------------------------------------------*\
        FUNCTION: vpb_loopvolt_set_lowlimit
        AUTHOR..: Peter Wintulich
        DATE....: 

	Return Value of the function indicates success or failure of the call.
	Failure may mean the IIC bus was busy on another call, if so try again.
	If the Device is busy the call is aborted.
\*--------------------------------------------------------------------------*/
int WINAPI vpb_loopvolt_set_lowlimit(int h, short volts)
{
	return loopvolt_set_lowlimit( h, volts);
}

/*---------------------------------------------------------------------------*\
        FUNCTION: vpb_loop_get_onhookwindow
        AUTHOR..: Peter Wintulich
        DATE....: 15/APR/2005

	Get time to Qualify on hook event. time in milliseconds
\*--------------------------------------------------------------------------*/
int WINAPI vpb_loop_get_onhookwindow(int h, int *ms)
{
	return loop_get_onhookwindow( h, ms);
}

/*---------------------------------------------------------------------------*\
        FUNCTION: vpb_loop_set_onhookwindow
        AUTHOR..: Peter Wintulich
        DATE....: 15/APR/2005

	Set time to Qualify on hook event. time in milliseconds
	Range 20 to 10000 ms.
	Return -1 Range error, 0 success.
\*--------------------------------------------------------------------------*/
int WINAPI vpb_loop_set_onhookwindow(int h, int ms)
{
	return loop_set_onhookwindow( h, ms);
}

/*---------------------------------------------------------------------------*\
        FUNCTION: vpb_loop_get_offhookwindow
        AUTHOR..: Peter Wintulich
        DATE....: 15/APR/2005

	Get time to Qualify off hook event. time in milliseconds
\*--------------------------------------------------------------------------*/
int WINAPI vpb_loop_get_offhookwindow(int h, int *ms)
{
	return loop_get_offhookwindow( h, ms);
}

/*---------------------------------------------------------------------------*\
        FUNCTION: vpb_loop_set_offhookwindow
        AUTHOR..: Peter Wintulich
        DATE....: 15/APR/2005

	Set time to Qualify off hook event. time in milliseconds
	Range 20 to 10000 ms.
	Return -1 Range error, 0 success.
\*--------------------------------------------------------------------------*/
int WINAPI vpb_loop_set_offhookwindow(int h, int ms)
{
	return loop_set_offhookwindow( h, ms);
}

#endif

void WINAPI vpb_set_mprintf(void (*func)(const char *format, ...))
{ //{{{
	mess_set_messages(func);
} //}}}

const char *vpb_model_desc(VPB_MODEL model)
{ //{{{
	switch(model){
		case VPB_OPCI:  return "OpenPCI";
		case VPB_OSW:   return "OpenSwitch";
		case VPB_V4PCI: return "OpenLine";
		case VPB_V4LOG: return "OpenLog";
		case VPB_PRI:   return "OpenPRI";

		case VPB_MODEL_UNKNOWN: return "Unknown model";
	}
	// We should never get here, but the compiler doesn't seem to know that
	return "Unknown";
}; //}}}

