unit ADCExports;

interface

uses
	Winapi.Windows,
	System.SysUtils,
	Core;

function ADC_Reset(uClientRef: TClientRef): TWinErrCode; cdecl;

function ADC_SetRate(uClientRef: TClientRef; Rate: TDouble): TWinErrCode; cdecl;
function ADC_GetRate(uClientRef: TClientRef; out Rate: TDouble): TWinErrCode; cdecl;
function ADC_SetRange(uClientRef: TClientRef; Ch: TChannelIndex {= ciAll}; RangeCode: TRangeCode): TWinErrCode; cdecl;
function ADC_SetDifferential(uClientRef: TClientRef; Ch: TChannelIndex {= ciAll}; bDifferential: ByteBool): TWinErrCode; cdecl;
function ADC_SetChScan(uClientRef: TClientRef; FirstCh, LastCh: TChannelIndex): TWinErrCode; cdecl;
function ADC_SetOversample(uClientRef: TClientRef; Oversample: TUInt32): TWinErrCode; cdecl;
function ADC_SetCalMode(uClientRef: TClientRef; CalMode: TCalMode = cmRealData): TWinErrCode; cdecl;
function ADC_SetRangeCal(uClientRef: TClientRef; RangeCode: TRangeCode; ScaleFactor: TDouble = 1; OffsetCounts: TDouble = 0): TWinErrCode; cdecl;

