/*****************************************************************************
Virtual Multichannel Streams Implementation Module

Copyright (C) 1997-2017 AudioScience, Inc. All rights reserved.

This software is provided 'as-is', without any express or implied warranty.
In no event will AudioScience Inc. be held liable for any damages arising
from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This copyright notice and list of conditions may not be altered or removed
   from any source distribution.

AudioScience, Inc. <support@audioscience.com>

( This license is GPL compatible see http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses )

*****************************************************************************/
#define SOURCEFILE_NAME "hpissx2.c"
#include "hpissx2.h"
#undef HPI_MESSAGE_LOWER_LAYER
#include "hpidebug.h"
#include "hpimsginit.h"

#ifndef HPI_OS_LINUX
#include "hpimsgx.h"
#else
// Implement in userspace library
// Message are sent to local adapters directly.
// must also be build with HPI_MULTIINTERFACE
void HPI_MessageIoctl(HPI_MESSAGE *phm, HPI_RESPONSE *phr);
#define HPI_MessageEx(m,r,o)  HPI_MessageIoctl(m, r)
#endif

#ifndef HPIMSGX_ALLADAPTERS
#define HPIMSGX_ALLADAPTERS	0xFFFF
#endif

#define HPI_HIDE_STEREO
#define HPI_MAX_SSX2STREAMS	8

#define HPI_SSX2_SUBBUFMAXSAMPLES	(2048)
#define HPI_SSX2_MASTERCONTROL		(0x8000)
#define HPI_SSX2_INDEX(a)			((a)&0x7FFF)
#define HPI_SSX2_INVALID_INDEX		(0x7FFF)

typedef struct HPI_SSX2_INDEX_MAP_ {
	uint16_t wNumEntries;
	uint16_t *pTable;
} HPI_SSX2_INDEX_MAP, *PHPI_SSX2_INDEX_MAP;

typedef struct HPI_SSX2_ADAPTER_INFO_ {
	uint16_t wPrepared;
	uint16_t wSsx2Channels;
	uint16_t wNumSsx2OStreams;
	uint16_t wFirstSsx2OStream;
	HPI_SSX2_INDEX_MAP tOStreamD2H;
	HPI_SSX2_INDEX_MAP tOStreamH2D;
	uint16_t wNumSsx2IStreams;
	uint16_t wFirstSsx2IStream;
	HPI_SSX2_INDEX_MAP tIStreamD2H;
	HPI_SSX2_INDEX_MAP tIStreamH2D;
	HPI_SSX2_INDEX_MAP tControlD2H;
	HPI_SSX2_INDEX_MAP tControlH2D;
} HPI_SSX2_ADAPTER_INFO, *PHPI_SSX2_ADAPTER_INFO;

typedef struct HPI_STREAMINFO_ {
//      uint16_t wAdapterIndex;
	uint16_t wStreamIndex;
	uint16_t wControlIndex;	// OStream volume or IStream mux
	uint32_t dwLastBufferSize;
	uint32_t dwLastDataToPlay;
	uint64_t qwHardwareBuffer;
	uint32_t dwBytesWrittenToStream;
} HPI_STREAMINFO, *PHPI_STREAMINFO;

typedef struct HPI_SSX2_STREAM_INFO_ {
	void *hOwner;
	void *pDataBuffer;
	uint32_t dwBytesPerFrame;
	uint8_t PartialFrame[64];
	uint32_t dwPartialFrameBytes;
	uint32_t dwDataBufferSize;
	HPI_STREAMINFO ssx2info;
	HPI_STREAMINFO hwStreamInfo[4];
	int numHwStreams;
	int numActiveHwStreams;
	int nBytesPerSample;
	int nOpen;
	HPI_SSX2_INDEX_MAP tSourceD2H;
	HPI_SSX2_INDEX_MAP tSourceH2D;
} HPI_SSX2_STREAM_INFO, *PHPI_SSX2_STREAM_INFO;

static HPI_SSX2_ADAPTER_INFO aADAPTER_SSX2[HPI_MAX_ADAPTERS];
static HPI_SSX2_STREAM_INFO aOSTREAM_SSX2[HPI_MAX_ADAPTERS][HPI_MAX_SSX2STREAMS];
static HPI_SSX2_STREAM_INFO aISTREAM_SSX2[HPI_MAX_ADAPTERS][HPI_MAX_SSX2STREAMS];

/*************************************************************************************\
* Local Function Declarations                                                         *
\*************************************************************************************/

static void HPISSX2_Reset(uint16_t wAdapterIndex);
static void HPISSX2_Init(HPI_MESSAGE *phm, HPI_RESPONSE *phr);
static void HPISSX2_Cleanup(uint16_t wAdapterIndex, void *hOwner);

static inline uint16_t MapIndex(PHPI_SSX2_INDEX_MAP pMap, uint16_t wIndex)
{
	if (wIndex < pMap->wNumEntries) {
		return pMap->pTable[wIndex];
	}
	return HPI_SSX2_INVALID_INDEX;
}

#define HPISSX2_MAP_OSTREAM_D2H(a,i)	MapIndex(&aADAPTER_SSX2[(a)].tOStreamD2H,(i))
#define HPISSX2_MAP_OSTREAM_H2D(a,i)	MapIndex(&aADAPTER_SSX2[(a)].tOStreamH2D,(i))
#define HPISSX2_MAP_ISTREAM_D2H(a,i)	MapIndex(&aADAPTER_SSX2[(a)].tIStreamD2H,(i))
#define HPISSX2_MAP_ISTREAM_H2D(a,i)	MapIndex(&aADAPTER_SSX2[(a)].tIStreamH2D,(i))
#define HPISSX2_MAP_CONTROL_D2H(a,i)	HPI_SSX2_INDEX(MapIndex(&aADAPTER_SSX2[(a)].tControlD2H,(i)))
#define HPISSX2_IS_CONTROL_MC(a,i)		(MapIndex(&aADAPTER_SSX2[(a)].tControlD2H,(i))&HPI_SSX2_MASTERCONTROL)
#define HPISSX2_MAP_CONTROL_H2D(a,i)	MapIndex(&aADAPTER_SSX2[(a)].tControlH2D,(i))
#define HPISSX2_MAP_SOURCE_D2H(a,s,i)	MapIndex(&aISTREAM_SSX2[(a)][(s)].tSourceD2H,(i))

static void OutStreamOpen(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner);
static void OutStreamClose(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner);
static void OutStreamGetInfoEx(HPI_MESSAGE *phm, HPI_RESPONSE *phr);
static void OutStreamWrite(HPI_MESSAGE *phm, HPI_RESPONSE *phr);
static void OutStreamStart(HPI_MESSAGE *phm, HPI_RESPONSE *phr);
static void OutStreamQueryFormat(HPI_MESSAGE *phm, HPI_RESPONSE *phr);

static void InStreamOpen(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner);
static void InStreamClose(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner);
static void InStreamGetInfoEx(HPI_MESSAGE *phm, HPI_RESPONSE *phr);
static void InStreamRead(HPI_MESSAGE *phm, HPI_RESPONSE *phr);
static void InStreamSetFormat(HPI_MESSAGE *phm, HPI_RESPONSE *phr);
static void InStreamQueryFormat(HPI_MESSAGE *phm, HPI_RESPONSE *phr);

static inline PHPI_SSX2_STREAM_INFO GetSsx2OStreamInfoStruct(uint16_t wAdapter, uint16_t wStream)
{
	return &aOSTREAM_SSX2[wAdapter][wStream - aADAPTER_SSX2[wAdapter].wFirstSsx2OStream];
}

static inline PHPI_SSX2_STREAM_INFO GetSsx2IStreamInfoStruct(uint16_t wAdapter, uint16_t wStream)
{
	return &aISTREAM_SSX2[wAdapter][wStream - aADAPTER_SSX2[wAdapter].wFirstSsx2IStream];
}

static inline int FormatBytesPerSample(struct hpi_msg_format *pFormat)
{
	switch (pFormat->wFormat) {
	case HPI_FORMAT_PCM8_UNSIGNED:
		return 1;
	case HPI_FORMAT_PCM16_SIGNED:
	case HPI_FORMAT_PCM16_BIGENDIAN:
		return 2;
	case HPI_FORMAT_PCM24_SIGNED:
		return 3;
	case HPI_FORMAT_PCM32_SIGNED:
	case HPI_FORMAT_PCM32_FLOAT:
		return 4;
	}
	return -1;
}

static inline int FormatActiveHwStreams(struct hpi_msg_format *pFormat)
{
	return (pFormat->wChannels + 1) / 2;
}

/*************************************************************************************\
* Message Filters - get all messages, pre version can prevent main handler by         *
*                   returning non-zero                                                *
\*************************************************************************************/

static HPI_BOOL SubSysPreMessage(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	HPI_BOOL handled = 0;
	switch (phm->wFunction) {
	case HPI_SUBSYS_CLOSE:
		HPISSX2_Cleanup(HPIMSGX_ALLADAPTERS, hOwner);
		break;
	default:
		break;
	}
	return handled;
}

static void SubSysPostMessage(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	switch (phm->wFunction) {
	case HPI_SUBSYS_CREATE_ADAPTER:
		//HPISSX2_Init( phm, phr );
		break;
	case HPI_SUBSYS_DRIVER_LOAD:
	case HPI_SUBSYS_DRIVER_UNLOAD:
		HPISSX2_Reset(HPIMSGX_ALLADAPTERS);
		break;
	default:
		break;
	}
}

static HPI_BOOL AdapterPreMessage(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	HPI_BOOL handled = 0;

	if (phm->wAdapterIndex >= HPI_MAX_ADAPTERS)
		return handled;

	switch (phm->wFunction) {
	case HPI_ADAPTER_DELETE:
		HPISSX2_Cleanup(phm->wAdapterIndex, hOwner);
		HPISSX2_Reset(phm->wAdapterIndex);
		aADAPTER_SSX2[phm->wAdapterIndex].wPrepared = 0;
		break;
	case HPI_ADAPTER_SET_PROPERTY:
		if (phm->u.ax.property_set.wProperty == HPI_ADAPTER_PROPERTY_ENABLE_SSX2) {
			if (phm->u.ax.property_set.wParameter1)
				HPISSX2_Init(phm, phr);
			/* else could disable SSX2 here? */
			handled = 1;
		}
		break;
	}
	return handled;
}

static void AdapterPostMessage(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	if (phm->wAdapterIndex >= HPI_MAX_ADAPTERS)
		return;

	switch (phm->wFunction) {
	case HPI_ADAPTER_GET_INFO:
#ifdef HPI_HIDE_STEREO
		if (aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2OStreams) {
			phr->u.ax.info.wNumOStreams = aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2OStreams;
		}
		if (aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2IStreams) {
			phr->u.ax.info.wNumIStreams = aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2IStreams;
		}
#else
		phr->u.ax.info.wNumOStreams += aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2OStreams;
		phr->u.ax.info.wNumIStreams += aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2IStreams;
#endif
		break;
	case HPI_ADAPTER_GET_PROPERTY:
		if (phm->u.ax.property_set.wProperty == HPI_ADAPTER_PROPERTY_CURCHANNELS) {
			if (aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2IStreams != 0)
				phr->u.ax.property_get.wParameter1 = 8;
			if (aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2OStreams != 0)
				phr->u.ax.property_get.wParameter2 = 8;
		}
		break;
	default:
		break;
	}
}

/*************************************************************************************\
* Message Handlers - get SSX2 specific messages for handling                          *
\*************************************************************************************/

