xref: /haiku/src/kits/game/PushGameSound.cpp (revision 4703fb5d8562169f672b3efe5203f1eb4d9ceb4a)
1*4703fb5dSbeveloper //------------------------------------------------------------------------------
2*4703fb5dSbeveloper //	Copyright (c) 2001-2002, OpenBeOS
3*4703fb5dSbeveloper //
4*4703fb5dSbeveloper //	Permission is hereby granted, free of charge, to any person obtaining a
5*4703fb5dSbeveloper //	copy of this software and associated documentation files (the "Software"),
6*4703fb5dSbeveloper //	to deal in the Software without restriction, including without limitation
7*4703fb5dSbeveloper //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8*4703fb5dSbeveloper //	and/or sell copies of the Software, and to permit persons to whom the
9*4703fb5dSbeveloper //	Software is furnished to do so, subject to the following conditions:
10*4703fb5dSbeveloper //
11*4703fb5dSbeveloper //	The above copyright notice and this permission notice shall be included in
12*4703fb5dSbeveloper //	all copies or substantial portions of the Software.
13*4703fb5dSbeveloper //
14*4703fb5dSbeveloper //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15*4703fb5dSbeveloper //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16*4703fb5dSbeveloper //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17*4703fb5dSbeveloper //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18*4703fb5dSbeveloper //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19*4703fb5dSbeveloper //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20*4703fb5dSbeveloper //	DEALINGS IN THE SOFTWARE.
21*4703fb5dSbeveloper //
22*4703fb5dSbeveloper //	File Name:		GameSound.cpp
23*4703fb5dSbeveloper //	Author:			Christopher ML Zumwalt May (zummy@users.sf.net)
24*4703fb5dSbeveloper //	Description:	BPushGameSound class
25*4703fb5dSbeveloper //------------------------------------------------------------------------------
260b4a36abSbeveloper 
27*4703fb5dSbeveloper // Standard Includes -----------------------------------------------------------
28*4703fb5dSbeveloper 
29*4703fb5dSbeveloper // System Includes -------------------------------------------------------------
30*4703fb5dSbeveloper #include <List.h>
31*4703fb5dSbeveloper 
32*4703fb5dSbeveloper // Project Includes ------------------------------------------------------------
33*4703fb5dSbeveloper #include <GSUtility.h>
34*4703fb5dSbeveloper 
35*4703fb5dSbeveloper // Local Includes --------------------------------------------------------------
360b4a36abSbeveloper #include <PushGameSound.h>
370b4a36abSbeveloper 
38*4703fb5dSbeveloper // Local Defines ---------------------------------------------------------------
390b4a36abSbeveloper 
40*4703fb5dSbeveloper // BPushGameSound --------------------------------------------------------------
410b4a36abSbeveloper BPushGameSound::BPushGameSound(size_t inBufferFrameCount,
420b4a36abSbeveloper 							   const gs_audio_format *format,
430b4a36abSbeveloper 							   size_t inBufferCount,
440b4a36abSbeveloper 							   BGameSoundDevice *device)
45*4703fb5dSbeveloper  	:	BStreamingGameSound(inBufferFrameCount, format, inBufferCount, device)
460b4a36abSbeveloper {
47*4703fb5dSbeveloper 	if (InitCheck() == B_OK)
48*4703fb5dSbeveloper 	{
49*4703fb5dSbeveloper 		status_t error = SetParameters(inBufferFrameCount, format, inBufferCount);
50*4703fb5dSbeveloper 		if (error != B_OK)
51*4703fb5dSbeveloper 			fPageLocked = new BList;
52*4703fb5dSbeveloper 		else
53*4703fb5dSbeveloper 			SetInitError(error);
54*4703fb5dSbeveloper 	}
550b4a36abSbeveloper }
560b4a36abSbeveloper 
570b4a36abSbeveloper 
58*4703fb5dSbeveloper BPushGameSound::BPushGameSound(BGameSoundDevice * device)
59*4703fb5dSbeveloper 		:	BStreamingGameSound(device),
60*4703fb5dSbeveloper 			fLockPos(0),
61*4703fb5dSbeveloper 			fPlayPos(0),
62*4703fb5dSbeveloper 			fBuffer(NULL),
63*4703fb5dSbeveloper 			fPageSize(0),
64*4703fb5dSbeveloper 			fPageCount(0),
65*4703fb5dSbeveloper 			fBufferSize(0)
66*4703fb5dSbeveloper {
67*4703fb5dSbeveloper 	fPageLocked = new BList;
68*4703fb5dSbeveloper }
69*4703fb5dSbeveloper 
700b4a36abSbeveloper BPushGameSound::~BPushGameSound()
710b4a36abSbeveloper {
72*4703fb5dSbeveloper 	delete [] fBuffer;
73*4703fb5dSbeveloper 	delete fPageLocked;
740b4a36abSbeveloper }
750b4a36abSbeveloper 
760b4a36abSbeveloper 
770b4a36abSbeveloper BPushGameSound::lock_status
780b4a36abSbeveloper BPushGameSound::LockNextPage(void **out_pagePtr,
790b4a36abSbeveloper 							 size_t *out_pageSize)
800b4a36abSbeveloper {
81*4703fb5dSbeveloper 	// the user can not lock every page
82*4703fb5dSbeveloper 	if (fPageLocked->CountItems() > fPageCount - 3) return lock_failed;
83*4703fb5dSbeveloper 
84*4703fb5dSbeveloper 	// the user cann't lock a page being played
85*4703fb5dSbeveloper 	if (fLockPos < fPlayPos && fLockPos + fPageSize > fPlayPos) return lock_failed;
86*4703fb5dSbeveloper 
87*4703fb5dSbeveloper 	// lock the page
88*4703fb5dSbeveloper 	char * lockPage = &fBuffer[fLockPos];
89*4703fb5dSbeveloper 	fPageLocked->AddItem(lockPage);
90*4703fb5dSbeveloper 
91*4703fb5dSbeveloper 	// move the locker to the next page
92*4703fb5dSbeveloper 	fLockPos += fPageSize;
93*4703fb5dSbeveloper 	if (fLockPos > fBufferSize) fLockPos = 0;
94*4703fb5dSbeveloper 
95*4703fb5dSbeveloper 	*out_pagePtr = lockPage;
96*4703fb5dSbeveloper 	*out_pageSize = fPageSize;
97*4703fb5dSbeveloper 
98*4703fb5dSbeveloper 	return lock_ok;
990b4a36abSbeveloper }
1000b4a36abSbeveloper 
1010b4a36abSbeveloper 
1020b4a36abSbeveloper status_t
1030b4a36abSbeveloper BPushGameSound::UnlockPage(void *in_pagePtr)
1040b4a36abSbeveloper {
105*4703fb5dSbeveloper 	return (fPageLocked->RemoveItem(in_pagePtr)) ? B_OK : B_ERROR;
1060b4a36abSbeveloper }
1070b4a36abSbeveloper 
1080b4a36abSbeveloper 
1090b4a36abSbeveloper BPushGameSound::lock_status
1100b4a36abSbeveloper BPushGameSound::LockForCyclic(void **out_basePtr,
1110b4a36abSbeveloper 							  size_t *out_size)
1120b4a36abSbeveloper {
113*4703fb5dSbeveloper 	*out_basePtr = fBuffer;
114*4703fb5dSbeveloper 	*out_size = fBufferSize;
115*4703fb5dSbeveloper 	return lock_ok;
1160b4a36abSbeveloper }
1170b4a36abSbeveloper 
1180b4a36abSbeveloper 
1190b4a36abSbeveloper status_t
1200b4a36abSbeveloper BPushGameSound::UnlockCyclic()
1210b4a36abSbeveloper {
122*4703fb5dSbeveloper 	return B_OK;
1230b4a36abSbeveloper }
1240b4a36abSbeveloper 
1250b4a36abSbeveloper 
1260b4a36abSbeveloper size_t
1270b4a36abSbeveloper BPushGameSound::CurrentPosition()
1280b4a36abSbeveloper {
129*4703fb5dSbeveloper 	return fPlayPos;
1300b4a36abSbeveloper }
1310b4a36abSbeveloper 
1320b4a36abSbeveloper 
1330b4a36abSbeveloper BGameSound *
1340b4a36abSbeveloper BPushGameSound::Clone() const
1350b4a36abSbeveloper {
136*4703fb5dSbeveloper 	gs_audio_format format = Format();
137*4703fb5dSbeveloper 	size_t frameSize = get_sample_size(format.format) * format.channel_count;
138*4703fb5dSbeveloper 	size_t bufferFrameCount = fPageSize / frameSize;
139*4703fb5dSbeveloper 
140*4703fb5dSbeveloper 	return new BPushGameSound(bufferFrameCount, &format, fPageCount, Device());
1410b4a36abSbeveloper }
1420b4a36abSbeveloper 
1430b4a36abSbeveloper 
1440b4a36abSbeveloper status_t
1450b4a36abSbeveloper BPushGameSound::Perform(int32 selector,
1460b4a36abSbeveloper 						void *data)
1470b4a36abSbeveloper {
1480b4a36abSbeveloper 	return B_ERROR;
1490b4a36abSbeveloper }
1500b4a36abSbeveloper 
1510b4a36abSbeveloper 
1520b4a36abSbeveloper status_t
1530b4a36abSbeveloper BPushGameSound::SetParameters(size_t inBufferFrameCount,
1540b4a36abSbeveloper 							  const gs_audio_format *format,
1550b4a36abSbeveloper 							  size_t inBufferCount)
1560b4a36abSbeveloper {
157*4703fb5dSbeveloper 	status_t error = BStreamingGameSound::SetParameters(inBufferFrameCount, format, inBufferCount);
158*4703fb5dSbeveloper 	if (error != B_OK) return error;
159*4703fb5dSbeveloper 
160*4703fb5dSbeveloper 	size_t frameSize = get_sample_size(format->format) * format->channel_count;
161*4703fb5dSbeveloper 
162*4703fb5dSbeveloper 	fPageCount = inBufferCount;
163*4703fb5dSbeveloper 	fPageSize = frameSize * inBufferFrameCount;
164*4703fb5dSbeveloper 	fBufferSize = fPageSize * fPageCount;
165*4703fb5dSbeveloper 
166*4703fb5dSbeveloper 	fBuffer = new char[fBufferSize];
167*4703fb5dSbeveloper 
168*4703fb5dSbeveloper 	return B_OK;
1690b4a36abSbeveloper }
1700b4a36abSbeveloper 
1710b4a36abSbeveloper 
1720b4a36abSbeveloper status_t
1730b4a36abSbeveloper BPushGameSound::SetStreamHook(void (*hook)(void * inCookie, void * inBuffer, size_t inByteCount, BStreamingGameSound * me),
1740b4a36abSbeveloper 							  void * cookie)
1750b4a36abSbeveloper {
1760b4a36abSbeveloper 	return B_ERROR;
1770b4a36abSbeveloper }
1780b4a36abSbeveloper 
1790b4a36abSbeveloper 
1800b4a36abSbeveloper void
1810b4a36abSbeveloper BPushGameSound::FillBuffer(void *inBuffer,
1820b4a36abSbeveloper 						   size_t inByteCount)
1830b4a36abSbeveloper {
184*4703fb5dSbeveloper 	size_t bytes = inByteCount;
185*4703fb5dSbeveloper 
186*4703fb5dSbeveloper 	if (BytesReady(&bytes))
187*4703fb5dSbeveloper 	{
188*4703fb5dSbeveloper 		if (fPlayPos + bytes > fBufferSize)
189*4703fb5dSbeveloper 		{
190*4703fb5dSbeveloper 			size_t remainder = fPlayPos + bytes - fBufferSize;
191*4703fb5dSbeveloper 			char * buffer = (char*)inBuffer;
192*4703fb5dSbeveloper 
193*4703fb5dSbeveloper 			// fill the buffer with the samples left at the end of our buffer
194*4703fb5dSbeveloper 			memcpy(buffer, &fBuffer[fPlayPos], remainder);
195*4703fb5dSbeveloper 			fPlayPos = 0;
196*4703fb5dSbeveloper 
197*4703fb5dSbeveloper 			// fill the remainder of the buffer by looping to the start
198*4703fb5dSbeveloper 			// of the buffer if it isn't locked
199*4703fb5dSbeveloper 			bytes -= remainder;
200*4703fb5dSbeveloper 			if (BytesReady(&bytes))
201*4703fb5dSbeveloper 			{
202*4703fb5dSbeveloper 				memcpy(&buffer[remainder], fBuffer, bytes);
203*4703fb5dSbeveloper 				fPlayPos += bytes;
204*4703fb5dSbeveloper 			}
205*4703fb5dSbeveloper 		}
206*4703fb5dSbeveloper 		else
207*4703fb5dSbeveloper 		{
208*4703fb5dSbeveloper 			memcpy(inBuffer, &fBuffer[fPlayPos], bytes);
209*4703fb5dSbeveloper 			fPlayPos += bytes;
2100b4a36abSbeveloper 		}
2110b4a36abSbeveloper 
212*4703fb5dSbeveloper 		BStreamingGameSound::FillBuffer(inBuffer, inByteCount);
213*4703fb5dSbeveloper 	}
214*4703fb5dSbeveloper }
215*4703fb5dSbeveloper 
216*4703fb5dSbeveloper 
217*4703fb5dSbeveloper bool
218*4703fb5dSbeveloper BPushGameSound::BytesReady(size_t * bytes)
219*4703fb5dSbeveloper {
220*4703fb5dSbeveloper 	if (fPageLocked->CountItems() > 0)
221*4703fb5dSbeveloper 	{
222*4703fb5dSbeveloper 		size_t start = fPlayPos;
223*4703fb5dSbeveloper 		size_t ready = fPlayPos;
224*4703fb5dSbeveloper 		int32 page = int32(start / fPageSize);
225*4703fb5dSbeveloper 
226*4703fb5dSbeveloper 		// return if there is nothing to do
227*4703fb5dSbeveloper 		if (fPageLocked->HasItem(&fBuffer[page * fPageSize])) return false;
228*4703fb5dSbeveloper 
229*4703fb5dSbeveloper 		while (ready < *bytes)
230*4703fb5dSbeveloper 		{
231*4703fb5dSbeveloper 			ready += fPageSize;
232*4703fb5dSbeveloper 			page = int32(ready / fPageSize);
233*4703fb5dSbeveloper 
234*4703fb5dSbeveloper 			if (fPageLocked->HasItem(&fBuffer[page * fPageSize]))
235*4703fb5dSbeveloper 			{
236*4703fb5dSbeveloper 				// we have found a locked page
237*4703fb5dSbeveloper 				*bytes = ready - start - (ready - page * fPageSize);
238*4703fb5dSbeveloper 				return true;
239*4703fb5dSbeveloper 			}
240*4703fb5dSbeveloper 		}
241*4703fb5dSbeveloper 	}
242*4703fb5dSbeveloper 
243*4703fb5dSbeveloper 	// all of the bytes are ready
244*4703fb5dSbeveloper 	return true;
245*4703fb5dSbeveloper }
2460b4a36abSbeveloper 
2470b4a36abSbeveloper /* unimplemented for protection of the user:
2480b4a36abSbeveloper  *
2490b4a36abSbeveloper  * BPushGameSound::BPushGameSound()
2500b4a36abSbeveloper  * BPushGameSound::BPushGameSound(const BPushGameSound &)
2510b4a36abSbeveloper  * BPushGameSound &BPushGameSound::operator=(const BPushGameSound &)
2520b4a36abSbeveloper  */
2530b4a36abSbeveloper 
2540b4a36abSbeveloper 
2550b4a36abSbeveloper status_t
2560b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_0(int32 arg, ...)
2570b4a36abSbeveloper {
2580b4a36abSbeveloper 	return B_ERROR;
2590b4a36abSbeveloper }
2600b4a36abSbeveloper 
2610b4a36abSbeveloper 
2620b4a36abSbeveloper status_t
2630b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_1(int32 arg, ...)
2640b4a36abSbeveloper {
2650b4a36abSbeveloper 	return B_ERROR;
2660b4a36abSbeveloper }
2670b4a36abSbeveloper 
2680b4a36abSbeveloper 
2690b4a36abSbeveloper status_t
2700b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_2(int32 arg, ...)
2710b4a36abSbeveloper {
2720b4a36abSbeveloper 	return B_ERROR;
2730b4a36abSbeveloper }
2740b4a36abSbeveloper 
2750b4a36abSbeveloper 
2760b4a36abSbeveloper status_t
2770b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_3(int32 arg, ...)
2780b4a36abSbeveloper {
2790b4a36abSbeveloper 	return B_ERROR;
2800b4a36abSbeveloper }
2810b4a36abSbeveloper 
2820b4a36abSbeveloper 
2830b4a36abSbeveloper status_t
2840b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_4(int32 arg, ...)
2850b4a36abSbeveloper {
2860b4a36abSbeveloper 	return B_ERROR;
2870b4a36abSbeveloper }
2880b4a36abSbeveloper 
2890b4a36abSbeveloper 
2900b4a36abSbeveloper status_t
2910b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_5(int32 arg, ...)
2920b4a36abSbeveloper {
2930b4a36abSbeveloper 	return B_ERROR;
2940b4a36abSbeveloper }
2950b4a36abSbeveloper 
2960b4a36abSbeveloper 
2970b4a36abSbeveloper status_t
2980b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_6(int32 arg, ...)
2990b4a36abSbeveloper {
3000b4a36abSbeveloper 	return B_ERROR;
3010b4a36abSbeveloper }
3020b4a36abSbeveloper 
3030b4a36abSbeveloper 
3040b4a36abSbeveloper status_t
3050b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_7(int32 arg, ...)
3060b4a36abSbeveloper {
3070b4a36abSbeveloper 	return B_ERROR;
3080b4a36abSbeveloper }
3090b4a36abSbeveloper 
3100b4a36abSbeveloper 
3110b4a36abSbeveloper status_t
3120b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_8(int32 arg, ...)
3130b4a36abSbeveloper {
3140b4a36abSbeveloper 	return B_ERROR;
3150b4a36abSbeveloper }
3160b4a36abSbeveloper 
3170b4a36abSbeveloper 
3180b4a36abSbeveloper status_t
3190b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_9(int32 arg, ...)
3200b4a36abSbeveloper {
3210b4a36abSbeveloper 	return B_ERROR;
3220b4a36abSbeveloper }
3230b4a36abSbeveloper 
3240b4a36abSbeveloper 
3250b4a36abSbeveloper status_t
3260b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_10(int32 arg, ...)
3270b4a36abSbeveloper {
3280b4a36abSbeveloper 	return B_ERROR;
3290b4a36abSbeveloper }
3300b4a36abSbeveloper 
3310b4a36abSbeveloper 
3320b4a36abSbeveloper status_t
3330b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_11(int32 arg, ...)
3340b4a36abSbeveloper {
3350b4a36abSbeveloper 	return B_ERROR;
3360b4a36abSbeveloper }
3370b4a36abSbeveloper 
3380b4a36abSbeveloper 
3390b4a36abSbeveloper status_t
3400b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_12(int32 arg, ...)
3410b4a36abSbeveloper {
3420b4a36abSbeveloper 	return B_ERROR;
3430b4a36abSbeveloper }
3440b4a36abSbeveloper 
3450b4a36abSbeveloper 
3460b4a36abSbeveloper status_t
3470b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_13(int32 arg, ...)
3480b4a36abSbeveloper {
3490b4a36abSbeveloper 	return B_ERROR;
3500b4a36abSbeveloper }
3510b4a36abSbeveloper 
3520b4a36abSbeveloper 
3530b4a36abSbeveloper status_t
3540b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_14(int32 arg, ...)
3550b4a36abSbeveloper {
3560b4a36abSbeveloper 	return B_ERROR;
3570b4a36abSbeveloper }
3580b4a36abSbeveloper 
3590b4a36abSbeveloper 
3600b4a36abSbeveloper status_t
3610b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_15(int32 arg, ...)
3620b4a36abSbeveloper {
3630b4a36abSbeveloper 	return B_ERROR;
3640b4a36abSbeveloper }
3650b4a36abSbeveloper 
3660b4a36abSbeveloper 
3670b4a36abSbeveloper status_t
3680b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_16(int32 arg, ...)
3690b4a36abSbeveloper {
3700b4a36abSbeveloper 	return B_ERROR;
3710b4a36abSbeveloper }
3720b4a36abSbeveloper 
3730b4a36abSbeveloper 
3740b4a36abSbeveloper status_t
3750b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_17(int32 arg, ...)
3760b4a36abSbeveloper {
3770b4a36abSbeveloper 	return B_ERROR;
3780b4a36abSbeveloper }
3790b4a36abSbeveloper 
3800b4a36abSbeveloper 
3810b4a36abSbeveloper status_t
3820b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_18(int32 arg, ...)
3830b4a36abSbeveloper {
3840b4a36abSbeveloper 	return B_ERROR;
3850b4a36abSbeveloper }
3860b4a36abSbeveloper 
3870b4a36abSbeveloper 
3880b4a36abSbeveloper status_t
3890b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_19(int32 arg, ...)
3900b4a36abSbeveloper {
3910b4a36abSbeveloper 	return B_ERROR;
3920b4a36abSbeveloper }
3930b4a36abSbeveloper 
3940b4a36abSbeveloper 
3950b4a36abSbeveloper status_t
3960b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_20(int32 arg, ...)
3970b4a36abSbeveloper {
3980b4a36abSbeveloper 	return B_ERROR;
3990b4a36abSbeveloper }
4000b4a36abSbeveloper 
4010b4a36abSbeveloper 
4020b4a36abSbeveloper status_t
4030b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_21(int32 arg, ...)
4040b4a36abSbeveloper {
4050b4a36abSbeveloper 	return B_ERROR;
4060b4a36abSbeveloper }
4070b4a36abSbeveloper 
4080b4a36abSbeveloper 
4090b4a36abSbeveloper status_t
4100b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_22(int32 arg, ...)
4110b4a36abSbeveloper {
4120b4a36abSbeveloper 	return B_ERROR;
4130b4a36abSbeveloper }
4140b4a36abSbeveloper 
4150b4a36abSbeveloper 
4160b4a36abSbeveloper status_t
4170b4a36abSbeveloper BPushGameSound::_Reserved_BPushGameSound_23(int32 arg, ...)
4180b4a36abSbeveloper {
4190b4a36abSbeveloper 	return B_ERROR;
4200b4a36abSbeveloper }
4210b4a36abSbeveloper 
4220b4a36abSbeveloper 
423