function ADC_ImmediateGetScanV(uClientRef: TClientRef; pData: TPDouble = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode; cdecl;
function ADC_ImmediateGetScanRaw(uClientRef: TClientRef; pDataRaw: TPUInt32): TWinErrCode; cdecl;
function ADC_ImmediateGetSampleV(uClientRef: TClientRef; Ch: TChannelIndex; pData: TPDouble = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode; cdecl;
function ADC_ImmediateGetSampleRaw(uClientRef: TClientRef; Ch: TChannelIndex; pDataRaw: TPUInt32): TWinErrCode; cdecl;

function ADC_AcquireBegin(uClientRef: TClientRef; TriggerMode: TUInt8): TWinErrCode; cdecl;
function ADC_AcquireEnd(uClientRef: TClientRef): TWinErrCode; cdecl;
function ADC_AcquireGetDataV(uClientRef: TClientRef; pDataSamples: TPUInt32; pData: TPDouble = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode; cdecl;
function ADC_AcquireGetDataRaw(uClientRef: TClientRef; pDataSamples: TPUInt32; pDataRaw: TPUInt32): TWinErrCode; cdecl;
//headers overload ADC_AcquireGetDataV and ADC_AcquireGetDataRaw as ADC_AcquireGetData
function ADC_AcquireGetDataPtr(uClientRef: TClientRef): TPUInt8; cdecl;





implementation

uses
	Math,
	CoreDataMessages, ENetDataItems;

type
	TChData = record
		FirstStatus: TUInt32;
		Tot: TUInt32;
		Wt: TUInt32;
	end;
	TChDatas = array of TChData;

const
	MaxGetSamples = $1000000;
	SampleInvalid = $80000000;
	ChannelShift = 20;

function ValidateADRange(var Range: TRangeCode): TWinErrCode;
begin
	Result := ERROR_SUCCESS;
	case Range of
		rcUnipolar10  : Range := rcRangeCode0; rcRangeCode0: ;
		rcBipolar10   : Range := rcRangeCode1; rcRangeCode1: ;
		rcunipolar5   : Range := rcRangeCode2; rcRangeCode2: ;
		rcBipolar5    : Range := rcRangeCode3; rcRangeCode3: ;
		rcUnipolar2   : Range := rcRangeCode4; rcRangeCode4: ;
		rcBipolar2    : Range := rcRangeCode5; rcRangeCode5: ;
		rcUnipolar1   : Range := rcRangeCode6; rcRangeCode6: ;
		rcBipolar1    : Range := rcRangeCode7; rcRangeCode7: ;
		rcNoChange: Result := ERROR_HANDLE_EOF;
		else Result := ERROR_INVALID_PARAMETER;
	end;
end;

function VFromRow(Row: TUInt32): Double;
var
	Counts: TUInt16;
	Control: TUInt16;
	RangeCode: TRangeCode;

begin
	Counts := Row and $0000FFFF;
	Control := Row shr 16;
	if (Control and $8000) <> 0 then begin
		Result := NAN;
		Exit;
	end;

	RangeCode := (Control shr 11) and rcRangeCode7;
	case RangeCode of
		//This conversion duplicates the range table from ValidateADRange.
		rcRangeCode0: Result := 10/$10000 * TUInt16(Counts);
		rcRangeCode1: Result := 10/$8000  * TSInt16(Counts xor $8000);
		rcRangeCode2: Result :=  5/$10000 * TUInt16(Counts);
		rcRangeCode3: Result :=  5/$8000  * TSInt16(Counts xor $8000);
		rcRangeCode4: Result :=  2/$10000 * TUInt16(Counts);
		rcRangeCode5: Result :=  2/$8000  * TSInt16(Counts xor $8000);
		rcRangeCode6: Result :=  1/$10000 * TUInt16(Counts);
		rcRangeCode7: Result :=  1/$8000  * TSInt16(Counts xor $8000);
		else Result := NAN;
	end;
end;

procedure ParseADRow(Row: TUInt32; pStatus: TPUInt16; pTimestamp: TPUInt16; pDataCounts: TPUInt16; pDataV: TPDouble);
begin
	if pStatus <> nil then pStatus^ := Row shr 16;

	if pTimestamp <> nil then pTimestamp^ := 0;

	if pDataCounts <> nil then pDataCounts^ := Row;
	if pDataV <> nil then pDataV^ := VFromRow(Row);
end;

procedure ParseChData(const ChData: TChData; pStatus: TPUInt16; pTimestamp: TPUInt16; pDataV: TPDouble);
var
	FCt, V: TDouble;
	RangeCode: TRangeCode;

begin
	if pStatus <> nil then begin
		if ChData.Wt = 0 then
			pStatus^ := SampleInvalid shr 16
		else
			pStatus^ := ChData.FirstStatus
		;
	end;

	if pTimestamp <> nil then pTimestamp^ := 0;

	if pDataV <> nil then begin
		if ChData.Wt = 0 then
			pDataV^ := NAN
		else begin
			FCt := ChData.Tot / ChData.Wt;
			RangeCode := (ChData.FirstStatus shr 11) and rcRangeCode7;
			case RangeCode of
				//This conversion duplicates the range table from ValidateADRange.
				rcRangeCode0: V := 10/$10000 * FCt;
				rcRangeCode1: V := 10/$8000  * (FCt - $8000);
				rcRangeCode2: V :=  5/$10000 * FCt;
				rcRangeCode3: V :=  5/$8000  * (FCt - $8000);
				rcRangeCode4: V :=  2/$10000 * FCt;
				rcRangeCode5: V :=  2/$8000  * (FCt - $8000);
				rcRangeCode6: V :=  1/$10000 * FCt;
				rcRangeCode7: V :=  1/$8000  * (FCt - $8000);
				else V := NAN;
			end;
			pDataV^ := V;
		end;
	end;
end;

function RawFromChData(const ChData: TChData): TUInt32;
begin
	with ChData do
		Result := Round(Tot / Wt) or (FirstStatus shl 16)
	;
end;



function ADC_Reset(uClientRef: TClientRef): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		Msg := TMessageItem.Create;
		try
			Msg.MID := MIDMixed;
			with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
				RegOff := $00;
				RegData8 := 2;
			end;
			Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
		finally
			Msg.Free;
		end;

		if Result = ERROR_SUCCESS then begin
			ThisClient.bADCStreaming := False;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;



function ADC_SetRate(uClientRef: TClientRef; Rate: TDouble): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Divisor: LongWord;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if Rate <= 0 then begin
			Divisor := 0;
		end
		else begin
			if Rate >= ThisClient.BaseClock then begin
				Divisor := 1;
			end
			else begin
				Divisor := Round(ThisClient.BaseClock / Rate);
			end;
		end;

		Msg := TMessageItem.Create;
		Msg.MID := MIDMixed;
		with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
			RegOff := $18;
			RegData32 := Divisor;
		end;

		Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);

		Msg.Free;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_GetRate(uClientRef: TClientRef; out Rate: TDouble): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Divisor: LongWord;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		try
			Rate := NAN;
		except
			Result := ERROR_NOACCESS;
			Exit;
		end;

		Msg := TMessageItem.Create;
		try
			Msg.MID := MIDMixed;
			with TReadRegDataItem(Msg.AddNewDataItem(TReadRegDataItem)) do begin
				RegOff := $18;
			end;

			Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
			if Result <> ERROR_SUCCESS then Exit;

			with TReadRegReplyDataItem(Msg.DataItem[0]) do begin
				Divisor := RegData;
				if RegDataSize = rds8 then Divisor := Divisor and $FF;
			end;
		finally
			Msg.Free;
		end;

		if Divisor = 0 then begin
			Rate := 0;
		end
		else begin
			Rate := Round(ThisClient.BaseClock / Divisor);
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetRange(uClientRef: TClientRef; Ch: TChannelIndex {= ciAll}; RangeCode: TRangeCode): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		Result := ValidateADRange(RangeCode);
		if Result <> ERROR_SUCCESS then Exit;

		//RangeCode := RangeCode or $8; //Set SE, for v1.6 FPGA.

		Msg := TMessageItem.Create;
		Msg.MID := MIDMixed;
		try
			if Ch = ciAll then begin
				for Ch := $0 to $F do
				with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
					RegOff := $1 + Ch;
					RegData8 := RangeCode;
				end;
			end
			else begin
				if Ch > $F then begin
					Result := ERROR_INVALID_INDEX;
					Exit;
				end;

				with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
					RegOff := $1 + Ch;
					RegData8 := RangeCode;
				end;
			end;

			Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
		finally
			Msg.Free;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetDifferential(uClientRef: TClientRef; Ch: TChannelIndex {= ciAll}; bDifferential: ByteBool): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;
	SEMask: Byte;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if Ch = ciAll then begin
			Msg := TMessageItem.Create;
			try
				Msg.MID := MIDMixed;
				with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
					RegOff := $17;
					if bDifferential then
						RegData8 := $00
					else
						RegData8 := $FF
					;
				end;
				Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
			finally
				Msg.Free;
			end;
		end
		else begin
			if Ch > $7 then begin
				Result := ERROR_INVALID_INDEX;
				Exit;
			end;

			Msg := TMessageItem.Create;
			try
				Msg.MID := MIDMixed;
				TReadRegDataItem(Msg.AddNewDataItem(TReadRegDataItem)).RegOff := $17;
				Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
				if Result <> ERROR_SUCCESS then Exit;

				SEMask := TReadRegReplyDataItem(Msg.DataItem[0]).RegData;
				Msg.ClearDataItems;
				if bDifferential then
					SEMask := SEMask and not (1 shl Ch)
				else
					SEMask := SEMask or (1 shl Ch)
				;

				Msg.MID := MIDMixed;
				with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
					RegOff := $17;
					RegData8 := SEMask;
				end;
				Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
			finally
				Msg.Free;
			end;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetChScan(uClientRef: TClientRef; FirstCh, LastCh: TChannelIndex): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		Msg := TMessageItem.Create;
		Msg.MID := MIDMixed;
		with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
			RegOff := $13;
			RegData8 := FirstCh;
		end;
		with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
			RegOff := $14;
			RegData8 := LastCh;
		end;

		Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);

		Msg.Free;

		if Result = ERROR_SUCCESS then begin
			ThisClient.FirstCh := FirstCh;
			ThisClient.LastCh := LastCh;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetOversample(uClientRef: TClientRef; Oversample: TUInt32): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if Oversample > $FF then begin
			Result := ERROR_BAD_LENGTH;
			Exit;
		end;

		Msg := TMessageItem.Create;
		Msg.MID := MIDMixed;
		with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
			RegOff := $15;
			RegData8 := Oversample;
		end;

		Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);

		Msg.Free;

		if Result = ERROR_SUCCESS then ThisClient.OversampleByte := Oversample;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetCalMode(uClientRef: TClientRef; CalMode: TCalMode = cmRealData): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		case CalMode of
			cmRealData, cmUnipolarGround, cmUnipolarReference, cmBipolarGround, cmBipolarReference: ;
			else begin
				Result := ERROR_INVALID_DATA;
				Exit;
			end;
		end;

		Msg := TMessageItem.Create;
		Msg.MID := MIDMixed;
		with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
			RegOff := $11;
			RegData8 := CalMode;
		end;

		Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);

		Msg.Free;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetRangeCal(uClientRef: TClientRef; RangeCode: TRangeCode; ScaleFactor: TDouble = 1; OffsetCounts: TDouble = 0): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;
	Scale32F: Single;
	Scale32I: TUInt32 absolute Scale32F;
	Offset32F: Single;
	Offset32I: TUInt32 absolute Offset32F;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		Result := ValidateADRange(RangeCode);
		if Result <> ERROR_SUCCESS then Exit;

		//Swizzle from A/D range codes to cal constant range codes.
		RangeCode := ((RangeCode and $6) shr 1) or ((RangeCode and 1) shl 2);

		try
			Scale32F := ScaleFactor;
			Offset32F := OffsetCounts;
		except
			Result := ERROR_INVALID_DATA;
			Exit;
		end;
		if (Abs(Offset32F) >= $10000) or (Scale32F < 0) or (Scale32F >= $10000) then begin
			Result := ERROR_INVALID_DATA;
			Exit;
		end;

		Msg := TMessageItem.Create;
		Msg.MID := MIDMixed;
		try
			with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
				RegOff := $C0 + 8 * RangeCode;
				RegData32 := Scale32I;
			end;
			with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
				RegOff := $C4 + 8 * RangeCode;
				RegData32 := Offset32I;
			end;

			Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
		finally
			Msg.Free;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;