static HPI_BOOL MixerMessage(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	HPI_BOOL handled = 0;
	HPI_MESSAGE hm;
	uint16_t wAd = phm->wAdapterIndex;

	memcpy(&hm, phm, sizeof(HPI_MESSAGE));

	switch (phm->wFunction) {
	case HPI_MIXER_GET_CONTROL:
		if (hm.u.m.wNodeType1 == HPI_SOURCENODE_OSTREAM) {
			hm.u.m.wNodeIndex1 = HPISSX2_MAP_OSTREAM_D2H(wAd, phm->u.m.wNodeIndex1);
			if (phm->u.m.wNodeIndex1 <
				aADAPTER_SSX2[wAd].wNumSsx2OStreams
				&& (phm->u.m.wControlType == HPI_CONTROL_VOLUME
					|| phm->u.m.wControlType == HPI_CONTROL_CONNECTION)) {
				// Map to the right OStream for the Line Out (others will be hidden)
				hm.u.m.wNodeIndex1 +=
					hm.u.m.wNodeIndex2 % aOSTREAM_SSX2[wAd][phm->u.m.wNodeIndex1].numHwStreams;
			}
		}
		if (hm.u.m.wNodeType2 == HPI_DESTNODE_ISTREAM) {
			hm.u.m.wNodeIndex2 = HPISSX2_MAP_ISTREAM_D2H(wAd, phm->u.m.wNodeIndex2);
		}
		HPI_MessageEx(&hm, phr, NULL);
		phr->u.m.wControlIndex = HPISSX2_MAP_CONTROL_H2D(wAd, phr->u.m.wControlIndex);
		if (phr->u.m.wControlIndex == HPI_SSX2_INVALID_INDEX) {
			if (phm->u.m.wControlType != HPI_CONTROL_CONNECTION)
				phr->wError = HPI_ERROR_INVALID_NODE;
		}
		handled = 1;
		break;
	case HPI_MIXER_GET_CONTROL_BY_INDEX:
		hm.u.m.wControlIndex = HPISSX2_MAP_CONTROL_D2H(wAd, phm->u.m.wControlIndex);
		HPI_MessageEx(&hm, phr, NULL);
		if (phr->u.m.wSrcNodeType + HPI_SOURCENODE_NONE == HPI_SOURCENODE_OSTREAM) {
			phr->u.m.wSrcNodeIndex = HPISSX2_MAP_OSTREAM_H2D(wAd, phr->u.m.wSrcNodeIndex);
		}
		if (phr->u.m.wDstNodeType + HPI_DESTNODE_NONE == HPI_DESTNODE_ISTREAM) {
			phr->u.m.wDstNodeIndex = HPISSX2_MAP_ISTREAM_H2D(wAd, phr->u.m.wDstNodeIndex);
		}
		handled = 1;
		break;
	}
	return handled;
}

static HPI_BOOL ControlMessage(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	HPI_BOOL handled = 0;
	HPI_MESSAGE hm;
	uint16_t wIdx;
	uint16_t wAd = phm->wAdapterIndex;

	memcpy(&hm, phm, sizeof(HPI_MESSAGE));

	hm.wObjIndex = HPISSX2_MAP_CONTROL_D2H(wAd, phm->wObjIndex);
	if (hm.wObjIndex == HPI_SSX2_INVALID_INDEX) {
		phr->wError = HPI_ERROR_INVALID_CONTROL;
		handled = 1;
	} else if (HPISSX2_IS_CONTROL_MC(wAd, phm->wObjIndex)) {
		uint16_t wSSX2_Stream;

		for (wSSX2_Stream = 0; wSSX2_Stream < aADAPTER_SSX2[wAd].wNumSsx2IStreams; wSSX2_Stream++) {
			// if it's the mux of an ssx2 istream
			//              duplicate it's effect to the slave controls,
			//              remap any OStream indexes and
			//              remap querysource indexes
			if (aISTREAM_SSX2[wAd][wSSX2_Stream].ssx2info.wControlIndex == phm->wObjIndex) {
				if (phm->wFunction == HPI_CONTROL_SET_STATE
					&& phm->u.c.wAttribute == HPI_MULTIPLEXER_SOURCE) {
					if (hm.u.c.dwParam1 == HPI_SOURCENODE_OSTREAM)
						hm.u.c.dwParam2 = HPISSX2_MAP_OSTREAM_D2H(wAd, (uint16_t)phm->u.c.dwParam2);

					if (hm.u.c.dwParam2 ==
						HPI_SSX2_INVALID_INDEX
						|| hm.u.c.dwParam2 %
						aISTREAM_SSX2[wAd][wSSX2_Stream].numHwStreams != 0) {
						phr->wError = HPI_ERROR_INVALID_CONTROL_VALUE;
					} else {
						HPI_MessageEx(&hm, phr, NULL);
						for (wIdx = 1; wIdx < aISTREAM_SSX2[wAd]
							[wSSX2_Stream].numHwStreams; wIdx++) {
							hm.wObjIndex = aISTREAM_SSX2[wAd]
								[wSSX2_Stream].hwStreamInfo[wIdx].wControlIndex;
							hm.u.c.dwParam2++;
							if (hm.wObjIndex != HPI_SSX2_INVALID_INDEX)
								HPI_MessageEx(&hm, phr, NULL);
						}
					}

					handled = 1;
				} else if (phm->wFunction == HPI_CONTROL_GET_STATE) {
					if (phm->u.c.wAttribute == HPI_MULTIPLEXER_QUERYSOURCE) {
						// Map the index
						hm.u.c.dwParam1 =
							HPISSX2_MAP_SOURCE_D2H
							(wAd, wSSX2_Stream, (uint16_t)phm->u.c.dwParam1);
						if (hm.u.c.dwParam1 == HPI_SSX2_INVALID_INDEX) {
							phr->wError = HPI_ERROR_INVALID_CONTROL_VALUE;
							handled = 1;
						}
					}
					if (!handled)
						HPI_MessageEx(&hm, phr, NULL);
					handled = 1;
					if (phr->u.c.dwParam1 == HPI_SOURCENODE_OSTREAM)
						phr->u.c.dwParam2 =
							HPISSX2_MAP_OSTREAM_H2D(wAd, (uint16_t)phr->u.c.dwParam2);
				}
				break;
			}
		}
		if (!handled) {
			for (wSSX2_Stream = 0; wSSX2_Stream < aADAPTER_SSX2[wAd].wNumSsx2OStreams; wSSX2_Stream++) {
				// if it's the volume on an ssx2 ostream
				//              duplicate it's effect to the slave controls
				if (aOSTREAM_SSX2[wAd][wSSX2_Stream].ssx2info.wControlIndex == phm->wObjIndex) {
					if (phm->wFunction == HPI_CONTROL_SET_STATE) {
						// Apply left channel to all channels
						hm.u.c.anLogValue[1] = hm.u.c.anLogValue[0];
						for (wIdx = 0; wIdx < aOSTREAM_SSX2[wAd]
							[wSSX2_Stream].numHwStreams; wIdx++) {
							hm.wObjIndex = aOSTREAM_SSX2[wAd]
								[wSSX2_Stream].hwStreamInfo[wIdx].wControlIndex;
							if (hm.wObjIndex != HPI_SSX2_INVALID_INDEX)
								HPI_MessageEx(&hm, phr, NULL);
						}
						handled = 1;
					}
					break;
				}
			}
		}
	} else {		// Not an SSX2 control but it could be a mux for a non-SSX2 record.
	}
	if (!handled && hm.wObjIndex != phm->wObjIndex) {
		HPI_MessageEx(&hm, phr, NULL);
		handled = 1;
	}
	return handled;
}

static HPI_BOOL OStreamMessage(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	HPI_BOOL handled = 1;
	uint16_t wHwStreamIndex;

	wHwStreamIndex = HPISSX2_MAP_OSTREAM_D2H(phm->wAdapterIndex, phm->wObjIndex);
	if (wHwStreamIndex == HPI_SSX2_INVALID_INDEX)	// It's a valid HW index that needs to be blocked
	{
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
	} else if (phm->wObjIndex - aADAPTER_SSX2[phm->wAdapterIndex].wFirstSsx2OStream >=
		aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2OStreams) {
		// It's not an SSX2 stream
		HPI_MESSAGE hm;

		memcpy(&hm, phm, sizeof(HPI_MESSAGE));
		hm.wObjIndex = wHwStreamIndex;
		HPI_MessageEx(&hm, phr, hOwner);
	} else {		// It's a valid SSX2 stream index
		PHPI_SSX2_STREAM_INFO pSsx2Strm;
		pSsx2Strm = GetSsx2OStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

		// Shortcut for SSX2 stream in mono or stereo mode
		if (pSsx2Strm->nOpen && pSsx2Strm->nBytesPerSample != 0 &&	// Format has been initialized
			pSsx2Strm->numActiveHwStreams == 1 &&
			phm->wFunction != HPI_OSTREAM_CLOSE &&
			phm->wFunction != HPI_OSTREAM_HOSTBUFFER_ALLOC &&
			phm->wFunction != HPI_OSTREAM_HOSTBUFFER_FREE) {
			uint16_t wSaveIndex = phm->wObjIndex;
			phm->wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
			HPI_MessageEx(phm, phr, hOwner);
			phm->wObjIndex = wSaveIndex;
			handled = 1;
		} else {
			switch (phm->wFunction) {
			case HPI_OSTREAM_OPEN:
				OutStreamOpen(phm, phr, hOwner);
				break;
			case HPI_OSTREAM_CLOSE:
				OutStreamClose(phm, phr, hOwner);
				break;
			case HPI_OSTREAM_WRITE:
				OutStreamWrite(phm, phr);
				break;
			case HPI_OSTREAM_START:
				OutStreamStart(phm, phr);
				break;
			case HPI_OSTREAM_RESET:
				{
					int i;

					// Clear bytes written count
					for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
						pSsx2Strm->hwStreamInfo[i].dwBytesWrittenToStream = 0;
					}
				}
				// Fall through
			case HPI_OSTREAM_STOP:
				{
					uint16_t wSaveIndex = phm->wObjIndex;

					// Pass message to first real stream
					phm->wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
					HPI_MessageEx(phm, phr, hOwner);
					phm->wObjIndex = wSaveIndex;
					pSsx2Strm->dwPartialFrameBytes = 0;
				}
				break;
			case HPI_OSTREAM_GET_INFO:
				OutStreamGetInfoEx(phm, phr);
				break;
			case HPI_OSTREAM_QUERY_FORMAT:
				OutStreamQueryFormat(phm, phr);
				break;
			case HPI_OSTREAM_HOSTBUFFER_ALLOC:
				{
					// Pass message to each real stream
					int i;
					int chg = 0;

					for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
						HPI_MESSAGE hm;

						memcpy(&hm, phm, sizeof(HPI_MESSAGE));
						hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
						hm.u.d.u.buffer.dwBufferSize = phm->u.d.u.buffer.dwBufferSize / pSsx2Strm->numHwStreams;
						HPI_MessageEx(&hm, phr, hOwner);
					}
					if (chg && phr->u.d.u.stream_info.dwBufferSize ==
						phr->u.d.u.stream_info.dwDataAvailable)
						phr->u.d.u.stream_info.dwDataAvailable = 0;
				}
				break;
			case HPI_OSTREAM_HOSTBUFFER_FREE:
				{
					// Pass message to each real stream
					int i;

					for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
						HPI_MESSAGE hm;

						memcpy(&hm, phm, sizeof(HPI_MESSAGE));
						hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
						HPI_MessageEx(&hm, phr, hOwner);
					}
				}
				break;
			case HPI_OSTREAM_DATA:
			case HPI_OSTREAM_SET_VELOCITY:
			case HPI_OSTREAM_SET_PUNCHINOUT:
			case HPI_OSTREAM_SINEGEN:
			case HPI_OSTREAM_ANC_RESET:
			case HPI_OSTREAM_ANC_GET_INFO:
			case HPI_OSTREAM_ANC_READ:
			case HPI_OSTREAM_SET_TIMESCALE:
			case HPI_OSTREAM_SET_FORMAT:
			case HPI_OSTREAM_GROUP_ADD:
			case HPI_OSTREAM_GROUP_GETMAP:
			case HPI_OSTREAM_GROUP_RESET:
			default:
				// Fail message
				HPI_InitResponse(phr, phm->wObject, phm->wFunction, HPI_ERROR_INVALID_FUNC);
				break;
			}
		}
	}
	return handled;
}

static HPI_BOOL IStreamMessage(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	HPI_BOOL handled = 1;
	uint16_t wHwStreamIndex;

	wHwStreamIndex = HPISSX2_MAP_ISTREAM_D2H(phm->wAdapterIndex, phm->wObjIndex);
	if (wHwStreamIndex == HPI_SSX2_INVALID_INDEX)	// It's a valid HW index that needs to be blocked
	{
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
	} else if (phm->wObjIndex - aADAPTER_SSX2[phm->wAdapterIndex].wFirstSsx2IStream >=
		aADAPTER_SSX2[phm->wAdapterIndex].wNumSsx2IStreams) {
		// It's not an SSX2 stream
		HPI_MESSAGE hm;

		memcpy(&hm, phm, sizeof(HPI_MESSAGE));
		hm.wObjIndex = wHwStreamIndex;
		HPI_MessageEx(&hm, phr, hOwner);
	} else {		// It's a valid SSX2 stream index
		PHPI_SSX2_STREAM_INFO pSsx2Strm;
		pSsx2Strm = GetSsx2IStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

		// Shortcut for SSX2 stream in mono or stereo mode
		if (pSsx2Strm->nOpen && pSsx2Strm->nBytesPerSample != 0 &&
			pSsx2Strm->numActiveHwStreams == 1 &&
			phm->wFunction != HPI_ISTREAM_CLOSE &&
			phm->wFunction != HPI_ISTREAM_SET_FORMAT &&
			phm->wFunction != HPI_ISTREAM_HOSTBUFFER_ALLOC &&
			phm->wFunction != HPI_ISTREAM_HOSTBUFFER_FREE) {
			uint16_t wSaveIndex = phm->wObjIndex;
			phm->wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
			HPI_MessageEx(phm, phr, hOwner);
			phm->wObjIndex = wSaveIndex;
			handled = 1;
		} else {
			switch (phm->wFunction) {
			case HPI_ISTREAM_OPEN:
				InStreamOpen(phm, phr, hOwner);
				break;
			case HPI_ISTREAM_CLOSE:
				InStreamClose(phm, phr, hOwner);
				break;
			case HPI_ISTREAM_SET_FORMAT:
				InStreamSetFormat(phm, phr);
				break;
			case HPI_ISTREAM_READ:
				InStreamRead(phm, phr);
				break;
			case HPI_ISTREAM_START:
			case HPI_ISTREAM_STOP:
			case HPI_ISTREAM_RESET:
				{
					uint16_t wSaveIndex = phm->wObjIndex;
					phm->wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
					HPI_MessageEx(phm, phr, hOwner);
					phm->wObjIndex = wSaveIndex;
				}
				break;
			case HPI_ISTREAM_GET_INFO:
				InStreamGetInfoEx(phm, phr);
				break;
			case HPI_ISTREAM_HOSTBUFFER_ALLOC:
				{
					// Pass message to each real stream
					int i;
					int chg = 0;

					for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
						HPI_MESSAGE hm;

						memcpy(&hm, phm, sizeof(HPI_MESSAGE));
						hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
						hm.u.d.u.buffer.dwBufferSize = phm->u.d.u.buffer.dwBufferSize / pSsx2Strm->numHwStreams;
						HPI_MessageEx(&hm, phr, hOwner);
					}
					if (chg && phr->u.d.u.stream_info.dwBufferSize ==
						phr->u.d.u.stream_info.dwDataAvailable)
						phr->u.d.u.stream_info.dwDataAvailable = 0;
				}
				break;
			case HPI_ISTREAM_HOSTBUFFER_FREE:
				{
					// Pass message to each real stream
					int i;

					for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
						HPI_MESSAGE hm;

						memcpy(&hm, phm, sizeof(HPI_MESSAGE));
						hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
						HPI_MessageEx(&hm, phr, hOwner);
					}
				}
				break;
			case HPI_ISTREAM_QUERY_FORMAT:
				InStreamQueryFormat(phm, phr);
				break;
			case HPI_ISTREAM_ANC_RESET:
			case HPI_ISTREAM_ANC_GET_INFO:
			case HPI_ISTREAM_ANC_WRITE:
			case HPI_ISTREAM_GROUP_ADD:
			case HPI_ISTREAM_GROUP_GETMAP:
			case HPI_ISTREAM_GROUP_RESET:
			default:
				// Fail message
				HPI_InitResponse(phr, phm->wObject, phm->wFunction, HPI_ERROR_INVALID_FUNC);
				break;
			}
		}
	}
	return handled;
}

/*************************************************************************************\
* Local Function Definitions                                                          *
\*************************************************************************************/

static inline void AllocateIndexMaps(uint16_t wAdapter, uint16_t wOuts, uint16_t wIns, uint16_t wCtrls)
{
	aADAPTER_SSX2[wAdapter].tOStreamD2H.pTable = (uint16_t *)HpiOs_MemAlloc(sizeof(uint16_t) * wOuts);
	aADAPTER_SSX2[wAdapter].tOStreamD2H.wNumEntries = wOuts;
	aADAPTER_SSX2[wAdapter].tOStreamH2D.pTable = (uint16_t *)HpiOs_MemAlloc(sizeof(uint16_t) * wOuts);
	aADAPTER_SSX2[wAdapter].tOStreamH2D.wNumEntries = wOuts;
	aADAPTER_SSX2[wAdapter].tIStreamD2H.pTable = (uint16_t *)HpiOs_MemAlloc(sizeof(uint16_t) * wIns);
	aADAPTER_SSX2[wAdapter].tIStreamD2H.wNumEntries = wIns;
	aADAPTER_SSX2[wAdapter].tIStreamH2D.pTable = (uint16_t *)HpiOs_MemAlloc(sizeof(uint16_t) * wIns);
	aADAPTER_SSX2[wAdapter].tIStreamH2D.wNumEntries = wIns;
	aADAPTER_SSX2[wAdapter].tControlD2H.pTable = (uint16_t *)HpiOs_MemAlloc(sizeof(uint16_t) * wCtrls);
	aADAPTER_SSX2[wAdapter].tControlD2H.wNumEntries = wCtrls;
	aADAPTER_SSX2[wAdapter].tControlH2D.pTable = (uint16_t *)HpiOs_MemAlloc(sizeof(uint16_t) * wCtrls);
	aADAPTER_SSX2[wAdapter].tControlH2D.wNumEntries = wCtrls;
}

static inline void FreeIndexMaps(uint16_t wAdapter)
{
	uint16_t wDest;

	if (aADAPTER_SSX2[wAdapter].tOStreamD2H.pTable) {
		aADAPTER_SSX2[wAdapter].tOStreamD2H.wNumEntries = 0;
		HpiOs_MemFree(aADAPTER_SSX2[wAdapter].tOStreamD2H.pTable);
		aADAPTER_SSX2[wAdapter].tOStreamD2H.pTable = NULL;
	}
	if (aADAPTER_SSX2[wAdapter].tOStreamH2D.pTable) {
		aADAPTER_SSX2[wAdapter].tOStreamH2D.wNumEntries = 0;
		HpiOs_MemFree(aADAPTER_SSX2[wAdapter].tOStreamH2D.pTable);
		aADAPTER_SSX2[wAdapter].tOStreamH2D.pTable = NULL;
	}
	if (aADAPTER_SSX2[wAdapter].tIStreamD2H.pTable) {
		aADAPTER_SSX2[wAdapter].tIStreamD2H.wNumEntries = 0;
		HpiOs_MemFree(aADAPTER_SSX2[wAdapter].tIStreamD2H.pTable);
		aADAPTER_SSX2[wAdapter].tIStreamD2H.pTable = NULL;
	}
	if (aADAPTER_SSX2[wAdapter].tIStreamH2D.pTable) {
		aADAPTER_SSX2[wAdapter].tIStreamH2D.wNumEntries = 0;
		HpiOs_MemFree(aADAPTER_SSX2[wAdapter].tIStreamH2D.pTable);
		aADAPTER_SSX2[wAdapter].tIStreamH2D.pTable = NULL;
	}
	if (aADAPTER_SSX2[wAdapter].tControlD2H.pTable) {
		aADAPTER_SSX2[wAdapter].tControlD2H.wNumEntries = 0;
		HpiOs_MemFree(aADAPTER_SSX2[wAdapter].tControlD2H.pTable);
		aADAPTER_SSX2[wAdapter].tControlD2H.pTable = NULL;
	}
	if (aADAPTER_SSX2[wAdapter].tControlH2D.pTable) {
		aADAPTER_SSX2[wAdapter].tControlH2D.wNumEntries = 0;
		HpiOs_MemFree(aADAPTER_SSX2[wAdapter].tControlH2D.pTable);
		aADAPTER_SSX2[wAdapter].tControlH2D.pTable = NULL;
	}
	for (wDest = 0; wDest < aADAPTER_SSX2[wAdapter].wNumSsx2IStreams; wDest++) {
		if (aISTREAM_SSX2[wAdapter][wDest].tSourceD2H.pTable) {
			aISTREAM_SSX2[wAdapter][wDest].tSourceD2H.wNumEntries = 0;
			HpiOs_MemFree(aISTREAM_SSX2[wAdapter][wDest].tSourceD2H.pTable);
			aISTREAM_SSX2[wAdapter][wDest].tSourceD2H.pTable = NULL;
		}
		if (aISTREAM_SSX2[wAdapter][wDest].tSourceH2D.pTable) {
			aISTREAM_SSX2[wAdapter][wDest].tSourceH2D.wNumEntries = 0;
			HpiOs_MemFree(aISTREAM_SSX2[wAdapter][wDest].tSourceH2D.pTable);
			aISTREAM_SSX2[wAdapter][wDest].tSourceH2D.pTable = NULL;
		}
	}
}

static inline void CountLinesAndControls(uint16_t wAdapter, uint16_t *pOuts, uint16_t *pIns, uint16_t *pCtrls)
{
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	int nMaxOut = -1;
	int nMaxIn = -1;
	uint16_t wIndex = 0;

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN);
	hm.wAdapterIndex = wAdapter;
	HPI_MessageEx(&hm, &hr, NULL);

	do {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_GET_CONTROL_BY_INDEX);
		hm.wAdapterIndex = wAdapter;
		hm.u.m.wControlIndex = wIndex;
		HPI_MessageEx(&hm, &hr, NULL);
		if (!hr.wError) {
			wIndex++;

			if ((hr.u.m.wDstNodeType + HPI_DESTNODE_NONE ==
					HPI_DESTNODE_LINEOUT
					|| hr.u.m.wDstNodeType + HPI_DESTNODE_NONE == HPI_DESTNODE_AESEBU_OUT)
				&& (int)hr.u.m.wDstNodeIndex > nMaxOut)
				nMaxOut = (int)hr.u.m.wDstNodeIndex;

			if ((hr.u.m.wSrcNodeType + HPI_SOURCENODE_NONE ==
					HPI_SOURCENODE_LINEIN
					|| hr.u.m.wSrcNodeType + HPI_SOURCENODE_NONE ==
					HPI_SOURCENODE_AESEBU_IN
					|| hr.u.m.wSrcNodeType + HPI_SOURCENODE_NONE == HPI_SOURCENODE_TUNER)
				&& (int)hr.u.m.wSrcNodeIndex > nMaxIn)
				nMaxIn = (int)hr.u.m.wSrcNodeIndex;
		}
	} while (!hr.wError);

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE);
	hm.wAdapterIndex = wAdapter;
	HPI_MessageEx(&hm, &hr, NULL);

	*pOuts = (int)(nMaxOut + 1);
	*pIns = (int)(nMaxIn + 1);
	*pCtrls = wIndex;
}