function ADC_AcquireBegin(uClientRef: TClientRef; TriggerMode: TUInt8): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if ThisClient.bADCStreaming then begin
			Result := ERROR_ALREADY_INITIALIZED;
			Exit;
		end;

		Msg := TMessageItem.Create;
		try
			Msg.MID := MIDMixed;
			with TADCStreamStartDataItem(Msg.AddNewDataItem(TADCStreamStartDataItem)) do begin
				ConnectionID := ThisClient.ContClientSock.HelloSample and $7FFFFFFF;
			end;
			with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
				RegOff := $12;
				RegData8 := TriggerMode;
			end;
			Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
		finally
			Msg.Free;
		end;

		if Result = ERROR_SUCCESS then ThisClient.bADCStreaming := True;

	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_AcquireEnd(uClientRef: TClientRef): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	Msg: TMessageItem;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if not ThisClient.bADCStreaming then begin
			Result := ERROR_NOT_READY;
			Exit;
		end;


		Msg := TMessageItem.Create;
		try
			Msg.MID := MIDMixed;
			Msg.AddNewDataItem(TADCStreamStopDataItem);
			Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
		finally
			Msg.Free;
		end;

		if Result = ERROR_SUCCESS then ThisClient.bADCStreaming := False;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;



function ADC_AcquireGetDataV(uClientRef: TClientRef; pDataSamples: TPUInt32; pData: TPDouble = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	DataSamples: TUInt32;
	RawData: array of TUInt32;
	iData: Integer;
	pThisData: TPDouble;
	pThisStatus, pThisTimestamp: TPUInt16;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if not ThisClient.bADCStreaming then begin
			SetLastError(ERROR_NOT_READY);
			Exit;
		end;

		try
			DataSamples := Min(MaxGetSamples, pDataSamples^);
			pDataSamples^ := 0;
		except
			Result := ERROR_NOACCESS;
			Exit;
		end;
		try
			if pData <> nil then FillChar(pData^, SizeOf(pData^) * DataSamples, 0);
			if pStatus <> nil then FillChar(pStatus^, SizeOf(pStatus^) * DataSamples, 0);
			if pTimestamp <> nil then FillChar(pTimestamp^, SizeOf(pTimestamp^) * DataSamples, 0);
		except
			Result := ERROR_NOACCESS;
			Exit;
		end;

		SetLength(RawData, DataSamples);
		Result := ADC_AcquireGetDataRaw(uClientRef, @DataSamples, @RawData[0]);
		if Result <> ERROR_SUCCESS then begin
			SetLength(RawData, 0);
			Exit;
		end;

		pThisData := pData;
		pThisStatus := pStatus;
		pThisTimestamp := pTimestamp;
		for iData := 0 to DataSamples-1 do begin
			ParseADRow(RawData[iData], pThisStatus, pThisTimestamp, nil, pThisData);
			if pThisData <> nil then Inc(pThisData);
			if pThisStatus <> nil then Inc(pThisStatus);
			if pThisTimestamp <> nil then Inc(pThisTimestamp);
		end;
		SetLength(RawData, 0);
		pDataSamples^ := DataSamples;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_AcquireGetDataRaw(uClientRef: TClientRef; pDataSamples: TPUInt32; pDataRaw: TPUInt32): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	DataBuf: array of TPUInt32;
	DregsBuf: TPUInt32;
	DataBufCt, NewADDataBufCt: TUInt32;
	SamplesWanted: TUInt32;
	BytesLeft, SourceDataBytes: TSInt32;
	iADDataBuf: TSInt32;
	pThisDregsData, pThisSourceData: TPUInt32;
	pThisDataRaw: TPUInt8;
	DataBytes: TUInt32;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if not ThisClient.bADCStreaming then begin
			Result := ERROR_NOT_READY;
			Exit;
		end;

		try
			SamplesWanted := Min(MaxGetSamples, pDataSamples^);
			pDataSamples^ := 0;
		except
			Result := ERROR_NOACCESS;
			Exit;
		end;
		try
			FillChar(pDataRaw^, SizeOf(pDataRaw^) * SamplesWanted, 0);
		except
			Result := ERROR_NOACCESS;
			Exit;
		end;

		if SamplesWanted = 0 then Exit; //Successful NOP.

		DataBufCt := 0;
		BytesLeft := SamplesWanted * SizeOf(pDataRaw^);
		WaitForSingleObject(ThisClient.ContClientSock.hPacketMutex, INFINITE);
		try
			//Determine how many buffers to take.
			for iADDataBuf := 0 to High(ThisClient.ContClientSock.ADDataBuf) do begin
				BytesLeft := BytesLeft - TPSInt32(ThisClient.ContClientSock.ADDataBuf[iADDataBuf])^;
				Inc(DataBufCt);
				if BytesLeft <= 0 then Break;
			end;

			if BytesLeft >= 0 then begin
				//We're taking only whole buffers, probably because they wanted more data than we have.
				DregsBuf := nil;
			end
			else begin
				//We're only taking part of the last buffer, so prepare a new buffer with the dregs of the last buffer.
				DregsBuf := NewADDataBuf(-BytesLeft);
				pThisDregsData := DregsBuf;
				pThisSourceData := TPUInt32(ThisClient.ContClientSock.ADDataBuf[DataBufCt - 1]);
				SourceDataBytes := pThisSourceData^;
				Inc(pThisDregsData);
				Inc(pThisSourceData);
				Inc(TPUInt8(pThisSourceData), SourceDataBytes + BytesLeft);
				Move(pThisSourceData^, pThisDregsData^, -BytesLeft);
			end;

			//Take the buffers into our local array.
			SetLength(DataBuf, DataBufCt);
			NewADDataBufCt := TUInt32(Length(ThisClient.ContClientSock.ADDataBuf)) - DataBufCt + TUInt32(Ord(DregsBuf <> nil));
			Move(ThisClient.ContClientSock.ADDataBuf[0], DataBuf[0], DataBufCt * SizeOf(DataBuf[0]));
			if NewADDataBufCt <= 0 then begin
				SetLength(ThisClient.ContClientSock.ADDataBuf, 0);
			end
			else begin
				Move(ThisClient.ContClientSock.ADDataBuf[DataBufCt], ThisClient.ContClientSock.ADDataBuf[Ord(DregsBuf <> nil)], NewADDataBufCt * SizeOf(DataBuf[0]));
				SetLength(ThisClient.ContClientSock.ADDataBuf, NewADDataBufCt);
				if DregsBuf <> nil then ThisClient.ContClientSock.ADDataBuf[0] := TPUInt8(DregsBuf);
			end;
		finally
			ReleaseMutex(ThisClient.ContClientSock.hPacketMutex);
		end;

		//Lay out the local array of buffers into the caller's contiguous buffer, and dispose of the taken buffers.
		pThisDataRaw := TPUInt8(pDataRaw);
		BytesLeft := SamplesWanted * SizeOf(pDataRaw^);
		DataBytes := 0;
		for iADDataBuf := 0 to DataBufCt-1 do begin
			pThisSourceData := DataBuf[iADDataBuf];
			SourceDataBytes := pThisSourceData^;
			Inc(pThisSourceData);
			if BytesLeft > SourceDataBytes then begin
				Move(pThisSourceData^, pThisDataRaw^, SourceDataBytes);
				Inc(pThisDataRaw, SourceDataBytes);
				Dec(BytesLeft, SourceDataBytes);
				Inc(DataBytes, SourceDataBytes);
			end
			else begin
				Move(pThisSourceData^, pThisDataRaw^, BytesLeft);
				Inc(pThisDataRaw, BytesLeft);
				BytesLeft := 0;
				Inc(DataBytes, BytesLeft);
			end;
			DisposeADDataBuf(DataBuf[iADDataBuf]);
		end;
		SetLength(DataBuf, 0);

		pDataSamples^ := DataBytes div SizeOf(pDataRaw^);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;



function ADC_AcquireGetDataPtr(uClientRef: TClientRef): TPUInt8; cdecl;
var
	ThisClient: TRefClient;
	H: Integer;
	Status: TWinErrCode;

begin
	Result := nil;

	Status := VerifyClientRef(uClientRef);
	if Status <> ERROR_SUCCESS then begin
		SetLastError(Status);
		Exit;
	end;

	ThisClient := Pointer(uClientRef);

	try
		if not ThisClient.bADCStreaming then begin
			SetLastError(ERROR_NOT_READY);
			Exit;
		end;

		WaitForSingleObject(ThisClient.ContClientSock.hPacketMutex, INFINITE);
		try
			H := High(ThisClient.ContClientSock.ADDataBuf);
			if H >= 0 then begin
				Result := ThisClient.ContClientSock.ADDataBuf[0];
				if H > 0 then begin
					ThisClient.ContClientSock.ADDataBuf[0] := ThisClient.ContClientSock.ADDataBuf[H]; //For garbage collection.
					Move(ThisClient.ContClientSock.ADDataBuf[1], ThisClient.ContClientSock.ADDataBuf[0], H * SizeOf(ThisClient.ContClientSock.ADDataBuf[0]));
				end;
				SetLength(ThisClient.ContClientSock.ADDataBuf, H);
			end;
		finally
			ReleaseMutex(ThisClient.ContClientSock.hPacketMutex);
		end;

		if Result = nil then
			SetLastError(ERROR_NO_DATA)
		else
			SetLastError(ERROR_SUCCESS)
		;
	except
		SetLastError(ERROR_INTERNAL_ERROR);
		Result := nil;
	end;
end;



function ImmediateGetScan_Inner(ThisClient: TRefClient; var ChData: TChDatas): TWinErrCode;
var
	Msg: TMessageItem;
	iCh: TChannelIndex;
	iRead: Integer;
	Channels, Oversample: Integer;
	ThisSample: TUInt32;

begin
	try
		if ThisClient.bADCStreaming then begin
			Result := ERROR_BUSY;
			Exit;
		end;

		Result := ThisClient.GetNeedsReg;
		if Result <> ERROR_SUCCESS then Exit;

		Msg := TMessageItem.Create;
		try
			Channels := 1 + ThisClient.LastCh - ThisClient.FirstCh;
			Oversample := 1 + ThisClient.OversampleByte;

			Msg.MID := MIDMixed;
			//TReadRegDataItem(Msg.AddNewDataItem(TReadRegDataItem)).RegOff := $24;
			with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
				RegOff := $12;
				RegData8 := $14;
			end;
			with TWriteRegDataItem(Msg.AddNewDataItem(TWriteRegDataItem)) do begin
				RegOff := $16;
				RegData8 := 0;
			end;
			//TReadRegDataItem(Msg.AddNewDataItem(TReadRegDataItem)).RegOff := $24;
			for iRead := 1 to 2+Channels*Oversample do
				TReadRegDataItem(Msg.AddNewDataItem(TReadRegDataItem)).RegOff := $1C
			;
			//TReadRegDataItem(Msg.AddNewDataItem(TReadRegDataItem)).RegOff := $24;

			Result := ThisClient.SendReceiveMsgAndExpectOK(Msg);
			if Result <> ERROR_SUCCESS then Exit;

			SetLength(ChData, ThisClient.Channels);
			ZeroMemory(@ChData[0], SizeOf(ChData[0]) * Length(ChData));
			//for iCh := 0 to High(ChData) do ChData[iCh].FirstStatus := SampleInvalid shr 16;
			for iRead := 0 to Msg.DataItemCount-1 do begin
				if not (Msg.DataItem[iRead] is TReadRegReplyDataItem) then Continue;
				if TReadRegReplyDataItem(Msg.DataItem[iRead]).RegOff <> $1C then begin
					Continue;
				end;

				ThisSample := TReadRegReplyDataItem(Msg.DataItem[iRead]).RegData;
				if (ThisSample and SampleInvalid) <> 0 then Continue;

				iCh := (ThisSample shr ChannelShift) and $7F;
				if iCh >= TChannelIndex(Length(ChData)) then Continue;

				if ChData[iCh].Wt = 0 then ChData[iCh].FirstStatus := ThisSample shr 16;
				Inc(ChData[iCh].Tot, ThisSample and $FFFF);
				Inc(ChData[iCh].Wt);
			end;
		finally
			Msg.Free;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_ImmediateGetScanV(uClientRef: TClientRef; pData: TPDouble = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	ChData: TChDatas;
	iCh: Integer;
	pThisData: TPDouble;
	pThisStatus: TPUInt16;
	pThisTimestamp: TPUInt16;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if pData <> nil then begin
			pThisData := pData;
			for iCh := 0 to ThisClient.Channels-1 do begin
				pThisData^ := NAN;
				Inc(pThisData);
			end;
		end;
		if pStatus <> nil then begin
			pThisStatus := pStatus;
			for iCh := 0 to ThisClient.Channels-1 do begin
				pThisStatus^ := SampleInvalid shr 16;
				Inc(pThisStatus);
			end;
		end;
		if pTimestamp <> nil then ZeroMemory(pTimestamp, SizeOf(pTimestamp^) * ThisClient.Channels);
	except
		Result := ERROR_NOACCESS;
		Exit;
	end;

	try
		Result := ImmediateGetScan_Inner(ThisClient, ChData);
		if Result <> ERROR_SUCCESS then Exit;

		pThisData := pData;
		pThisStatus := pStatus;
		pThisTimestamp := pTimestamp;
		for iCh := 0 to High(ChData) do begin
			ParseChData(ChData[iCh], pThisStatus, pThisTimestamp, pThisData);
			if pThisData <> nil then Inc(pThisData);
			if pThisStatus <> nil then Inc(pThisStatus);
			if pThisTimestamp <> nil then Inc(pThisTimestamp);
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_ImmediateGetScanRaw(uClientRef: TClientRef; pDataRaw: TPUInt32): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	iCh: TChannelIndex;
	pThisDataRaw: TPUInt32;
	ChData: TChDatas;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		pThisDataRaw := pDataRaw;
		for iCh := 0 to ThisClient.Channels-1 do begin
			pThisDataRaw^ := SampleInvalid;
			Inc(pThisDataRaw);
		end;
	except
		Result := ERROR_NOACCESS;
		Exit;
	end;

	try
		Result := ImmediateGetScan_Inner(ThisClient, ChData);
		if Result <> ERROR_SUCCESS then Exit;

		pThisDataRaw := pDataRaw;
		for iCh := 0 to High(ChData) do begin
			if ChData[iCh].Wt <> 0 then
				pThisDataRaw^ := RawFromChData(ChData[iCh])
			;
			Inc(pThisDataRaw);
		end;
		SetLength(ChData, 0);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_ImmediateGetSampleV(uClientRef: TClientRef; Ch: TChannelIndex; pData: TPDouble = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	ChData: TChDatas;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		if pData <> nil then pData^ := NAN;
		if pStatus <> nil then pStatus^ := SampleInvalid shr 16;
	except
		Result := ERROR_NOACCESS;
		Exit;
	end;

	try
		if Ch > ThisClient.Channels then begin
			Result := ERROR_INVALID_INDEX;
			Exit;
		end;

		Result := ImmediateGetScan_Inner(ThisClient, ChData);
		if Result <> ERROR_SUCCESS then Exit;

		ParseChData(ChData[Ch], pStatus, pTimestamp, pData);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_ImmediateGetSampleRaw(uClientRef: TClientRef; Ch: TChannelIndex; pDataRaw: TPUInt32): TWinErrCode; cdecl;
var
	ThisClient: TRefClient;
	ChData: TChDatas;

begin
	Result := VerifyClientRef(uClientRef);
	if Result <> ERROR_SUCCESS then Exit;

	ThisClient := Pointer(uClientRef);

	try
		pDataRaw^ := SampleInvalid;
	except
		Result := ERROR_NOACCESS;
		Exit;
	end;

	try
		if Ch > ThisClient.Channels then begin
			Result := ERROR_INVALID_INDEX;
			Exit;
		end;

		Result := ImmediateGetScan_Inner(ThisClient, ChData);
		if Result <> ERROR_SUCCESS then Exit;

		pDataRaw^ := RawFromChData(ChData[Ch]);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

end.