static inline void InitializeIndexMaps(uint16_t wAdapter, uint16_t wStereoPerSSX2)
{
	uint16_t n, m, i, j;

	n = 0;
	m = 0;
	for (i = 0; i < aADAPTER_SSX2[wAdapter].wNumSsx2OStreams; i++) {
		aADAPTER_SSX2[wAdapter].tOStreamD2H.pTable[m++] = n;
		for (j = 0; j < wStereoPerSSX2; j++) {
			aADAPTER_SSX2[wAdapter].tOStreamH2D.pTable[n++] = i;
		}
	}
	while (n < aADAPTER_SSX2[wAdapter].tOStreamH2D.wNumEntries) {
		aADAPTER_SSX2[wAdapter].tOStreamD2H.pTable[m++] = n;
		aADAPTER_SSX2[wAdapter].tOStreamH2D.pTable[n++] = i++;
	}
	while (m < aADAPTER_SSX2[wAdapter].tOStreamD2H.wNumEntries) {
		aADAPTER_SSX2[wAdapter].tOStreamD2H.pTable[m++] = HPI_SSX2_INVALID_INDEX;
	}

	n = 0;
	m = 0;
	for (i = 0; i < aADAPTER_SSX2[wAdapter].wNumSsx2IStreams; i++) {
		aADAPTER_SSX2[wAdapter].tIStreamD2H.pTable[m++] = n;
		for (j = 0; j < wStereoPerSSX2; j++) {
			aADAPTER_SSX2[wAdapter].tIStreamH2D.pTable[n++] = i;
		}
	}
	while (n < aADAPTER_SSX2[wAdapter].tIStreamH2D.wNumEntries) {
		aADAPTER_SSX2[wAdapter].tIStreamD2H.pTable[m++] = n;
		aADAPTER_SSX2[wAdapter].tIStreamH2D.pTable[n++] = i++;
	}
	while (m < aADAPTER_SSX2[wAdapter].tIStreamD2H.wNumEntries) {
		aADAPTER_SSX2[wAdapter].tIStreamD2H.pTable[m++] = HPI_SSX2_INVALID_INDEX;
	}

	for (n = 0; n < aADAPTER_SSX2[wAdapter].tControlD2H.wNumEntries; n++) {
		aADAPTER_SSX2[wAdapter].tControlD2H.pTable[n] = n;
		aADAPTER_SSX2[wAdapter].tControlH2D.pTable[n] = n;
	}
}

static inline void ScanMuxSources(uint16_t wAdapter, PHPI_SSX2_STREAM_INFO pStreamInfo, uint16_t wControlIndex)
{
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	uint16_t wIndex;
	uint16_t wDIndex;
	uint16_t wCount;
	uint16_t wSSX2_Src;
	uint16_t wHide;

	wIndex = 0;
	do {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_CONTROL, HPI_CONTROL_GET_STATE);
		hm.wAdapterIndex = wAdapter;
		hm.wObjIndex = wControlIndex;
		hm.u.c.wAttribute = HPI_MULTIPLEXER_QUERYSOURCE;
		hm.u.c.dwParam1 = wIndex;
		HPI_MessageEx(&hm, &hr, NULL);
		if (hr.wError)
			wCount = wIndex;
		wIndex++;
	} while (!hr.wError);
	pStreamInfo->tSourceD2H.pTable = (uint16_t *)HpiOs_MemAlloc(sizeof(uint16_t) * wCount);
	pStreamInfo->tSourceD2H.wNumEntries = wCount;
	pStreamInfo->tSourceH2D.pTable = (uint16_t *)HpiOs_MemAlloc(sizeof(uint16_t) * wCount);
	pStreamInfo->tSourceH2D.wNumEntries = wCount;
	wDIndex = 0;
	for (wIndex = 0; wIndex < wCount; wIndex++) {
		wHide = 0;
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_CONTROL, HPI_CONTROL_GET_STATE);
		hm.wAdapterIndex = wAdapter;
		hm.wObjIndex = wControlIndex;
		hm.u.c.wAttribute = HPI_MULTIPLEXER_QUERYSOURCE;
		hm.u.c.dwParam1 = wIndex;
		HPI_MessageEx(&hm, &hr, NULL);
		if ((uint16_t)hr.u.c.dwParam1 == HPI_SOURCENODE_OSTREAM) {
			wSSX2_Src = HPISSX2_MAP_OSTREAM_H2D(wAdapter, (uint16_t)hr.u.c.dwParam2);
			if (wSSX2_Src <
				aADAPTER_SSX2[wAdapter].wNumSsx2OStreams
				&& (uint16_t)hr.u.c.dwParam2 !=
				aOSTREAM_SSX2[wAdapter][wSSX2_Src].hwStreamInfo[0].wStreamIndex) {
				wHide = 1;
			}
		} else if ((uint16_t)hr.u.c.dwParam1 == HPI_SOURCENODE_LINEIN) {
			if ((uint16_t)hr.u.c.dwParam2 % pStreamInfo->numHwStreams) {
				wHide = 1;
			}
		}
		if (wHide) {
			pStreamInfo->tSourceH2D.pTable[wIndex] = HPI_SSX2_INVALID_INDEX;
		} else {
			pStreamInfo->tSourceH2D.pTable[wIndex] = wDIndex;
			pStreamInfo->tSourceD2H.pTable[wDIndex] = wIndex;
			wDIndex++;
		}
	}
	for (; wDIndex < wCount; wDIndex++) {
		pStreamInfo->tSourceD2H.pTable[wDIndex] = HPI_SSX2_INVALID_INDEX;
	}
}

static inline void ScanControls(uint16_t wAdapter, uint16_t wStereoPerSSX2)
{
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	HPI_MESSAGE hm_mute;
	HPI_RESPONSE hr_mute;
	int n;
	uint16_t wIndex;
	uint16_t wDIndex;
	uint16_t wHide;
	uint16_t wMaster;

	// Initialize a mute message
	HPI_InitMessageResponse(&hm_mute, &hr_mute, HPI_OBJ_CONTROL, HPI_CONTROL_SET_STATE);
	hm_mute.wAdapterIndex = wAdapter;
	hm_mute.u.c.wAttribute = HPI_VOLUME_GAIN;
	for (n = 0; n < HPI_MAX_CHANNELS; ++n)
		hm_mute.u.c.anLogValue[n] = HPI_GAIN_OFF;

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN);
	hm.wAdapterIndex = wAdapter;
	HPI_MessageEx(&hm, &hr, NULL);

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_GET_CONTROL_BY_INDEX);
	hm.wAdapterIndex = wAdapter;
	wIndex = 0;
	wDIndex = 0;
	do {
		wHide = 0;
		wMaster = 0;
		hm.u.m.wControlIndex = wIndex;
		HPI_MessageEx(&hm, &hr, NULL);
		if (!hr.wError) {
			uint16_t wSSX2_Src = HPI_SSX2_INVALID_INDEX;
			uint16_t wSSX2_Dst = HPI_SSX2_INVALID_INDEX;

			if (hr.u.m.wSrcNodeType + HPI_SOURCENODE_NONE == HPI_SOURCENODE_OSTREAM) {
				wSSX2_Src = HPISSX2_MAP_OSTREAM_H2D(wAdapter, hr.u.m.wSrcNodeIndex);
				if (wSSX2_Src >= aADAPTER_SSX2[wAdapter].wNumSsx2OStreams)
					wSSX2_Src = HPI_SSX2_INVALID_INDEX;
			}
			if (hr.u.m.wDstNodeType + HPI_DESTNODE_NONE == HPI_DESTNODE_ISTREAM) {
				wSSX2_Dst = HPISSX2_MAP_ISTREAM_H2D(wAdapter, hr.u.m.wDstNodeIndex);
				if (wSSX2_Dst >= aADAPTER_SSX2[wAdapter].wNumSsx2IStreams)
					wSSX2_Dst = HPI_SSX2_INVALID_INDEX;
			}
			if (wSSX2_Src != HPI_SSX2_INVALID_INDEX && wSSX2_Dst != HPI_SSX2_INVALID_INDEX) {
				if (hr.u.m.wControlIndex ==
					HPI_CONTROL_CONNECTION && ((hr.u.m.wSrcNodeIndex % wStereoPerSSX2)
						|| (hr.u.m.wDstNodeIndex % wStereoPerSSX2))) {
					wHide = 1;
				}
			} else if (wSSX2_Src != HPI_SSX2_INVALID_INDEX) {
				if (hr.u.m.wControlIndex == HPI_CONTROL_VOLUME) {
					aOSTREAM_SSX2[wAdapter][wSSX2_Src].hwStreamInfo[hr.u.m.wSrcNodeIndex %
						wStereoPerSSX2].wControlIndex = wIndex;
					if (hr.u.m.wSrcNodeIndex % wStereoPerSSX2 !=
						hr.u.m.wDstNodeIndex % wStereoPerSSX2) {
						wHide = 1;
						// Mute the control before we hide it.
						if (hr.u.m.wDstNodeType + HPI_DESTNODE_NONE == HPI_DESTNODE_LINEOUT) {
							hm_mute.wObjIndex = wIndex;
							HPI_MessageEx(&hm_mute, &hr_mute, NULL);
						}
					} else {
						aOSTREAM_SSX2[wAdapter]
							[wSSX2_Src].ssx2info.wControlIndex = wDIndex;
						wMaster = 1;
					}
				} else if (hr.u.m.wControlIndex == HPI_CONTROL_CHANNEL_MODE) {
					wHide = 1;
				}
			} else if (wSSX2_Dst != HPI_SSX2_INVALID_INDEX) {
				if (hr.u.m.wControlIndex == HPI_CONTROL_MULTIPLEXER) {
					// IStream Mux, save the index
					aISTREAM_SSX2[wAdapter][wSSX2_Dst].hwStreamInfo[hr.u.m.wDstNodeIndex %
						wStereoPerSSX2].wControlIndex = wIndex;
					if (hr.u.m.wDstNodeIndex % wStereoPerSSX2) {
						wHide = 1;
					} else {
						aISTREAM_SSX2[wAdapter]
							[wSSX2_Dst].ssx2info.wControlIndex = wDIndex;
						wMaster = 1;
						ScanMuxSources(wAdapter, &aISTREAM_SSX2[wAdapter]
							[wSSX2_Dst], wIndex);
					}
				} else if (hr.u.m.wControlIndex ==
					HPI_CONTROL_CONNECTION && ((hr.u.m.wSrcNodeIndex % wStereoPerSSX2)
						|| (hr.u.m.wDstNodeIndex % wStereoPerSSX2))) {
					wHide = 1;
				} else if (hr.u.m.wControlIndex == HPI_CONTROL_CHANNEL_MODE) {
					wHide = 1;
				} else if (hr.u.m.wControlIndex == HPI_CONTROL_VOX) {
					wHide = 1;
				}
			}
			if (wHide) {
				aADAPTER_SSX2[wAdapter].tControlH2D.pTable[wIndex] = HPI_SSX2_INVALID_INDEX;
			} else {
				aADAPTER_SSX2[wAdapter].tControlH2D.pTable[wIndex] = wDIndex;
				aADAPTER_SSX2[wAdapter].tControlD2H.pTable[wDIndex] = wIndex;
				if (wMaster)
					aADAPTER_SSX2[wAdapter].tControlD2H.pTable[wDIndex] |= HPI_SSX2_MASTERCONTROL;
				wDIndex++;
			}
			wIndex++;
		}
	} while (!hr.wError);

	for (; wDIndex < aADAPTER_SSX2[wAdapter].tControlD2H.wNumEntries; wDIndex++) {
		wIndex = aADAPTER_SSX2[wAdapter].tControlD2H.pTable[wDIndex] = HPI_SSX2_INVALID_INDEX;
	}

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE);
	hm.wAdapterIndex = wAdapter;
	HPI_MessageEx(&hm, &hr, NULL);
}

static void HPISSX2_Reset(uint16_t wAdapterIndex)
{

	if (wAdapterIndex == HPIMSGX_ALLADAPTERS) {
		uint16_t wAdapter;
		for (wAdapter = 0; wAdapter < HPI_MAX_ADAPTERS; wAdapter++)
			FreeIndexMaps(wAdapter);
		memset(aADAPTER_SSX2, 0, sizeof(aADAPTER_SSX2));
		memset(aOSTREAM_SSX2, 0, sizeof(aOSTREAM_SSX2));
		memset(aISTREAM_SSX2, 0, sizeof(aISTREAM_SSX2));
	} else if (wAdapterIndex < HPI_MAX_ADAPTERS) {
		FreeIndexMaps(wAdapterIndex);
		memset(&aADAPTER_SSX2[wAdapterIndex], 0, sizeof(aADAPTER_SSX2[0]));
		memset(&aOSTREAM_SSX2[wAdapterIndex], 0, sizeof(aOSTREAM_SSX2[0]));
		memset(&aISTREAM_SSX2[wAdapterIndex], 0, sizeof(aISTREAM_SSX2[0]));
	}
}

static void HPISSX2_Init(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	uint16_t wFirstAdapter;
	uint16_t wLastAdapter;
	uint16_t wAdapter;

	if (phm) {
		wFirstAdapter = phm->wAdapterIndex;
		wLastAdapter = phm->wAdapterIndex;

		if (phm->wAdapterIndex >= HPI_MAX_ADAPTERS)
			return;
	} else {
		wFirstAdapter = 0;
		wLastAdapter = HPI_MAX_ADAPTERS;
	}
	for (wAdapter = wFirstAdapter; wAdapter <= wLastAdapter; ++wAdapter) {
		if (!aADAPTER_SSX2[wAdapter].wPrepared) {
			uint16_t wNumOStreams = 0;
			uint16_t wNumIStreams = 0;
			uint16_t wNumOuts = 0;
			uint16_t wNumIns = 0;
			uint16_t wNumInsOrOuts = 0;
			uint16_t wNumCtrls = 0;
			uint16_t wStereoPerSSX2;
			int i, j;

			CountLinesAndControls(wAdapter, &wNumOuts, &wNumIns, &wNumCtrls);
			HPISSX2_Reset(wAdapter);

			// Allocate the tables for index translation
			if (wNumIns > wNumOuts)
				wNumInsOrOuts = wNumIns;
			else
				wNumInsOrOuts = wNumOuts;

			if (wNumInsOrOuts < 2) {	// Can't do SSX2
				return;
			} else if (wNumInsOrOuts < 4) {	// Less than 4 stereo outputs => do quad
				aADAPTER_SSX2[wAdapter].wSsx2Channels = 4;
			} else {	// 4 or more stereo outputs => do eight channel
				aADAPTER_SSX2[wAdapter].wSsx2Channels = 8;
			}
			wStereoPerSSX2 = aADAPTER_SSX2[wAdapter].wSsx2Channels / 2;

			// Test if the adapter supports the grouping API
			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ADAPTER, HPI_ADAPTER_GET_PROPERTY);
			hm.wAdapterIndex = wAdapter;
			hm.u.ax.property_set.wProperty = HPI_ADAPTER_PROPERTY_ENABLE_SSX2;
			HPI_MessageEx(&hm, &hr, NULL);
			if (!hr.wError) {
				HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ADAPTER, HPI_ADAPTER_GET_INFO);
				hm.wAdapterIndex = wAdapter;
				HPI_MessageEx(&hm, &hr, NULL);
				if (!hr.wError) {
					wNumOStreams = hr.u.ax.info.wNumOStreams;
					wNumIStreams = hr.u.ax.info.wNumIStreams;

					aADAPTER_SSX2[wAdapter].wNumSsx2OStreams = wNumOStreams / wStereoPerSSX2;
					aADAPTER_SSX2[wAdapter].wNumSsx2IStreams = wNumIStreams / wStereoPerSSX2;
#ifdef HPI_HIDE_STEREO
					aADAPTER_SSX2[wAdapter].wFirstSsx2OStream = 0;
					aADAPTER_SSX2[wAdapter].wFirstSsx2IStream = 0;
#else
					aADAPTER_SSX2[wAdapter].wFirstSsx2OStream = wNumOStreams;
					aADAPTER_SSX2[wAdapter].wFirstSsx2IStream = wNumIStreams;
					wNumOStreams += aADAPTER_SSX2[wAdapter].wNumSsx2OStreams;
					wNumIStreams += aADAPTER_SSX2[wAdapter].wNumSsx2IStreams;
#endif
					for (i = 0; i < aADAPTER_SSX2[wAdapter].wNumSsx2OStreams; ++i) {
						//aOSTREAM_SSX2[wAdapter][i].ssx2info.wAdapterIndex = wAdapter;
						aOSTREAM_SSX2[wAdapter][i].ssx2info.wStreamIndex =
							aADAPTER_SSX2[wAdapter].wFirstSsx2OStream + i;

						for (j = 0; j < wStereoPerSSX2; ++j) {
							//aOSTREAM_SSX2[wAdapter][i].hwStreamInfo[j].wAdapterIndex = wAdapter;
							aOSTREAM_SSX2
								[wAdapter][i].hwStreamInfo
								[j].wStreamIndex = i * wStereoPerSSX2 + j;
							//initialize control index
							aOSTREAM_SSX2
								[wAdapter][i].hwStreamInfo
								[j].wControlIndex = HPI_SSX2_INVALID_INDEX;
						}
						aOSTREAM_SSX2[wAdapter][i].numHwStreams = wStereoPerSSX2;
					}
					for (i = 0; i < aADAPTER_SSX2[wAdapter].wNumSsx2IStreams; ++i) {
						//aISTREAM_SSX2[wAdapter][i].ssx2info.wAdapterIndex = wAdapter;
						aISTREAM_SSX2[wAdapter][i].ssx2info.wStreamIndex =
							aADAPTER_SSX2[wAdapter].wFirstSsx2IStream + i;

						for (j = 0; j < wStereoPerSSX2; ++j) {
							//aISTREAM_SSX2[wAdapter][i].hwStreamInfo[j].wAdapterIndex = wAdapter;
							aISTREAM_SSX2
								[wAdapter][i].hwStreamInfo
								[j].wStreamIndex = i * wStereoPerSSX2 + j;
							//initialize control index
							aISTREAM_SSX2
								[wAdapter][i].hwStreamInfo
								[j].wControlIndex = HPI_SSX2_INVALID_INDEX;
						}
						aISTREAM_SSX2[wAdapter][i].numHwStreams = wStereoPerSSX2;
					}
					AllocateIndexMaps(wAdapter, wNumOStreams, wNumIStreams, wNumCtrls);
					InitializeIndexMaps(wAdapter, wStereoPerSSX2);
					ScanControls(wAdapter, wStereoPerSSX2);
					aADAPTER_SSX2[wAdapter].wPrepared = 1;
					phr->wError = 0;
				}
			}
		}
	}
}

static void HPISSX2_Cleanup(uint16_t wAdapterIndex, void *hOwner)
{
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	uint16_t wFirstAdapter;
	uint16_t wLastAdapter;
	uint16_t wAdapter;
	int i;

	if (wAdapterIndex == HPIMSGX_ALLADAPTERS) {
		wFirstAdapter = 0;
		wLastAdapter = HPI_MAX_ADAPTERS - 1;
	} else {
		wFirstAdapter = wAdapterIndex;
		wLastAdapter = wAdapterIndex;

		if (wAdapterIndex >= HPI_MAX_ADAPTERS)
			return;
	}
	for (wAdapter = wFirstAdapter; wAdapter <= wLastAdapter; ++wAdapter) {
		if (!aADAPTER_SSX2[wAdapter].wPrepared)
			continue;
		for (i = 0; i < aADAPTER_SSX2[wAdapter].wNumSsx2OStreams; ++i) {
			if (aOSTREAM_SSX2[wAdapter][i].nOpen && aOSTREAM_SSX2[wAdapter][i].hOwner == hOwner) {
				HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE);
				hm.wAdapterIndex = wAdapter;
				hm.wObjIndex = aOSTREAM_SSX2[wAdapter][i].ssx2info.wStreamIndex;

				OutStreamClose(&hm, &hr, hOwner);
			}
		}
		for (i = 0; i < aADAPTER_SSX2[wAdapter].wNumSsx2IStreams; ++i) {
			if (aISTREAM_SSX2[wAdapter][i].nOpen && aISTREAM_SSX2[wAdapter][i].hOwner == hOwner) {
				HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_CLOSE);
				hm.wAdapterIndex = wAdapter;
				hm.wObjIndex = aISTREAM_SSX2[wAdapter][i].ssx2info.wStreamIndex;

				InStreamClose(&hm, &hr, hOwner);
			}
		}
	}
}

//*************************************************************************************
// Out Stream Functions

static void OutStreamOpen(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	int i;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2OStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen) {
		phr->wError = HPI_ERROR_OBJ_ALREADY_OPEN;
		return;
	}
	// Open the underlying streams
	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_OPEN);
	hm.wAdapterIndex = phm->wAdapterIndex;

	for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
		HPI_MessageEx(&hm, &hr, hOwner);

		if (hr.wError != 0) {
			break;
		}
		pSsx2Strm->hwStreamInfo[i].dwBytesWrittenToStream = 0;
	}

	if (hr.wError == 0) {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GROUP_ADD);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
		hm.u.d.u.stream.wObjectType = HPI_OBJ_OSTREAM;

		for (i = 1; i < pSsx2Strm->numHwStreams; ++i) {
			hm.u.d.u.stream.wStreamIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			HPI_MessageEx(&hm, &hr, hOwner);

			if (hr.wError != 0) {
				// all streams opened so set index to numHwStreams
				i = pSsx2Strm->numHwStreams;
				break;
			}
		}
	}

	if (hr.wError != 0) {
		phr->wError = hr.wError;

		// Error, close the ones previously opened
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GROUP_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
		HPI_MessageEx(&hm, &hr, hOwner);

		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE);
		hm.wAdapterIndex = phm->wAdapterIndex;
		for (--i; i >= 0; --i) {
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			HPI_MessageEx(&hm, &hr, hOwner);
		}
	} else {
		pSsx2Strm->hOwner = hOwner;
		pSsx2Strm->nOpen = 1;
	}
}

static void OutStreamClose(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	int i;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2OStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen && pSsx2Strm->hOwner == hOwner) {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GROUP_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
		HPI_MessageEx(&hm, &hr, hOwner);

		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE);
		hm.wAdapterIndex = phm->wAdapterIndex;

		for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			HPI_MessageEx(&hm, &hr, hOwner);
		}

		// free the buffer
		if (pSsx2Strm->pDataBuffer) {
			void *pB = pSsx2Strm->pDataBuffer;
			pSsx2Strm->pDataBuffer = NULL;
			pSsx2Strm->dwDataBufferSize = 0;
			HpiOs_MemFree(pB);
		}
		pSsx2Strm->dwPartialFrameBytes = 0;
		pSsx2Strm->nBytesPerSample = 0;
		pSsx2Strm->hOwner = NULL;
		pSsx2Strm->nOpen = 0;
	} else {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
	}
}

static void OutStreamGetInfoEx(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	uint32_t dwBuffFreeBytes, dwMinBuffFreeBytes;
	int i;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2OStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen == 0) {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
		return;
	}

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GET_INFO);
	hm.wAdapterIndex = phm->wAdapterIndex;

	phr->u.d.u.stream_info.dwBufferSize = 0;
	phr->u.d.u.stream_info.dwDataAvailable = 0;
	phr->u.d.u.stream_info.dwAuxiliaryDataAvailable = 0;

	if (pSsx2Strm->numActiveHwStreams == 0)
		pSsx2Strm->numActiveHwStreams = aADAPTER_SSX2[phm->wAdapterIndex].wSsx2Channels / 2;

	for (i = 0; i < pSsx2Strm->numActiveHwStreams; ++i) {
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
		HPI_MessageEx(&hm, &hr, NULL);

		if (hr.wError != 0) {
			phr->wError = hr.wError;
			break;
		}
		dwBuffFreeBytes = hr.u.d.u.stream_info.dwBufferSize - hr.u.d.u.stream_info.dwDataAvailable;
		if (i == 0) {
			phr->u.d.u.stream_info.wState = hr.u.d.u.stream_info.wState;
			phr->u.d.u.stream_info.dwSamplesTransferred = hr.u.d.u.stream_info.dwSamplesTransferred;
			dwMinBuffFreeBytes = dwBuffFreeBytes;
		} else {
			if (dwMinBuffFreeBytes > dwBuffFreeBytes)
				dwMinBuffFreeBytes = dwBuffFreeBytes;
		}
		phr->u.d.u.stream_info.dwBufferSize += hr.u.d.u.stream_info.dwBufferSize;
		phr->u.d.u.stream_info.dwDataAvailable += hr.u.d.u.stream_info.dwDataAvailable;
		phr->u.d.u.stream_info.dwAuxiliaryDataAvailable += hr.u.d.u.stream_info.dwAuxiliaryDataAvailable;

		pSsx2Strm->hwStreamInfo[i].dwLastBufferSize = hr.u.d.u.stream_info.dwBufferSize;
		pSsx2Strm->hwStreamInfo[i].dwLastDataToPlay = hr.u.d.u.stream_info.dwDataAvailable;
	}
	if (phr->u.d.u.stream_info.dwBufferSize -
		phr->u.d.u.stream_info.dwDataAvailable > dwMinBuffFreeBytes * pSsx2Strm->numActiveHwStreams) {
		phr->u.d.u.stream_info.dwDataAvailable =
			phr->u.d.u.stream_info.dwBufferSize - dwMinBuffFreeBytes * pSsx2Strm->numActiveHwStreams;
	}
	phr->u.d.u.stream_info.dwDataAvailable += pSsx2Strm->dwPartialFrameBytes;
	pSsx2Strm->ssx2info.dwLastBufferSize = phr->u.d.u.stream_info.dwBufferSize;
	pSsx2Strm->ssx2info.dwLastDataToPlay = phr->u.d.u.stream_info.dwDataAvailable;
}

static void OutStreamWrite(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	int nBytesPerSample;
	int nActiveHwStreams;
	int i;
	unsigned int j;
	int nSamplesToTransfer;
	uint8_t *CurBufferPosition;
	int nUsePartial = 0;
	int nPrtlBytesLeft = 0;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2OStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen == 0) {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
		return;
	}

	nBytesPerSample = FormatBytesPerSample(&phm->u.d.u.data.format);
	nActiveHwStreams = FormatActiveHwStreams(&phm->u.d.u.data.format);

	if (!pSsx2Strm->nBytesPerSample) {
		pSsx2Strm->nBytesPerSample = nBytesPerSample;
		pSsx2Strm->numActiveHwStreams = nActiveHwStreams;

		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE);
		hm.wAdapterIndex = phm->wAdapterIndex;

		if (nActiveHwStreams < pSsx2Strm->numHwStreams) {
			// Regroup using only active streams
			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GROUP_RESET);
			hm.wAdapterIndex = phm->wAdapterIndex;
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
			HPI_MessageEx(&hm, &hr, NULL);

			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GROUP_ADD);
			hm.wAdapterIndex = phm->wAdapterIndex;
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
			hm.u.d.u.stream.wObjectType = HPI_OBJ_OSTREAM;

			for (i = 1; i < nActiveHwStreams; ++i) {
				hm.u.d.u.stream.wStreamIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
				HPI_MessageEx(&hm, &hr, NULL);
			}

			if (nActiveHwStreams == 1)	// shortcut, just pass it on to the real stream
			{
				uint16_t wSaveIndex = phm->wObjIndex;

				phm->wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
				HPI_MessageEx(phm, phr, NULL);
				phm->wObjIndex = wSaveIndex;
				return;
			}
		}
		pSsx2Strm->dwBytesPerFrame = nBytesPerSample * 2;
		if (pSsx2Strm->dwBytesPerFrame % 4)
			pSsx2Strm->dwBytesPerFrame *= 2;
		pSsx2Strm->dwBytesPerFrame *= nActiveHwStreams;
		if (pSsx2Strm->numActiveHwStreams < pSsx2Strm->numHwStreams) {
			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GROUP_RESET);
			hm.wAdapterIndex = phm->wAdapterIndex;
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
			HPI_MessageEx(&hm, &hr, NULL);

			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GROUP_ADD);
			hm.wAdapterIndex = phm->wAdapterIndex;
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
			hm.u.d.u.stream.wObjectType = HPI_OBJ_OSTREAM;

			for (i = 1; i < pSsx2Strm->numActiveHwStreams; ++i) {
				hm.u.d.u.stream.wStreamIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
				HPI_MessageEx(&hm, &hr, NULL);
			}
		}
		// Allocate buffer for individual stream writes
		pSsx2Strm->dwDataBufferSize = pSsx2Strm->nBytesPerSample * 2 * HPI_SSX2_SUBBUFMAXSAMPLES;
		pSsx2Strm->pDataBuffer = HpiOs_MemAlloc(pSsx2Strm->dwDataBufferSize);
	} else {
		if (pSsx2Strm->nBytesPerSample != nBytesPerSample) {
			phr->wError = HPI_ERROR_INVALID_FORMAT;
			return;
		}
		if (pSsx2Strm->numActiveHwStreams != nActiveHwStreams) {
			phr->wError = HPI_ERROR_INVALID_CHANNELS;
			return;
		}
	}

	if (pSsx2Strm->ssx2info.dwLastBufferSize - pSsx2Strm->ssx2info.dwLastDataToPlay < phm->u.d.u.data.dwDataSize) {
		phr->wError = HPI_ERROR_INVALID_DATASIZE;
		return;
	}

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_WRITE);
	memcpy(&hm.u.d.u.data.format, &phm->u.d.u.data.format, sizeof(hm.u.d.u.data.format));
	hm.u.d.u.data.format.wChannels = 2;
	hm.u.d.u.data.pbData = (uint8_t *)pSsx2Strm->pDataBuffer;
	hm.wAdapterIndex = phm->wAdapterIndex;
	CurBufferPosition = (uint8_t *)phm->u.d.u.data.pbData;

	if (pSsx2Strm->dwPartialFrameBytes) {
		int nPrtlBytesToCopy = 0;

		// # bytes needed to fill partial sample   =   bytes_per_frame   -   # bytes left over from last
		nPrtlBytesLeft = pSsx2Strm->dwBytesPerFrame - pSsx2Strm->dwPartialFrameBytes;
		nPrtlBytesToCopy = nPrtlBytesLeft;
		if ((int)phm->u.d.u.data.dwDataSize < nPrtlBytesToCopy)
			nPrtlBytesToCopy = phm->u.d.u.data.dwDataSize;

		// Complete the partial sample
		for (i = 0; i < nPrtlBytesToCopy; ++i) {
			pSsx2Strm->PartialFrame[i + pSsx2Strm->dwPartialFrameBytes] = *CurBufferPosition;
			++CurBufferPosition;
		}

		// If the out stream write is insufficient to complete the partial sample, just return
		if (nPrtlBytesToCopy < nPrtlBytesLeft) {
			pSsx2Strm->dwPartialFrameBytes += nPrtlBytesToCopy;
			return;
		}
		// Back this up one full multichannel sample because the first one will come from the partial sample buffer
		CurBufferPosition -= pSsx2Strm->dwBytesPerFrame;

		// nUsePartial = stereo samples per frame
		nUsePartial =
			pSsx2Strm->dwBytesPerFrame / (pSsx2Strm->nBytesPerSample * pSsx2Strm->numActiveHwStreams * 2);
	}
	// Set nSamplesToTransfer to number of frames then multiply by samples per frame
	nSamplesToTransfer = (phm->u.d.u.data.dwDataSize - nPrtlBytesLeft) / pSsx2Strm->dwBytesPerFrame;
	nSamplesToTransfer *=
		pSsx2Strm->dwBytesPerFrame / (pSsx2Strm->nBytesPerSample * pSsx2Strm->numActiveHwStreams * 2);

	// save leftovers for next time = ( size in bytes  -  bytes used to fill partial sample ) % bytes_per_frame
	pSsx2Strm->dwPartialFrameBytes = (phm->u.d.u.data.dwDataSize - nPrtlBytesLeft) % pSsx2Strm->dwBytesPerFrame;

	while (nSamplesToTransfer) {
		int nTransferSize;

		if (nSamplesToTransfer + nUsePartial > HPI_SSX2_SUBBUFMAXSAMPLES)
			nTransferSize = HPI_SSX2_SUBBUFMAXSAMPLES;
		else
			nTransferSize = nSamplesToTransfer + nUsePartial;

		hm.u.d.u.data.dwDataSize = nTransferSize * 2 * pSsx2Strm->nBytesPerSample;

		for (i = 0; i < pSsx2Strm->numActiveHwStreams; ++i) {
			int nSample, byte;

			for (nSample = 0; nSample < nUsePartial; ++nSample) {
				for (byte = 0; byte < pSsx2Strm->nBytesPerSample * 2; ++byte)
					((uint8_t *)pSsx2Strm->pDataBuffer)[nSample *
						pSsx2Strm->nBytesPerSample * 2 +
						byte] =
						pSsx2Strm->PartialFrame[pSsx2Strm->nBytesPerSample *
						2 * (nSample * pSsx2Strm->numActiveHwStreams + i) + byte];
			}
			for (; nSample < nTransferSize; ++nSample) {
				for (byte = 0; byte < pSsx2Strm->nBytesPerSample * 2; ++byte)
					((uint8_t *)pSsx2Strm->pDataBuffer)[nSample *
						pSsx2Strm->nBytesPerSample * 2 +
						byte] =
						CurBufferPosition[pSsx2Strm->nBytesPerSample * 2 *
						(nSample * pSsx2Strm->numActiveHwStreams + i) + byte];
			}
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			HPI_MessageEx(&hm, &hr, NULL);
			if (hr.wError == 0) {
				pSsx2Strm->hwStreamInfo[i].dwBytesWrittenToStream +=
					nTransferSize * 2 * pSsx2Strm->nBytesPerSample;
			} else {
				HPI_DEBUG_LOG1(ERROR, "Error sending data to stream %d.\n", i);
			}
			pSsx2Strm->hwStreamInfo[i].dwLastDataToPlay += hm.u.d.u.data.dwDataSize;
		}

		pSsx2Strm->ssx2info.dwLastDataToPlay += hm.u.d.u.data.dwDataSize * pSsx2Strm->numActiveHwStreams;
		nSamplesToTransfer -= (nTransferSize - nUsePartial);
		CurBufferPosition += nTransferSize * pSsx2Strm->nBytesPerSample * pSsx2Strm->numActiveHwStreams * 2;
		nUsePartial = 0;
	}

	for (j = 0; j < pSsx2Strm->dwPartialFrameBytes; ++j) {
		pSsx2Strm->PartialFrame[j] =
			((uint8_t *)phm->u.d.u.data.pbData)[phm->u.d.u.data.dwDataSize -
			pSsx2Strm->dwPartialFrameBytes + j];
	}
}

static void OutStreamStart(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	uint16_t wSaveIndex = phm->wObjIndex;
	int i;
	int timeout;

	pSsx2Strm = GetSsx2OStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	HPI_DEBUG_LOG1(DEBUG, "ssx2 OutStreamStart() index %d\n", phm->wObjIndex);

	// Are all streams ready?
	if(pSsx2Strm->hwStreamInfo[0].dwBytesWrittenToStream){
		timeout = 100;
		for (i = 0; i < pSsx2Strm->numActiveHwStreams; ++i) {
			HPI_MESSAGE hm;
			HPI_RESPONSE hr;

			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_GET_INFO);
			hm.wAdapterIndex = phm->wAdapterIndex;
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			do{
				HPI_MessageEx(&hm, &hr, NULL);

				if( hr.wError ) {
					HPI_DEBUG_LOG2(ERROR,
						"ssx2 OutStreamStart() HPI_OSTREAM_GET_INFO index %d, error %d\n",
						i, hr.wError);
					break;
				}

				HPI_DEBUG_LOG4(DEBUG,
					"ssx2 OutStreamStart() HPI_OSTREAM_GET_INFO index %d, written %d,"
					" avail %d, aux avail %d\n",
					i, pSsx2Strm->hwStreamInfo[i].dwBytesWrittenToStream,
					hr.u.d.u.stream_info.dwDataAvailable,
					hr.u.d.u.stream_info.dwAuxiliaryDataAvailable);

				if(	hr.u.d.u.stream_info.dwDataAvailable +
					hr.u.d.u.stream_info.dwAuxiliaryDataAvailable <
					pSsx2Strm->hwStreamInfo[i].dwBytesWrittenToStream )
						break;

				HpiOs_DelayMicroSeconds(1000);
			}while(--timeout);
			if(!timeout)
				break;
		}
	}

	// Pass message to first real stream
	phm->wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
	HPI_MessageEx(phm, phr, NULL);
	phm->wObjIndex = wSaveIndex;
}

static void OutStreamQueryFormat(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2OStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen == 0) {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
		return;
	}

	memcpy(&hm, phm, sizeof(HPI_MESSAGE));
	memcpy(&hr, phr, sizeof(HPI_RESPONSE));
	hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
	if (FormatActiveHwStreams(&hm.u.d.u.data.format) == 1) {
		HPI_MessageEx(&hm, &hr, NULL);
	} else {
		if ((hm.u.d.u.data.format.wChannels % 2) != 0 ||
			hm.u.d.u.data.format.wChannels > pSsx2Strm->numHwStreams * 2) {
			phr->wError = HPI_ERROR_INVALID_CHANNELS;
			return;
		}
		switch (hm.u.d.u.data.format.wFormat) {
		case HPI_FORMAT_PCM8_UNSIGNED:
		case HPI_FORMAT_PCM16_SIGNED:
		case HPI_FORMAT_PCM16_BIGENDIAN:
		case HPI_FORMAT_PCM32_SIGNED:
		case HPI_FORMAT_PCM32_FLOAT:
		case HPI_FORMAT_PCM24_SIGNED:
			break;
		default:
			phr->wError = HPI_ERROR_INVALID_FORMAT;
			return;
		}
		hm.u.d.u.data.format.wChannels = 2;
		HPI_MessageEx(&hm, &hr, NULL);
	}

	phr->wError = hr.wError;
}

//*************************************************************************************
// In Stream Functions

static void InStreamOpen(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	int i;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2IStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen) {
		phr->wError = HPI_ERROR_OBJ_ALREADY_OPEN;
		return;
	}
	// Open the underlying streams
	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_OPEN);
	hm.wAdapterIndex = phm->wAdapterIndex;

	for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
		HPI_MessageEx(&hm, &hr, hOwner);

		if (hr.wError != 0) {
			break;
		}
	}

	if (hr.wError == 0) {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_GROUP_ADD);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
		hm.u.d.u.stream.wObjectType = HPI_OBJ_ISTREAM;

		for (i = 1; i < pSsx2Strm->numHwStreams; ++i) {
			hm.u.d.u.stream.wStreamIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			HPI_MessageEx(&hm, &hr, hOwner);

			if (hr.wError != 0) {
				// all streams opened so set index to numHwStreams
				i = pSsx2Strm->numHwStreams;
				break;
			}
		}
	}

	if (hr.wError != 0) {
		phr->wError = hr.wError;

		// Error, close the ones previously opened
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_GROUP_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
		HPI_MessageEx(&hm, &hr, hOwner);

		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_CLOSE);
		hm.wAdapterIndex = phm->wAdapterIndex;
		for (--i; i >= 0; --i) {
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			HPI_MessageEx(&hm, &hr, hOwner);
		}
	} else {
		pSsx2Strm->hOwner = hOwner;
		pSsx2Strm->nOpen = 1;
	}
}

static void InStreamClose(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	int i;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2IStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen && pSsx2Strm->hOwner == hOwner) {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_GROUP_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
		HPI_MessageEx(&hm, &hr, hOwner);

		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_CLOSE);
		hm.wAdapterIndex = phm->wAdapterIndex;

		for (i = 0; i < pSsx2Strm->numHwStreams; ++i) {
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			HPI_MessageEx(&hm, &hr, hOwner);
		}

		// free the buffer
		if (pSsx2Strm->pDataBuffer) {
			void *pB = pSsx2Strm->pDataBuffer;
			pSsx2Strm->pDataBuffer = NULL;
			pSsx2Strm->dwDataBufferSize = 0;
			HpiOs_MemFree(pB);
		}
		pSsx2Strm->dwPartialFrameBytes = 0;
		pSsx2Strm->nBytesPerSample = 0;
		pSsx2Strm->hOwner = NULL;
		pSsx2Strm->nOpen = 0;
	} else {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
	}
}

static void InStreamGetInfoEx(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	uint32_t dwBuffBytesAvailable, dwMinBuffBytesAvailable;
	int i;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2IStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen == 0) {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
		return;
	}

	for (i = 0; i < pSsx2Strm->numActiveHwStreams; ++i) {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_GROUP_GETMAP);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
		HPI_MessageEx(&hm, &hr, NULL);
	}

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_GET_INFO);
	hm.wAdapterIndex = phm->wAdapterIndex;

	phr->u.d.u.stream_info.dwBufferSize = 0;
	phr->u.d.u.stream_info.dwDataAvailable = 0;
	phr->u.d.u.stream_info.dwAuxiliaryDataAvailable = 0;

	if (pSsx2Strm->numActiveHwStreams == 0)
		pSsx2Strm->numActiveHwStreams = aADAPTER_SSX2[phm->wAdapterIndex].wSsx2Channels / 2;

	for (i = 0; i < pSsx2Strm->numActiveHwStreams; ++i) {
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
		HPI_MessageEx(&hm, &hr, NULL);

		if (hr.wError != 0) {
			phr->wError = hr.wError;
			break;
		}
		dwBuffBytesAvailable = hr.u.d.u.stream_info.dwDataAvailable;
		if (i == 0) {
			phr->u.d.u.stream_info.wState = hr.u.d.u.stream_info.wState;
			phr->u.d.u.stream_info.dwSamplesTransferred = hr.u.d.u.stream_info.dwSamplesTransferred;
			dwMinBuffBytesAvailable = dwBuffBytesAvailable;
		} else {
			if (dwMinBuffBytesAvailable > dwBuffBytesAvailable)
				dwMinBuffBytesAvailable = dwBuffBytesAvailable;
		}
		phr->u.d.u.stream_info.dwBufferSize += hr.u.d.u.stream_info.dwBufferSize;
		phr->u.d.u.stream_info.dwDataAvailable += hr.u.d.u.stream_info.dwDataAvailable;
		phr->u.d.u.stream_info.dwAuxiliaryDataAvailable += hr.u.d.u.stream_info.dwAuxiliaryDataAvailable;

		pSsx2Strm->hwStreamInfo[i].dwLastBufferSize = hr.u.d.u.stream_info.dwBufferSize;
		pSsx2Strm->hwStreamInfo[i].dwLastDataToPlay = hr.u.d.u.stream_info.dwDataAvailable;
	}
	if (phr->u.d.u.stream_info.dwDataAvailable > dwMinBuffBytesAvailable * pSsx2Strm->numActiveHwStreams) {
		phr->u.d.u.stream_info.dwDataAvailable = dwMinBuffBytesAvailable * pSsx2Strm->numActiveHwStreams;
	}
	phr->u.d.u.stream_info.dwDataAvailable += pSsx2Strm->dwPartialFrameBytes;
	pSsx2Strm->ssx2info.dwLastBufferSize = phr->u.d.u.stream_info.dwBufferSize;
	pSsx2Strm->ssx2info.dwLastDataToPlay = phr->u.d.u.stream_info.dwDataAvailable;
}

static void InStreamRead(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	uint8_t *CurBufferPosition;
	int i, nStream;
	int nChans;
	int nSamplesToTransfer;	// Number of samples left to transfer
	int nSamplesToRead;	// Number of samples to transfer this iteration
	int nSamplesToCopy;	// Number of samples to copy into output buffer (nSamplesToRead - partial frame)
	int nPartialBytes;	// Number of bytes required from partial frame buffer at end
	int nSamplesPerFrame;	// Number of multichannel samples per frame (usually 1, 2 for 24 bit)

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2IStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen == 0) {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
		return;
	}
	// Verify that a valid format was set in InStreamSetFormat
	if (!pSsx2Strm->pDataBuffer) {
		phr->wError = HPI_ERROR_INVALID_FORMAT;
		return;
	}
	// Verify that there's enough data in the input buffers
	if (phm->u.d.u.data.dwDataSize > pSsx2Strm->ssx2info.dwLastDataToPlay) {
		phr->wError = HPI_ERROR_INVALID_DATASIZE;
		return;
	}

	nChans = pSsx2Strm->numActiveHwStreams * 2;
	nSamplesPerFrame = pSsx2Strm->dwBytesPerFrame / pSsx2Strm->nBytesPerSample / nChans;

	CurBufferPosition = (uint8_t *)phm->u.d.u.data.pbData;

	// Use old partial frame first
	if (pSsx2Strm->dwPartialFrameBytes) {
		uint8_t *PartialFrameBuf =
			&pSsx2Strm->PartialFrame[pSsx2Strm->dwBytesPerFrame - pSsx2Strm->dwPartialFrameBytes];
		// Source location for partial frame bytes is the Nth byte in the buffer
		// N = bytes per frame - bytes leftover
		if (pSsx2Strm->dwPartialFrameBytes > phm->u.d.u.data.dwDataSize) {
			memcpy(CurBufferPosition, PartialFrameBuf, phm->u.d.u.data.dwDataSize);
			pSsx2Strm->dwPartialFrameBytes -= phm->u.d.u.data.dwDataSize;
			return;
		} else {
			memcpy(CurBufferPosition, PartialFrameBuf, pSsx2Strm->dwPartialFrameBytes);
			CurBufferPosition += pSsx2Strm->dwPartialFrameBytes;
		}
	}
	// nSamplesToTransfer to hold # Frames then multiply by samples per frame after computing nPartialBytes
	nSamplesToTransfer = (phm->u.d.u.data.dwDataSize - pSsx2Strm->dwPartialFrameBytes) / pSsx2Strm->dwBytesPerFrame;

	// Calculate additional bytes from partial frame (appended to end of buffer)
	nPartialBytes =
		(phm->u.d.u.data.dwDataSize - pSsx2Strm->dwPartialFrameBytes) -
		pSsx2Strm->dwBytesPerFrame * nSamplesToTransfer;
	nSamplesToTransfer *= nSamplesPerFrame;

	// If partial bytes will be needed at the end of the buffer add nSamplesPerFrame to nSamplesToTransfer
	if (nPartialBytes != 0)
		nSamplesToTransfer += nSamplesPerFrame;

	hm = *phm;
	hm.u.d.u.data.format.wChannels = 2;
	hm.u.d.u.data.pbData = pSsx2Strm->pDataBuffer;
	HPI_InitResponse(&hr, hm.wObject, hm.wFunction, 0);

	// We know how much to read, so it begins
	while (nSamplesToTransfer) {
		if (nSamplesToTransfer > HPI_SSX2_SUBBUFMAXSAMPLES)
			nSamplesToRead = HPI_SSX2_SUBBUFMAXSAMPLES;
		else
			nSamplesToRead = nSamplesToTransfer;

		if (nSamplesToRead == nSamplesToTransfer && nPartialBytes != 0)	// Is it the last read? w Partial?
			nSamplesToCopy = nSamplesToRead - nSamplesPerFrame;
		else
			nSamplesToCopy = nSamplesToRead;

		for (nStream = 0; nStream < pSsx2Strm->numActiveHwStreams; ++nStream) {
			int byte;

			hm.u.d.u.data.dwDataSize = nSamplesToRead * pSsx2Strm->nBytesPerSample * 2;
			hm.wObjIndex = pSsx2Strm->hwStreamInfo[nStream].wStreamIndex;

			HPI_MessageEx(&hm, &hr, NULL);
			if (hr.wError != 0) {
				HPI_DEBUG_LOG1(ERROR,
					"Error reading data from stream %d.\n",
					pSsx2Strm->hwStreamInfo[nStream].wStreamIndex);
				phr->wError = hr.wError;
				return;
			}
			// Copy the data directly into the output buffer
			for (i = 0; i < nSamplesToCopy; ++i) {
				for (byte = 0; byte < pSsx2Strm->nBytesPerSample * 2; ++byte) {
					CurBufferPosition[pSsx2Strm->nBytesPerSample *
						2 * (i * pSsx2Strm->numActiveHwStreams + nStream) + byte] = ((uint8_t *)
						pSsx2Strm->pDataBuffer)[pSsx2Strm->nBytesPerSample * 2 * i + byte];
				}
			}
			// On last read, copy the last bit to the partial frame (if needed)
			for (; i < nSamplesToRead; ++i) {
				for (byte = 0; byte < pSsx2Strm->nBytesPerSample * 2; ++byte) {
					pSsx2Strm->PartialFrame[pSsx2Strm->nBytesPerSample * 2 * ((i - nSamplesToCopy)
							* pSsx2Strm->numActiveHwStreams + nStream) + byte] = ((uint8_t *)
						pSsx2Strm->pDataBuffer)[pSsx2Strm->nBytesPerSample * 2 * i + byte];
				}
			}
		}
		CurBufferPosition += nSamplesToCopy * pSsx2Strm->nBytesPerSample * pSsx2Strm->numActiveHwStreams * 2;
		nSamplesToTransfer -= nSamplesToRead;
	}
	if (nPartialBytes) {
		memcpy(CurBufferPosition, pSsx2Strm->PartialFrame, nPartialBytes);
		pSsx2Strm->dwPartialFrameBytes = pSsx2Strm->dwBytesPerFrame - nPartialBytes;
	} else {
		pSsx2Strm->dwPartialFrameBytes = 0;
	}
	pSsx2Strm->ssx2info.dwLastDataToPlay -= phm->u.d.u.data.dwDataSize;
}

static void InStreamSetFormat(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;
	int nActiveHwStreams;
	int i;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2IStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen == 0) {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
		return;
	}

	nActiveHwStreams = FormatActiveHwStreams(&phm->u.d.u.data.format);

	if (nActiveHwStreams < pSsx2Strm->numHwStreams) {
		// Regroup using only active streams
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_GROUP_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
		HPI_MessageEx(&hm, &hr, NULL);

		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_GROUP_ADD);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
		hm.u.d.u.stream.wObjectType = HPI_OBJ_ISTREAM;

		for (i = 1; i < nActiveHwStreams; ++i) {
			hm.u.d.u.stream.wStreamIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
			HPI_MessageEx(&hm, &hr, NULL);
		}
	}

	hm = *phm;

	if (nActiveHwStreams > 1) {
		hm.u.d.u.data.format.wChannels = 2;

		if (nActiveHwStreams > pSsx2Strm->numHwStreams) {
			phr->wError = HPI_ERROR_INVALID_CHANNELS;
			return;
		}
		switch (hm.u.d.u.data.format.wFormat) {
		case HPI_FORMAT_PCM8_UNSIGNED:
		case HPI_FORMAT_PCM16_SIGNED:
		case HPI_FORMAT_PCM16_BIGENDIAN:
		case HPI_FORMAT_PCM32_SIGNED:
		case HPI_FORMAT_PCM32_FLOAT:
		case HPI_FORMAT_PCM24_SIGNED:
			break;
		default:
			phr->wError = HPI_ERROR_INVALID_FORMAT;
			return;
		}
	}

	for (i = 0; i < nActiveHwStreams; ++i) {
		hm.wObjIndex = pSsx2Strm->hwStreamInfo[i].wStreamIndex;
		HPI_MessageEx(&hm, &hr, NULL);

		if (hr.wError != 0) {
			phr->wError = hr.wError;
			break;
		}
	}

	if (i == nActiveHwStreams) {
		pSsx2Strm->numActiveHwStreams = nActiveHwStreams;
		pSsx2Strm->nBytesPerSample = FormatBytesPerSample(&phm->u.d.u.data.format);

		if (pSsx2Strm->numActiveHwStreams > 1) {
			pSsx2Strm->dwBytesPerFrame = pSsx2Strm->nBytesPerSample * 2;
			if (pSsx2Strm->dwBytesPerFrame % 4)
				pSsx2Strm->dwBytesPerFrame *= 2;
			pSsx2Strm->dwBytesPerFrame *= pSsx2Strm->numActiveHwStreams;

			// Allocate buffer for individual stream writes
			if (pSsx2Strm->pDataBuffer) {
				void *pB = pSsx2Strm->pDataBuffer;
				pSsx2Strm->pDataBuffer = NULL;
				pSsx2Strm->dwDataBufferSize = 0;
				HpiOs_MemFree(pB);
			}
			pSsx2Strm->dwDataBufferSize = pSsx2Strm->nBytesPerSample * 2 * HPI_SSX2_SUBBUFMAXSAMPLES;
			pSsx2Strm->pDataBuffer = HpiOs_MemAlloc(pSsx2Strm->dwDataBufferSize);
			pSsx2Strm->dwPartialFrameBytes = 0;
		}
	}
}

static void InStreamQueryFormat(HPI_MESSAGE *phm, HPI_RESPONSE *phr)
{
	PHPI_SSX2_STREAM_INFO pSsx2Strm;
	HPI_MESSAGE hm;
	HPI_RESPONSE hr;

	HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
	pSsx2Strm = GetSsx2IStreamInfoStruct(phm->wAdapterIndex, phm->wObjIndex);

	// Sanity check
	if (pSsx2Strm->ssx2info.wStreamIndex != phm->wObjIndex) {
		phr->wError = HPI_ERROR_INVALID_OBJ_INDEX;
		return;
	}

	if (pSsx2Strm->nOpen == 0) {
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
		return;
	}

	memcpy(&hm, phm, sizeof(HPI_MESSAGE));
	memcpy(&hr, phr, sizeof(HPI_RESPONSE));
	hm.wObjIndex = pSsx2Strm->hwStreamInfo[0].wStreamIndex;
	if (hm.u.d.u.data.format.wChannels > 2) {
		if (hm.u.d.u.data.format.wChannels >
			pSsx2Strm->numHwStreams * 2 || (hm.u.d.u.data.format.wChannels % 2) != 0) {
			phr->wError = HPI_ERROR_INVALID_CHANNELS;
			return;
		}
		hm.u.d.u.data.format.wChannels = 2;
		switch (hm.u.d.u.data.format.wFormat) {
		case HPI_FORMAT_PCM8_UNSIGNED:
		case HPI_FORMAT_PCM16_SIGNED:
		case HPI_FORMAT_PCM16_BIGENDIAN:
		case HPI_FORMAT_PCM32_SIGNED:
		case HPI_FORMAT_PCM32_FLOAT:
		case HPI_FORMAT_PCM24_SIGNED:
			break;
		default:
			phr->wError = HPI_ERROR_INVALID_FORMAT;
			return;
		}
	}
	HPI_MessageEx(&hm, &hr, NULL);

	phr->wError = hr.wError;
}

/*************************************************************************************\
* HPI_PreMessageSsx2                                                                  *
*	primary entry point prior to message handling, returns non-zero if no further *
*       processing is neccessary in HPI_MessageEx()                                   *
\*************************************************************************************/
HPI_BOOL HPI_PreMessageSsx2(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	HPI_BOOL handled = 0;

	if ( phm->wType != HPI_TYPE_REQUEST )
		return handled;

	if ( phm->wObject != HPI_OBJ_SUBSYSTEM &&
		( phm->wAdapterIndex >= HPI_MAX_ADAPTERS ||
		  !aADAPTER_SSX2[phm->wAdapterIndex].wPrepared ) ) {
			// Only message allowed when adapter is unprepared is the call that prepares it
			if (phm->wObject != HPI_OBJ_ADAPTER ||
				phm->wFunction != HPI_ADAPTER_SET_PROPERTY ||
				phm->u.ax.property_set.wProperty != HPI_ADAPTER_PROPERTY_ENABLE_SSX2) {
					return handled;
			}
	}

	switch (phm->wObject) {
	case HPI_OBJ_SUBSYSTEM:
		handled = SubSysPreMessage(phm, phr, hOwner);
		break;

	case HPI_OBJ_ADAPTER:
		handled = AdapterPreMessage(phm, phr, hOwner);
		break;

	case HPI_OBJ_MIXER:
		handled = MixerMessage(phm, phr);
		break;

	case HPI_OBJ_CONTROL:
		handled = ControlMessage(phm, phr);
		break;

	case HPI_OBJ_OSTREAM:
		handled = OStreamMessage(phm, phr, hOwner);
		break;

	case HPI_OBJ_ISTREAM:
		handled = IStreamMessage(phm, phr, hOwner);
		break;
	}
	return handled;
}

/*************************************************************************************\
* HPI_PostMessageSsx2                                                                 *
*		primary entry point after message handling in HPI_MessageEx()                 *
*       can override or modify result of operation                                    *
\*************************************************************************************/
void HPI_PostMessageSsx2(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	if (phm->wType != HPI_TYPE_REQUEST)
		return;

	if (phm->wAdapterIndex >= HPI_MAX_ADAPTERS || !aADAPTER_SSX2[phm->wAdapterIndex].wPrepared) {
		return;
	}

	switch (phm->wObject) {
	case HPI_OBJ_SUBSYSTEM:
		SubSysPostMessage(phm, phr, hOwner);
		break;

	case HPI_OBJ_ADAPTER:
		AdapterPostMessage(phm, phr, hOwner);
		break;
	}
}

/*************************************************************************************
* HPI_MessageSsx2
* Entry point for this module
*************************************************************************************/
void HPI_MessageSsx2(HPI_MESSAGE *phm, HPI_RESPONSE *phr, void *hOwner)
{
	HPI_BOOL handled = 0;
	union hpi_message_buffer_v1	hmb;

	if (phm->wType == HPI_TYPE_SSX2BYPASS_MESSAGE) {
		// OK, this message is from an app with ssx2 disabled so, send it on it's way
		memcpy(&hmb, phm, min(phm->wSize,sizeof(union hpi_message_buffer_v1)));
		hmb.m0.wType = HPI_TYPE_REQUEST;
		HPI_MessageEx(&hmb.m0, phr, hOwner);
	} else {
		handled = HPI_PreMessageSsx2(phm, phr, hOwner);

		if (!handled)
			HPI_MessageEx(phm, phr, hOwner);

		HPI_PostMessageSsx2(phm, phr, hOwner);
	}
}
