mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-11 17:49:53 -10:00
- "SoundDeviceOAL" reg option for user specified sound device (used instead of "Soundcard" for OpenAL) - reset current context when we are leaving, may be leaving drivers in a bad state, and it hasn't hung up in quite a while - if sound card (which DS or OAL) is set to "no sound" then be sure to disable both sound and music fix various things that Valgrind complained about make sure we can report both AL and ALC errors fix for ds_get_free_channel(), it shouldn't return -1 on an AL error
4211 lines
109 KiB
C++
4211 lines
109 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* $Logfile: /Freespace2/code/Sound/ds.cpp $
|
|
* $Revision: 2.47 $
|
|
* $Date: 2006-06-27 04:58:58 $
|
|
* $Author: taylor $
|
|
*
|
|
* C file for interface to DirectSound
|
|
*
|
|
* $Log: not supported by cvs2svn $
|
|
* Revision 2.46 2006/06/01 07:33:00 taylor
|
|
* make sure to use software device rather than hardware since we don't currently handle source rotation/reuse properly between ds and audiostr code
|
|
*
|
|
* Revision 2.45 2006/06/01 04:46:18 taylor
|
|
* put back listener setup for each sound, doesn't make a difference under Linux but it may be hurting Windows for some reason
|
|
*
|
|
* Revision 2.44 2006/05/31 04:01:13 taylor
|
|
* bah! this thing is crashing. disable for now until I can figure out what's going on
|
|
*
|
|
* Revision 2.43 2006/05/31 03:27:19 Goober5000
|
|
* whoops, didn't mean to commit that
|
|
*
|
|
* Revision 2.41 2006/05/27 16:39:40 taylor
|
|
* remove non-Windows timeSetEvent() functions
|
|
* make PDWORD and LPDWORD int instead of long (64-bit issue) (thanks Spike)
|
|
* fix 64-bit ptr issue with OpenAL audio streaming code (thanks Spike)
|
|
* add start of device enumberation for OpenAL 1.1 (it just lists available devices now)
|
|
* make sure we open the default OpenAL 1.1 device under Windows
|
|
* comment out blank listener stuff, this may have been crashing under OS X
|
|
* add support for AL_BYTE_OFFSET in OpenAL 1.1 for buffer positioning under Windows
|
|
*
|
|
* Revision 2.40 2006/04/20 06:32:30 Goober5000
|
|
* proper capitalization according to Volition
|
|
*
|
|
* Revision 2.39 2006/04/14 18:39:38 taylor
|
|
* something that Valgrind complained about
|
|
*
|
|
* Revision 2.38 2006/03/15 17:30:47 taylor
|
|
* remove alut headers, since we don't use any alut functions anyway (didn't I already do this a couple of months ago??)
|
|
*
|
|
* Revision 2.37 2006/01/30 07:14:32 taylor
|
|
* make sure to init dest and dest_size for any calling functions that don't
|
|
*
|
|
* Revision 2.36 2005/12/06 03:17:48 taylor
|
|
* cleanup some debug log messages:
|
|
* note that a nprintf() with "Warning" or "General" is basically the same thing as mprintf()
|
|
* make sure that OpenAL init failures always get to the debug log
|
|
*
|
|
* Revision 2.35 2005/11/14 05:12:01 taylor
|
|
* now able to build with OpenAL 1.0 and 1.1 thanks to newer OpenAL CVS changes (patch from icculus.org FS2 mailing list)
|
|
*
|
|
* Revision 2.34 2005/10/17 02:09:29 wmcoolmon
|
|
* Fix redmenace's compile problem
|
|
*
|
|
* Revision 2.33 2005/10/17 00:02:46 wmcoolmon
|
|
* Change this back for MSVC.NET?
|
|
*
|
|
* Revision 2.32 2005/09/20 02:48:37 taylor
|
|
* fix a couple of things that Valgrind complained about
|
|
*
|
|
* Revision 2.31 2005/07/31 01:35:43 taylor
|
|
* we only care about fmt and data tags so once we have them bail out, fixes some really bad seek errors in crappy wav files
|
|
*
|
|
* Revision 2.30 2005/06/19 02:45:55 taylor
|
|
* OGG streaming fixes to get data reading right and avoid skipping
|
|
* properly handle seeking in OGG streams
|
|
* compiler warning fix in OpenAL builds
|
|
*
|
|
* Revision 2.29 2005/06/02 01:56:32 wmcoolmon
|
|
* Cast to quiet error
|
|
*
|
|
* Revision 2.28 2005/06/01 09:41:14 taylor
|
|
* bit of cleanup for audiostr-openal and fix a Windows-only enum error
|
|
* bunch of OGG related fixes for Linux and Windows (DirectSound and OpenAL), fixes audio related TBP 3.2 crashes
|
|
* gracefully handle OGG logical bitstream changes, shouldn't even load if there is more than 1
|
|
*
|
|
* Revision 2.27 2005/05/26 04:32:31 taylor
|
|
* the Loki extensions are Linux only so don't even bother checking under Windows, the
|
|
* new spec has a cross-platform way of getting the same thing so this isn't a big deal
|
|
*
|
|
* Revision 2.26 2005/05/12 17:47:57 taylor
|
|
* use vm_malloc(), vm_free(), vm_realloc(), vm_strdup() rather than system named macros
|
|
* fixes various problems and is past time to make the switch
|
|
* fix a few streaming errors in OpenAL code (Jens Granseuer)
|
|
* temporary change to help deal with missing music in OpenAL Windows builds
|
|
* don't assert when si->data is NULL unless we really need to check (OpenAL only)
|
|
*
|
|
* Revision 2.25 2005/04/28 05:12:26 wmcoolmon
|
|
* Cleared up some ambiguity that made MSVC 2003 go on strike
|
|
*
|
|
* Revision 2.24 2005/04/24 12:45:14 taylor
|
|
* possible dev/0 bug fix (Jens Granseuer)
|
|
*
|
|
* Revision 2.23 2005/04/18 03:30:05 taylor
|
|
* more accurate play position for OpenAL when AL_BYTE_LOKI is used (Jens Granseuer)
|
|
*
|
|
* Revision 2.22 2005/04/13 22:31:53 Goober5000
|
|
* fixed the compile errors (thanks Phreak)
|
|
* --Goober5000
|
|
*
|
|
* Revision 2.21 2005/04/05 11:48:22 taylor
|
|
* remove acm-unix.cpp, replaced by acm-openal.cpp since it's properly cross-platform now
|
|
* better error handling for OpenAL functions
|
|
* Windows can now build properly with OpenAL
|
|
* extra check to make sure we don't try and use too many hardware bases sources
|
|
* fix memory error from OpenAL extension list in certain instances
|
|
*
|
|
* Revision 2.20 2005/04/05 05:53:25 taylor
|
|
* s/vector/vec3d/g, better support for different compilers (Jens Granseuer)
|
|
*
|
|
* Revision 2.19 2005/04/01 07:33:08 taylor
|
|
* fix hanging on exit with OpenAL
|
|
* some better error handling on OpenAL init and make it more Windows friendly too
|
|
* basic 3d sound stuff for OpenAL, not working right yet
|
|
*
|
|
* Revision 2.18 2005/03/28 00:40:09 taylor
|
|
* fix to snd_time_remaining() to make sure we are getting the correct index into Sounds[]
|
|
*
|
|
* Revision 2.17 2005/03/27 06:16:46 taylor
|
|
* don't use a platform specific ifdef since the OpenAL code may work under Windows now too
|
|
*
|
|
* Revision 2.16 2005/03/24 23:27:25 taylor
|
|
* make sounds.tbl dynamic
|
|
* have snd_time_remaining() be less stupid
|
|
* some OpenAL error fixerage
|
|
* be able to turn off some typically useless debug messages
|
|
*
|
|
* Revision 2.15 2005/02/02 10:36:23 taylor
|
|
* merge with Linux/OSX tree - p0202
|
|
*
|
|
* Revision 2.14 2005/01/18 01:14:17 wmcoolmon
|
|
* OGG fixes, ship selection fixes
|
|
*
|
|
* Revision 2.13 2005/01/08 09:59:10 wmcoolmon
|
|
* Sound quality in FreeSpace 2 is now controlled by SoundSampleBits, and SoundSampleRate. Also, some sounds will use hardware rather than software buffers if available.
|
|
*
|
|
* Revision 2.12 2004/12/25 17:45:38 wmcoolmon
|
|
* Quick update to tempfix possible bug.
|
|
*
|
|
* Revision 2.11 2004/12/25 00:23:46 wmcoolmon
|
|
* Ogg support for WIN32
|
|
*
|
|
* Revision 2.10 2004/09/05 19:23:24 Goober5000
|
|
* fixed a few warnings
|
|
* --Goober5000
|
|
*
|
|
* Revision 2.9 2004/08/23 07:48:08 Goober5000
|
|
* fix0red some warnings
|
|
* --Goober5000
|
|
*
|
|
* Revision 2.8 2004/07/26 20:47:52 Kazan
|
|
* remove MCD complete
|
|
*
|
|
* Revision 2.7 2004/07/12 16:33:06 Kazan
|
|
* MCD - define _MCD_CHECK to use memory tracking
|
|
*
|
|
* Revision 2.6 2004/06/22 23:14:10 wmcoolmon
|
|
* Nonworking OGG support for sound (not music) added, disabled load-only-used-weapons code, modification to sound system registry code.
|
|
* OGG code has been commented out, so you don't need the SDK yet.
|
|
*
|
|
* Revision 2.5 2004/06/18 04:59:55 wmcoolmon
|
|
* Only used weapons paged in instead of all, fixed music box in FRED, sound quality settable with SoundSampleRate and SoundSampleBits registry values
|
|
*
|
|
* Revision 2.4 2003/11/16 09:42:37 Goober5000
|
|
* clarified and pruned debug spew messages
|
|
* --Goober5000
|
|
*
|
|
* Revision 2.3 2003/03/02 06:37:24 penguin
|
|
* Use multimedia headers in local dir, not system's (headers are not present in MinGW distribution)
|
|
* - penguin
|
|
*
|
|
* Revision 2.2 2003/01/19 22:45:35 Goober5000
|
|
* cleaned up build output a bit
|
|
* --Goober5000
|
|
*
|
|
* Revision 2.1 2002/08/01 01:41:10 penguin
|
|
* The big include file move
|
|
*
|
|
* Revision 2.0 2002/06/03 04:02:29 penguin
|
|
* Warpcore CVS sync
|
|
*
|
|
* Revision 1.1 2002/05/02 18:03:13 mharris
|
|
* Initial checkin - converted filenames and includes to lower case
|
|
*
|
|
*
|
|
* 18 10/25/99 5:56p Jefff
|
|
* increase num software channels to the number the users hardware can
|
|
* handle. not less than 16, tho.
|
|
*
|
|
* 17 9/08/99 3:22p Dave
|
|
* Updated builtin mission list.
|
|
*
|
|
* 16 8/27/99 6:38p Alanl
|
|
* crush the blasted repeating messages bug
|
|
*
|
|
* 15 8/23/99 11:16p Danw
|
|
* Allow stereo waves
|
|
*
|
|
* 14 8/22/99 11:06p Alanl
|
|
* fix small bug in ds_close_channel
|
|
*
|
|
* 13 8/19/99 11:25a Alanl
|
|
* change format of secondary buffer from 44100 to 22050
|
|
*
|
|
* 12 8/17/99 4:11p Danw
|
|
* AL: temp fix for solving A3D crash
|
|
*
|
|
* 11 8/06/99 2:20p Jasonh
|
|
* AL: free 3D portion of buffer first
|
|
*
|
|
* 10 8/04/99 9:48p Alanl
|
|
* fix bug with setting 3D properties on a 2D sound buffer
|
|
*
|
|
* 9 8/04/99 11:42a Danw
|
|
* tone down EAX reverb
|
|
*
|
|
* 8 8/01/99 2:06p Alanl
|
|
* increase the rolloff for A3D
|
|
*
|
|
* 7 7/20/99 5:28p Dave
|
|
* Fixed debug build error.
|
|
*
|
|
* 6 7/20/99 1:49p Dave
|
|
* Peter Drake build. Fixed some release build warnings.
|
|
*
|
|
* 5 7/14/99 11:32a Danw
|
|
* AL: add some debug code to catch nefarious A3D problem
|
|
*
|
|
* 4 5/23/99 8:11p Alanl
|
|
* Added support for EAX
|
|
*
|
|
* 3 10/08/98 4:29p Dave
|
|
* Removed reference to osdefs.h
|
|
*
|
|
* 2 10/07/98 10:54a Dave
|
|
* Initial checkin.
|
|
*
|
|
* 1 10/07/98 10:51a Dave
|
|
*
|
|
* 72 6/28/98 6:34p Lawrance
|
|
* add sanity check in while() loop for releasing channels
|
|
*
|
|
* 71 6/13/98 1:45p Sandeep
|
|
*
|
|
* 70 6/10/98 2:29p Lawrance
|
|
* don't use COM for initializing DirectSound... appears some machines
|
|
* don't like it
|
|
*
|
|
* 69 5/26/98 2:10a Lawrance
|
|
* make sure DirectSound pointer gets freed if Aureal resource manager
|
|
* fails
|
|
*
|
|
* 68 5/21/98 9:14p Lawrance
|
|
* remove obsolete registry setting
|
|
*
|
|
* 67 5/20/98 4:28p Allender
|
|
* upped sound buffers as per alan's request
|
|
*
|
|
* 66 5/15/98 3:36p John
|
|
* Fixed bug with new graphics window code and standalone server. Made
|
|
* hwndApp not be a global anymore.
|
|
*
|
|
* 65 5/06/98 3:37p Lawrance
|
|
* allow panned sounds geesh
|
|
*
|
|
* 64 5/05/98 4:49p Lawrance
|
|
* Put in code to authenticate A3D, improve A3D support
|
|
*
|
|
* 63 4/20/98 11:17p Lawrance
|
|
* fix bug with releasing channels
|
|
*
|
|
* 62 4/20/98 7:34p Lawrance
|
|
* take out obsolete directsound3d debug command
|
|
*
|
|
* 61 4/20/98 11:10a Lawrance
|
|
* put correct flags when creating sound buffer
|
|
*
|
|
* 60 4/20/98 12:03a Lawrance
|
|
* Allow prioritizing of CTRL3D buffers
|
|
*
|
|
* 59 4/19/98 9:31p Lawrance
|
|
* Use Aureal_enabled flag
|
|
*
|
|
* 58 4/19/98 9:39a Lawrance
|
|
* use DYNAMIC_LOOPERS for Aureal resource manager
|
|
*
|
|
* 57 4/19/98 4:13a Lawrance
|
|
* Improve how dsound is initialized
|
|
*
|
|
* 56 4/18/98 9:13p Lawrance
|
|
* Added Aureal support.
|
|
*
|
|
* 55 4/13/98 5:04p Lawrance
|
|
* Write functions to determine how many milliseconds are left in a sound
|
|
*
|
|
* 54 4/09/98 5:53p Lawrance
|
|
* Make DirectSound init more robust
|
|
*
|
|
* 53 4/01/98 9:21p John
|
|
* Made NDEBUG, optimized build with no warnings or errors.
|
|
*
|
|
* 52 3/31/98 5:19p John
|
|
* Removed demo/save/restore. Made NDEBUG defined compile. Removed a
|
|
* bunch of debug stuff out of player file. Made model code be able to
|
|
* unload models and malloc out only however many models are needed.
|
|
*
|
|
*
|
|
* 51 3/29/98 12:56a Lawrance
|
|
* preload the warp in and explosions sounds before a mission.
|
|
*
|
|
* 50 3/25/98 6:10p Lawrance
|
|
* Work on DirectSound3D
|
|
*
|
|
* 49 3/24/98 4:28p Lawrance
|
|
* Make DirectSound3D support more robust
|
|
*
|
|
* 48 3/24/98 11:49a Dave
|
|
* AL: Change way buffer gets locked.
|
|
*
|
|
* 47 3/24/98 11:27a Lawrance
|
|
* Use buffer_size for memcpy when locking buffer
|
|
*
|
|
* 46 3/23/98 10:32a Lawrance
|
|
* Add functions for extracting raw sound data
|
|
*
|
|
* 45 3/19/98 5:36p Lawrance
|
|
* Add some sound debug functions to see how many sounds are playing, and
|
|
* to start/stop random looping sounds.
|
|
*
|
|
* 44 3/07/98 3:35p Dave
|
|
* AL: check for ds being initialized in ds_create_buffer()
|
|
*
|
|
* 43 2/18/98 5:49p Lawrance
|
|
* Even if the ADPCM codec is unavailable, allow game to continue.
|
|
*
|
|
* 42 2/16/98 7:31p Lawrance
|
|
* get compression/decompression of voice working
|
|
*
|
|
* 41 2/15/98 11:10p Lawrance
|
|
* more work on real-time voice system
|
|
*
|
|
* 40 2/15/98 4:43p Lawrance
|
|
* work on real-time voice
|
|
*
|
|
* 39 2/06/98 7:30p John
|
|
* Added code to monitor the number of channels of sound actually playing.
|
|
*
|
|
* 38 2/06/98 8:56a Allender
|
|
* fixed calling convention problem with DLL handles
|
|
*
|
|
* 37 2/04/98 6:08p Lawrance
|
|
* Read function pointers from dsound.dll, further work on
|
|
* DirectSoundCapture.
|
|
*
|
|
* 36 2/03/98 11:53p Lawrance
|
|
* Adding support for DirectSoundCapture
|
|
*
|
|
* 35 1/31/98 5:48p Lawrance
|
|
* Start on real-time voice recording
|
|
*
|
|
* 34 1/10/98 1:14p John
|
|
* Added explanation to debug console commands
|
|
*
|
|
* 33 12/21/97 4:33p John
|
|
* Made debug console functions a class that registers itself
|
|
* automatically, so you don't need to add the function to
|
|
* debugfunctions.cpp.
|
|
*
|
|
* 32 12/08/97 12:24a Lawrance
|
|
* Allow duplicate sounds to be stopped if less than OR equal to new sound
|
|
* volume.
|
|
*
|
|
* 31 12/05/97 5:19p Lawrance
|
|
* re-do sound priorities to make more general and extensible
|
|
*
|
|
* 30 11/28/97 2:09p Lawrance
|
|
* Overhaul how ADPCM conversion works... use much less memory... safer
|
|
* too.
|
|
*
|
|
* 29 11/22/97 11:32p Lawrance
|
|
* decompress ADPCM data into 8 bit (not 16bit) for regular sounds (ie not
|
|
* music)
|
|
*
|
|
* 28 11/20/97 5:36p Dave
|
|
* Hooked in a bunch of main hall changes (including sound). Made it
|
|
* possible to reposition (rewind/ffwd)
|
|
* sound buffer pointers. Fixed animation direction change framerate
|
|
* problem.
|
|
*
|
|
* 27 10/13/97 7:41p Lawrance
|
|
* store duration of sound
|
|
*
|
|
* 26 10/11/97 6:39p Lawrance
|
|
* start playing primary buffer, to reduce latency on sounds starting
|
|
*
|
|
* 25 10/08/97 5:09p Lawrance
|
|
* limit player impact sounds so only one plays at a time
|
|
*
|
|
* 24 9/26/97 5:43p Lawrance
|
|
* fix a bug that was freeing memory early when playing compressed sound
|
|
* data
|
|
*
|
|
* 23 9/09/97 3:39p Sandeep
|
|
* warning level 4 bugs
|
|
*
|
|
* 22 8/16/97 4:05p Lawrance
|
|
* don't load sounds into hardware if running Lean_and_mean
|
|
*
|
|
* 21 8/05/97 1:39p Lawrance
|
|
* support compressed stereo playback
|
|
*
|
|
* 20 7/31/97 10:38a Lawrance
|
|
* return old debug function for toggling DirectSound3D
|
|
*
|
|
* 19 7/29/97 3:27p Lawrance
|
|
* make console toggle for directsound3d work right
|
|
*
|
|
* 18 7/28/97 11:39a Lawrance
|
|
* allow individual volume scaling on 3D buffers
|
|
*
|
|
* 17 7/18/97 8:18p Lawrance
|
|
* fix bug in ds_get_free_channel() that caused sounds to not play when
|
|
* they should have
|
|
*
|
|
* 16 7/17/97 8:04p Lawrance
|
|
* allow priority sounds to play if free channel, otherwise stop lowest
|
|
* volume priority sound of same type
|
|
*
|
|
* 15 7/17/97 5:57p John
|
|
* made directsound3d config value work
|
|
*
|
|
* 14 7/17/97 5:43p John
|
|
* added new config stuff
|
|
*
|
|
* 13 7/17/97 4:25p John
|
|
* First, broken, stage of changing config stuff
|
|
*
|
|
* 12 7/15/97 12:13p Lawrance
|
|
* don't stop sounds that have highest priority
|
|
*
|
|
* 11 7/15/97 11:15a Lawrance
|
|
* limit the max instances of simultaneous sound effects, implement
|
|
* priorities to force critical sounds
|
|
*
|
|
* 10 6/09/97 11:50p Lawrance
|
|
* integrating DirectSound3D
|
|
*
|
|
* 9 6/08/97 5:59p Lawrance
|
|
* integrate DirectSound3D into sound system
|
|
*
|
|
* 8 6/04/97 1:19p Lawrance
|
|
* made hardware mixing robust
|
|
*
|
|
* 7 6/03/97 1:56p Hoffoss
|
|
* Return correct error code when direct sound init fails.
|
|
*
|
|
* 6 6/03/97 12:07p Lawrance
|
|
* don't enable 3D sounds in Primary buffer
|
|
*
|
|
* 5 6/02/97 3:45p Dan
|
|
* temp disable of hardware mixing until problem solved with
|
|
* CreateBuffer() failing
|
|
*
|
|
* 4 6/02/97 1:45p Lawrance
|
|
* implementing hardware mixing
|
|
*
|
|
* 3 5/29/97 4:01p Lawrance
|
|
* let snd_init() have final say on initialization
|
|
*
|
|
* 2 5/29/97 12:04p Lawrance
|
|
* creation of file to hold DirectSound specific portions
|
|
*
|
|
* $NoKeywords: $
|
|
*/
|
|
|
|
#include "globalincs/pstypes.h"
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <objbase.h>
|
|
//#include <initguid.h>
|
|
#include "mm/mmreg.h"
|
|
#define STUB_FUNCTION
|
|
#endif
|
|
#include "cfile/cfile.h"
|
|
#include "sound/ds.h"
|
|
#include "sound/ds3d.h"
|
|
#include "sound/acm.h"
|
|
#include "osapi/osapi.h"
|
|
#include "sound/dscap.h"
|
|
|
|
#ifdef USE_OPENAL
|
|
#if !(defined(__APPLE__) || defined(_WIN32))
|
|
#include <AL/al.h>
|
|
#include <AL/alc.h>
|
|
#else
|
|
#include "al.h"
|
|
#include "alc.h"
|
|
#endif // !__APPLE__ && !_WIN32
|
|
#else
|
|
#include "sound/channel.h"
|
|
#endif // USE_OPENAL
|
|
|
|
extern unsigned short UserSampleRate, UserSampleBits; //in sound.h
|
|
|
|
|
|
#ifndef USE_OPENAL
|
|
// Pointers to functions contained in DSOUND.dll
|
|
HRESULT (__stdcall *pfn_DirectSoundCreate)(LPGUID lpGuid, LPDIRECTSOUND *ppDS, IUnknown FAR *pUnkOuter) = NULL;
|
|
HRESULT (__stdcall *pfn_DirectSoundCaptureCreate)(LPGUID lpGUID, LPDIRECTSOUNDCAPTURE *lplpDSC, LPUNKNOWN pUnkOuter) = NULL;
|
|
int Ds_dll_loaded=0;
|
|
HINSTANCE Ds_dll_handle=NULL;
|
|
|
|
LPDIRECTSOUND pDirectSound = NULL;
|
|
LPDIRECTSOUNDBUFFER pPrimaryBuffer = NULL;
|
|
|
|
static LPKSPROPERTYSET pPropertySet; // pointer to sound card property set
|
|
static LPDIRECTSOUNDBUFFER Ds_property_set_pdsb = NULL;
|
|
static LPDIRECTSOUND3DBUFFER Ds_property_set_pds3db = NULL;
|
|
|
|
static int Ds_must_call_couninitialize = 0;
|
|
|
|
channel* Channels; //[MAX_CHANNELS];
|
|
static int channel_next_sig = 1;
|
|
|
|
#define MAX_DS_SOFTWARE_BUFFERS 256
|
|
typedef struct ds_sound_buffer
|
|
{
|
|
LPDIRECTSOUNDBUFFER pdsb;
|
|
DSBUFFERDESC desc;
|
|
WAVEFORMATEX wfx;
|
|
|
|
} ds_sound_buffer;
|
|
|
|
ds_sound_buffer ds_software_buffers[MAX_DS_SOFTWARE_BUFFERS];
|
|
|
|
#define MAX_DS_HARDWARE_BUFFERS 32
|
|
ds_sound_buffer ds_hardware_buffers[MAX_DS_HARDWARE_BUFFERS];
|
|
|
|
static DSCAPS Soundcard_caps; // current soundcard capabilities
|
|
|
|
extern int Snd_sram; // mem (in bytes) used up by storing sounds in system memory
|
|
extern int Snd_hram; // mem (in bytes) used up by storing sounds in soundcard memory
|
|
|
|
static int Ds_use_ds3d = 0;
|
|
static int Ds_use_eax = 0;
|
|
|
|
static bool Stop_logging_sounds = false;
|
|
|
|
static int MAX_CHANNELS = 0; // initialized properly in ds_init_channels()
|
|
|
|
///////////////////////////
|
|
//
|
|
// EAX
|
|
//
|
|
///////////////////////////
|
|
|
|
// presets
|
|
//#define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC,0.5F,1.493F,0.5F
|
|
#define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC,0.2F,0.2F,1.0F
|
|
#define EAX_PRESET_PADDEDCELL EAX_ENVIRONMENT_PADDEDCELL,0.25F,0.1F,0.0F
|
|
#define EAX_PRESET_ROOM EAX_ENVIRONMENT_ROOM,0.417F,0.4F,0.666F
|
|
#define EAX_PRESET_BATHROOM EAX_ENVIRONMENT_BATHROOM,0.653F,1.499F,0.166F
|
|
#define EAX_PRESET_LIVINGROOM EAX_ENVIRONMENT_LIVINGROOM,0.208F,0.478F,0.0F
|
|
#define EAX_PRESET_STONEROOM EAX_ENVIRONMENT_STONEROOM,0.5F,2.309F,0.888F
|
|
#define EAX_PRESET_AUDITORIUM EAX_ENVIRONMENT_AUDITORIUM,0.403F,4.279F,0.5F
|
|
#define EAX_PRESET_CONCERTHALL EAX_ENVIRONMENT_CONCERTHALL,0.5F,3.961F,0.5F
|
|
#define EAX_PRESET_CAVE EAX_ENVIRONMENT_CAVE,0.5F,2.886F,1.304F
|
|
#define EAX_PRESET_ARENA EAX_ENVIRONMENT_ARENA,0.361F,7.284F,0.332F
|
|
#define EAX_PRESET_HANGAR EAX_ENVIRONMENT_HANGAR,0.5F,10.0F,0.3F
|
|
#define EAX_PRESET_CARPETEDHALLWAY EAX_ENVIRONMENT_CARPETEDHALLWAY,0.153F,0.259F,2.0F
|
|
#define EAX_PRESET_HALLWAY EAX_ENVIRONMENT_HALLWAY,0.361F,1.493F,0.0F
|
|
#define EAX_PRESET_STONECORRIDOR EAX_ENVIRONMENT_STONECORRIDOR,0.444F,2.697F,0.638F
|
|
#define EAX_PRESET_ALLEY EAX_ENVIRONMENT_ALLEY,0.25F,1.752F,0.776F
|
|
#define EAX_PRESET_FOREST EAX_ENVIRONMENT_FOREST,0.111F,3.145F,0.472F
|
|
#define EAX_PRESET_CITY EAX_ENVIRONMENT_CITY,0.111F,2.767F,0.224F
|
|
#define EAX_PRESET_MOUNTAINS EAX_ENVIRONMENT_MOUNTAINS,0.194F,7.841F,0.472F
|
|
#define EAX_PRESET_QUARRY EAX_ENVIRONMENT_QUARRY,1.0F,1.499F,0.5F
|
|
#define EAX_PRESET_PLAIN EAX_ENVIRONMENT_PLAIN,0.097F,2.767F,0.224F
|
|
#define EAX_PRESET_PARKINGLOT EAX_ENVIRONMENT_PARKINGLOT,0.208F,1.652F,1.5F
|
|
#define EAX_PRESET_SEWERPIPE EAX_ENVIRONMENT_SEWERPIPE,0.652F,2.886F,0.25F
|
|
#define EAX_PRESET_UNDERWATER EAX_ENVIRONMENT_UNDERWATER,1.0F,1.499F,0.0F
|
|
#define EAX_PRESET_DRUGGED EAX_ENVIRONMENT_DRUGGED,0.875F,8.392F,1.388F
|
|
#define EAX_PRESET_DIZZY EAX_ENVIRONMENT_DIZZY,0.139F,17.234F,0.666F
|
|
#define EAX_PRESET_PSYCHOTIC EAX_ENVIRONMENT_PSYCHOTIC,0.486F,7.563F,0.806F
|
|
|
|
static LPKSPROPERTYSET Ds_eax_reverb = NULL;
|
|
|
|
static int Ds_eax_inited = 0;
|
|
|
|
EAX_REVERBPROPERTIES Ds_eax_presets[] =
|
|
{
|
|
{EAX_PRESET_GENERIC},
|
|
{EAX_PRESET_PADDEDCELL},
|
|
{EAX_PRESET_ROOM},
|
|
{EAX_PRESET_BATHROOM},
|
|
{EAX_PRESET_LIVINGROOM},
|
|
{EAX_PRESET_STONEROOM},
|
|
{EAX_PRESET_AUDITORIUM},
|
|
{EAX_PRESET_CONCERTHALL},
|
|
{EAX_PRESET_CAVE},
|
|
{EAX_PRESET_ARENA},
|
|
{EAX_PRESET_HANGAR},
|
|
{EAX_PRESET_CARPETEDHALLWAY},
|
|
{EAX_PRESET_HALLWAY},
|
|
{EAX_PRESET_STONECORRIDOR},
|
|
{EAX_PRESET_ALLEY},
|
|
{EAX_PRESET_FOREST},
|
|
{EAX_PRESET_CITY},
|
|
{EAX_PRESET_MOUNTAINS},
|
|
{EAX_PRESET_QUARRY},
|
|
{EAX_PRESET_PLAIN},
|
|
{EAX_PRESET_PARKINGLOT},
|
|
{EAX_PRESET_SEWERPIPE},
|
|
{EAX_PRESET_UNDERWATER},
|
|
{EAX_PRESET_DRUGGED},
|
|
{EAX_PRESET_DIZZY},
|
|
{EAX_PRESET_PSYCHOTIC},
|
|
};
|
|
|
|
GUID DSPROPSETID_EAX_ReverbProperties_Def = {0x4a4e6fc1, 0xc341, 0x11d1, {0xb7, 0x3a, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}};
|
|
GUID DSPROPSETID_EAXBUFFER_ReverbProperties_Def = {0x4a4e6fc0, 0xc341, 0x11d1, {0xb7, 0x3a, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}};
|
|
|
|
//----------------------------------------------------------------
|
|
// prototypes
|
|
void ds_get_soundcard_caps(DSCAPS *dscaps);
|
|
|
|
#elif defined(USE_OPENAL)
|
|
|
|
typedef struct sound_buffer
|
|
{
|
|
ALuint buf_id; // OpenAL buffer id
|
|
int source_id; // source index this buffer is currently bound to
|
|
|
|
int frequency;
|
|
int bits_per_sample;
|
|
int nchannels;
|
|
int nseconds;
|
|
int nbytes;
|
|
} sound_buffer;
|
|
|
|
#define MAX_DS_SOFTWARE_BUFFERS 256
|
|
|
|
static int MAX_CHANNELS = 32; // initialized properly in ds_init_channels()
|
|
channel *Channels = NULL;
|
|
static int channel_next_sig = 1;
|
|
|
|
sound_buffer sound_buffers[MAX_DS_SOFTWARE_BUFFERS];
|
|
|
|
extern int Snd_sram; // mem (in bytes) used up by storing sounds in system memory
|
|
|
|
static int Ds_use_ds3d = 0;
|
|
static int Ds_use_a3d = 0;
|
|
static int Ds_use_eax = 0;
|
|
|
|
static int AL_play_position = 0;
|
|
|
|
#ifndef AL_BYTE_LOKI
|
|
// in case it's not defined by older/other drivers
|
|
#define AL_BYTE_LOKI 0x100C
|
|
#endif
|
|
|
|
ALCdevice *ds_sound_device = NULL;
|
|
ALCcontext *ds_sound_context = NULL;
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
// openal_error_string()
|
|
//
|
|
// Returns the human readable error string if there is an error or NULL if not
|
|
//
|
|
const char* openal_error_string(int get_alc)
|
|
{
|
|
int i;
|
|
|
|
if (get_alc) {
|
|
i = alcGetError(NULL);
|
|
|
|
if ( i != ALC_NO_ERROR )
|
|
return (const char*) alcGetString(NULL, i);
|
|
}
|
|
else {
|
|
i = alGetError();
|
|
|
|
if ( i != AL_NO_ERROR )
|
|
return (const char*)alGetString(i);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#endif // defined(USE_OPENAL)
|
|
|
|
int ds_vol_lookup[101]; // lookup table for direct sound volumes
|
|
int ds_initialized = FALSE;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// ds_is_3d_buffer()
|
|
//
|
|
// Determine if a secondary buffer is a 3d secondary buffer.
|
|
//
|
|
#ifndef USE_OPENAL
|
|
int ds_is_3d_buffer(LPDIRECTSOUNDBUFFER pdsb)
|
|
{
|
|
DSBCAPS dsbc;
|
|
HRESULT hr;
|
|
|
|
dsbc.dwSize = sizeof(dsbc);
|
|
hr = pdsb->GetCaps(&dsbc);
|
|
if ( hr == DS_OK && dsbc.dwFlags & DSBCAPS_CTRL3D ) {
|
|
return TRUE;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
#endif
|
|
//--------------------------------------------------------------------------
|
|
// ds_is_3d_buffer()
|
|
//
|
|
// Determine if a secondary buffer is a 3d secondary buffer.
|
|
//
|
|
int ds_is_3d_buffer(int sid)
|
|
{
|
|
#ifndef USE_OPENAL
|
|
if ( sid >= 0 ) {
|
|
return ds_is_3d_buffer(ds_software_buffers[sid].pdsb);
|
|
}
|
|
#else
|
|
// they are all 3d
|
|
if ( sid >= 0 ) {
|
|
return 1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// ds_build_vol_lookup()
|
|
//
|
|
// Fills up the ds_vol_lookup[] tables that converts from a volume in the form
|
|
// 0.0 -> 1.0 to -10000 -> 0 (this is the DirectSound method, where units are
|
|
// hundredths of decibls)
|
|
//
|
|
void ds_build_vol_lookup()
|
|
{
|
|
int i;
|
|
float vol;
|
|
|
|
ds_vol_lookup[0] = -10000;
|
|
for ( i = 1; i <= 100; i++ ) {
|
|
vol = i / 100.0f;
|
|
ds_vol_lookup[i] = fl2i( (log(vol) / log(2.0f)) * 1000.0f);
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
// ds_convert_volume()
|
|
//
|
|
// Takes volume between 0.0f and 1.0f and converts into
|
|
// DirectSound style volumes between -10000 and 0.
|
|
int ds_convert_volume(float volume)
|
|
{
|
|
int index;
|
|
|
|
index = fl2i(volume * 100.0f);
|
|
if ( index > 100 )
|
|
index = 100;
|
|
if ( index < 0 )
|
|
index = 0;
|
|
|
|
return ds_vol_lookup[index];
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// ds_get_percentage_vol()
|
|
//
|
|
// Converts -10000 -> 0 range volume to 0 -> 1
|
|
float ds_get_percentage_vol(int ds_vol)
|
|
{
|
|
double vol;
|
|
vol = pow(2.0, ds_vol/1000.0);
|
|
return (float)vol;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_parse_sound()
|
|
//
|
|
// Parse a wave file.
|
|
//
|
|
// parameters: filename => file of sound to parse
|
|
// dest => address of pointer of where to store raw sound data (output parm)
|
|
// dest_size => number of bytes of sound data stored (output parm)
|
|
// header => address of pointer to a WAVEFORMATEX struct (output parm)
|
|
// ovf => pointer to a OggVorbis_File struct, OGG vorbis only (output parm)
|
|
//
|
|
// returns: 0 => wave file successfully parsed
|
|
// -1 => error
|
|
//
|
|
// NOTE: memory is malloced for the header and dest (if not OGG) in this function. It is the responsibility
|
|
// of the caller to free this memory later.
|
|
//
|
|
int ds_parse_sound(CFILE* fp, ubyte **dest, uint *dest_size, WAVEFORMATEX **header, OggVorbis_File *ovf)
|
|
{
|
|
PCMWAVEFORMAT PCM_header;
|
|
ushort cbExtra = 0;
|
|
unsigned int tag, size, next_chunk;
|
|
bool got_fmt = false, got_data = false;
|
|
|
|
// some preinit stuff, could be done from calling function but this should guarantee it's right
|
|
*dest = NULL;
|
|
*dest_size = 0;
|
|
|
|
if ( fp == NULL ) {
|
|
return -1;
|
|
}
|
|
|
|
// Check for OGG Vorbis first
|
|
if ( !ov_open_callbacks(fp, ovf, NULL, 0, cfile_callbacks) ) {
|
|
// got one, now read all of the needed header info
|
|
ov_info(ovf, -1);
|
|
|
|
// we only support one logical bitstream
|
|
if ( ov_streams(ovf) != 1 ) {
|
|
nprintf(( "Sound", "SOUND ==> OGG reading error: We don't support bitstream changes!\n" ));
|
|
return -1;
|
|
}
|
|
|
|
if ( (*header = (WAVEFORMATEX *) vm_malloc ( sizeof(WAVEFORMATEX) )) != NULL ) {
|
|
(*header)->wFormatTag = OGG_FORMAT_VORBIS;
|
|
(*header)->nChannels = (ushort)ovf->vi->channels;
|
|
(*header)->nSamplesPerSec = ovf->vi->rate;
|
|
(*header)->wBitsPerSample = 16; //OGGs always decoded at 16 bits here
|
|
(*header)->nBlockAlign = (ushort)(ovf->vi->channels * 2);
|
|
(*header)->nAvgBytesPerSec = ovf->vi->rate * ovf->vi->channels * 2;
|
|
|
|
*dest_size = (uint)(ovf->vi->rate * ovf->vi->channels * 16 * 2);
|
|
} else {
|
|
Assert( 0 );
|
|
return -1;
|
|
}
|
|
|
|
// we're all good, can leave now
|
|
return 0;
|
|
}
|
|
|
|
// Skip the "RIFF" tag and file size (8 bytes)
|
|
// Skip the "WAVE" tag (4 bytes)
|
|
// IMPORTANT!! Look at snd_load before even THINKING about changing this.
|
|
cfseek( fp, 12, CF_SEEK_SET );
|
|
|
|
// Now read RIFF tags until the end of file
|
|
|
|
while(1) {
|
|
if ( cfread( &tag, sizeof(uint), 1, fp ) != 1 )
|
|
break;
|
|
|
|
tag = INTEL_INT( tag );
|
|
|
|
if ( cfread( &size, sizeof(uint), 1, fp ) != 1 )
|
|
break;
|
|
|
|
size = INTEL_INT( size );
|
|
|
|
next_chunk = cftell(fp) + size;
|
|
|
|
switch( tag ) {
|
|
case 0x20746d66: // The 'fmt ' tag
|
|
//nprintf(("Sound", "SOUND => size of fmt block: %d\n", size));
|
|
PCM_header.wf.wFormatTag = cfread_ushort(fp);
|
|
PCM_header.wf.nChannels = cfread_ushort(fp);
|
|
PCM_header.wf.nSamplesPerSec = cfread_uint(fp);
|
|
PCM_header.wf.nAvgBytesPerSec = cfread_uint(fp);
|
|
PCM_header.wf.nBlockAlign = cfread_ushort(fp);
|
|
PCM_header.wBitsPerSample = cfread_ushort(fp);
|
|
|
|
if ( PCM_header.wf.wFormatTag != WAVE_FORMAT_PCM ) {
|
|
cbExtra = cfread_ushort(fp);
|
|
}
|
|
|
|
// Allocate memory for WAVEFORMATEX structure + extra bytes
|
|
if ( (*header = (WAVEFORMATEX *) vm_malloc ( sizeof(WAVEFORMATEX)+cbExtra )) != NULL ){
|
|
// Copy bytes from temporary format structure
|
|
memcpy (*header, &PCM_header, sizeof(PCM_header));
|
|
(*header)->cbSize = cbExtra;
|
|
|
|
// Read those extra bytes, append to WAVEFORMATEX structure
|
|
if (cbExtra != 0) {
|
|
cfread( ((ubyte *)(*header) + sizeof(WAVEFORMATEX)), cbExtra, 1, fp);
|
|
}
|
|
}
|
|
else {
|
|
Assert(0); // malloc failed
|
|
}
|
|
got_fmt = true;
|
|
|
|
break;
|
|
case 0x61746164: // the 'data' tag
|
|
*dest_size = size;
|
|
(*dest) = (ubyte *)vm_malloc(size);
|
|
Assert( *dest != NULL );
|
|
cfread( *dest, size, 1, fp );
|
|
got_data = true;
|
|
break;
|
|
default: // unknown, skip it
|
|
break;
|
|
}
|
|
|
|
// This is here so that we can avoid reading data that we don't understand or properly handle.
|
|
// We could do this just as well by checking the RIFF size, but this is easier - taylor
|
|
if (got_fmt && got_data)
|
|
break;
|
|
|
|
cfseek( fp, next_chunk, CF_SEEK_SET );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_get_sid()
|
|
//
|
|
//
|
|
int ds_get_sid()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_DS_SOFTWARE_BUFFERS; i++ ) {
|
|
if ( sound_buffers[i].buf_id == 0 )
|
|
break;
|
|
}
|
|
|
|
if ( i == MAX_DS_SOFTWARE_BUFFERS ) {
|
|
return -1;
|
|
}
|
|
|
|
return i;
|
|
#else
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_DS_SOFTWARE_BUFFERS; i++ ) {
|
|
if ( ds_software_buffers[i].pdsb == NULL )
|
|
break;
|
|
}
|
|
|
|
if ( i == MAX_DS_SOFTWARE_BUFFERS ) {
|
|
return -1;
|
|
}
|
|
|
|
return i;
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_get_hid()
|
|
//
|
|
//
|
|
int ds_get_hid()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return -1;
|
|
#else
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_DS_HARDWARE_BUFFERS; i++ ) {
|
|
if ( ds_hardware_buffers[i].pdsb == NULL )
|
|
break;
|
|
}
|
|
|
|
if ( i == MAX_DS_HARDWARE_BUFFERS ) {
|
|
return -1;
|
|
}
|
|
|
|
return i;
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// Load a DirectSound secondary buffer with sound data. The sounds data for
|
|
// game sounds are stored in the DirectSound secondary buffers, and are
|
|
// duplicated as needed and placed in the Channels[] array to be played.
|
|
//
|
|
//
|
|
// parameters:
|
|
// sid => pointer to software id for sound ( output parm)
|
|
// hid => pointer to hardware id for sound ( output parm)
|
|
// final_size => pointer to storage to receive uncompressed sound size (output parm)
|
|
// header => pointer to a WAVEFORMATEX structure
|
|
// si => sound_info structure, contains details on the sound format
|
|
// flags => buffer properties ( DS_HARDWARE , DS_3D )
|
|
//
|
|
// returns: -1 => sound effect could not loaded into a secondary buffer
|
|
// 0 => sound effect successfully loaded into a secondary buffer
|
|
//
|
|
//
|
|
// NOTE: this function is slow, especially when sounds are loaded into hardware. Don't call this
|
|
// function from within gameplay.
|
|
//
|
|
int ds_load_buffer(int *sid, int *hid, int *final_size, void *header, sound_info *si, int flags)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
Assert( final_size != NULL );
|
|
Assert( header != NULL );
|
|
Assert( si != NULL );
|
|
|
|
// All sounds are required to have a software buffer
|
|
|
|
*sid = ds_get_sid();
|
|
if ( *sid == -1 ) {
|
|
nprintf(("Sound","SOUND ==> No more sound buffers available\n"));
|
|
return -1;
|
|
}
|
|
|
|
ALuint pi;
|
|
OpenAL_ErrorCheck( alGenBuffers (1, &pi), return -1 );
|
|
|
|
ALenum format;
|
|
ALsizei size;
|
|
ALint bits, bps;
|
|
ALuint frequency;
|
|
ALvoid *data = NULL;
|
|
int byte_order = 0, section, last_section = -1;
|
|
|
|
// the below two covnert_ variables are only used when the wav format is not
|
|
// PCM. DirectSound only takes PCM sound data, so we must convert to PCM if required
|
|
ubyte *convert_buffer = NULL; // storage for converted wav file
|
|
int convert_len; // num bytes of converted wav file
|
|
uint src_bytes_used; // number of source bytes actually converted (should always be equal to original size)
|
|
int rc;
|
|
WAVEFORMATEX *pwfx = (WAVEFORMATEX *)header;
|
|
|
|
|
|
switch (si->format) {
|
|
case WAVE_FORMAT_PCM:
|
|
Assert( si->data != NULL );
|
|
bits = si->bits;
|
|
bps = si->avg_bytes_per_sec;
|
|
size = si->size;
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
// swap 16-bit sound data
|
|
if (bits == 16) {
|
|
ushort *swap_tmp;
|
|
|
|
for (uint i=0; i<size; i=i+2) {
|
|
swap_tmp = (ushort*)(si->data + i);
|
|
*swap_tmp = INTEL_SHORT(*swap_tmp);
|
|
}
|
|
}
|
|
#endif
|
|
data = si->data;
|
|
break;
|
|
|
|
case WAVE_FORMAT_ADPCM:
|
|
Assert( si->data != NULL );
|
|
// this ADPCM decoder decodes to 16-bit only so keep that in mind
|
|
nprintf(( "Sound", "SOUND ==> converting sound from ADPCM to PCM\n" ));
|
|
rc = ACM_convert_ADPCM_to_PCM(pwfx, si->data, si->size, &convert_buffer, 0, &convert_len, &src_bytes_used, 16);
|
|
|
|
if ( rc == -1 ) {
|
|
return -1;
|
|
}
|
|
|
|
if (src_bytes_used != si->size) {
|
|
return -1; // ACM conversion failed?
|
|
}
|
|
|
|
bits = 16;
|
|
bps = (((si->n_channels * bits) / 8) * si->sample_rate);
|
|
size = convert_len;
|
|
data = convert_buffer;
|
|
|
|
nprintf(( "Sound", "SOUND ==> Coverted sound from ADPCM to PCM successfully\n" ));
|
|
break;
|
|
|
|
case OGG_FORMAT_VORBIS:
|
|
nprintf(( "Sound", "SOUND ==> converting sound from OGG to PCM\n" ));
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
byte_order = 1;
|
|
#endif
|
|
src_bytes_used = 0;
|
|
convert_buffer = (ubyte*)vm_malloc(si->size);
|
|
Assert(convert_buffer != NULL);
|
|
|
|
if (convert_buffer == NULL)
|
|
return -1;
|
|
|
|
while (src_bytes_used < si->size) {
|
|
rc = ov_read(&si->ogg_info, (char *) convert_buffer + src_bytes_used, si->size - src_bytes_used, byte_order, si->bits / 8, 1, §ion);
|
|
|
|
// fail if the bitstream changes, shouldn't get this far if that's the case though
|
|
if ((last_section != -1) && (last_section != section)) {
|
|
nprintf(( "Sound", "SOUND ==> OGG reading error: We don't support bitstream changes!\n" ));
|
|
vm_free(convert_buffer);
|
|
convert_buffer = NULL;
|
|
return -1;
|
|
}
|
|
|
|
if (rc == OV_EBADLINK) {
|
|
vm_free(convert_buffer);
|
|
convert_buffer = NULL;
|
|
return -1;
|
|
} else if (rc == 0) {
|
|
break;
|
|
} else if (rc > 0) {
|
|
last_section = section;
|
|
src_bytes_used += rc;
|
|
}
|
|
}
|
|
|
|
bits = si->bits;
|
|
bps = (((si->n_channels * bits) / 8) * si->sample_rate);
|
|
size = (int)src_bytes_used;
|
|
data = convert_buffer;
|
|
|
|
// we're done with ogg stuff so clean it up
|
|
ov_clear(&si->ogg_info);
|
|
|
|
nprintf(( "Sound", "SOUND ==> Coverted sound from OGG to PCM successfully\n" ));
|
|
break;
|
|
|
|
default:
|
|
STUB_FUNCTION;
|
|
return -1;
|
|
}
|
|
|
|
/* format is now in pcm */
|
|
frequency = si->sample_rate;
|
|
|
|
if (bits == 16) {
|
|
if (si->n_channels == 2) {
|
|
format = AL_FORMAT_STEREO16;
|
|
} else if (si->n_channels == 1) {
|
|
format = AL_FORMAT_MONO16;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (bits == 8) {
|
|
if (si->n_channels == 2) {
|
|
format = AL_FORMAT_STEREO8;
|
|
} else if (si->n_channels == 1) {
|
|
format = AL_FORMAT_MONO8;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
Snd_sram += size;
|
|
*final_size = size;
|
|
|
|
OpenAL_ErrorCheck( alBufferData (pi, format, data, size, frequency), return -1 );
|
|
|
|
sound_buffers[*sid].buf_id = pi;
|
|
sound_buffers[*sid].source_id = -1;
|
|
sound_buffers[*sid].frequency = frequency;
|
|
sound_buffers[*sid].bits_per_sample = bits;
|
|
sound_buffers[*sid].nchannels = si->n_channels;
|
|
sound_buffers[*sid].nseconds = size / bps;
|
|
sound_buffers[*sid].nbytes = size;
|
|
|
|
if ( convert_buffer )
|
|
vm_free( convert_buffer );
|
|
|
|
return 0;
|
|
#else
|
|
Assert( final_size != NULL );
|
|
Assert( si != NULL );
|
|
Assert( si->size > 0 );
|
|
Assert( si->sample_rate > 0);
|
|
Assert( si->bits > 0 );
|
|
Assert( si->n_channels > 0 );
|
|
Assert( si->n_block_align >= 0 );
|
|
Assert( si->avg_bytes_per_sec > 0 );
|
|
|
|
WAVEFORMATEX *pwfx = (WAVEFORMATEX *)header;
|
|
DSBUFFERDESC BufferDesc;
|
|
WAVEFORMATEX WaveFormat;
|
|
HRESULT DSReturn;
|
|
int rc = 1, final_sound_size, DSOUND_load_buffer_result = 0, byte_order = 0, section, last_section = -1;
|
|
BYTE *pData, *pData2;
|
|
DWORD DataSize, DataSize2;
|
|
|
|
// the below two covnert_ variables are only used when the wav format is not
|
|
// PCM. DirectSound only takes PCM sound data, so we must convert to PCM if required
|
|
ubyte *convert_buffer = NULL; // storage for converted wav file
|
|
int convert_len; // num bytes of converted wav file
|
|
uint src_bytes_used; // number of source bytes actually converted (should always be equal to original size)
|
|
|
|
// Ensure DirectSound initialized
|
|
if (!ds_initialized) {
|
|
DSOUND_load_buffer_result = -1;
|
|
goto DSOUND_load_buffer_done;
|
|
}
|
|
|
|
// Set up buffer information
|
|
WaveFormat.wFormatTag = (unsigned short)si->format;
|
|
WaveFormat.nChannels = (unsigned short)si->n_channels;
|
|
WaveFormat.nSamplesPerSec = si->sample_rate;
|
|
WaveFormat.wBitsPerSample = (unsigned short)si->bits;
|
|
WaveFormat.cbSize = 0;
|
|
WaveFormat.nBlockAlign = (unsigned short)si->n_block_align;
|
|
WaveFormat.nAvgBytesPerSec = si->avg_bytes_per_sec;
|
|
|
|
final_sound_size = si->size; // assume this format will be used, may be over-ridded by convert_len
|
|
|
|
// Assert(WaveFormat.nChannels == 1);
|
|
|
|
switch ( si->format ) {
|
|
case WAVE_FORMAT_PCM:
|
|
break;
|
|
|
|
case WAVE_FORMAT_ADPCM:
|
|
Assert( pwfx != NULL );
|
|
Assert( si->data != NULL );
|
|
nprintf(( "Sound", "SOUND ==> converting sound from ADPCM to PCM\n" ));
|
|
|
|
//Do ADPCM conversion at what bitrate the user wants.
|
|
if(UserSampleBits == 16 || UserSampleBits == 8)
|
|
WaveFormat.wBitsPerSample = UserSampleBits;
|
|
else if(UserSampleBits > 16)
|
|
WaveFormat.wBitsPerSample = 16;
|
|
else
|
|
WaveFormat.wBitsPerSample = 8;
|
|
|
|
rc = ACM_convert_ADPCM_to_PCM(pwfx, si->data, si->size, &convert_buffer, 0, &convert_len, &src_bytes_used, WaveFormat.wBitsPerSample);
|
|
if ( rc == -1 ) {
|
|
DSOUND_load_buffer_result = -1;
|
|
goto DSOUND_load_buffer_done;
|
|
}
|
|
|
|
if (src_bytes_used != si->size) {
|
|
Int3(); // ACM conversion failed?
|
|
DSOUND_load_buffer_result = -1;
|
|
goto DSOUND_load_buffer_done;
|
|
}
|
|
|
|
final_sound_size = convert_len;
|
|
|
|
// Set up the WAVEFORMATEX structure to have the right PCM characteristics
|
|
WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
|
WaveFormat.nChannels = (unsigned short)si->n_channels;
|
|
WaveFormat.nSamplesPerSec = si->sample_rate;
|
|
WaveFormat.cbSize = 0;
|
|
WaveFormat.nBlockAlign = (unsigned short)(( WaveFormat.nChannels * WaveFormat.wBitsPerSample ) / 8);
|
|
WaveFormat.nAvgBytesPerSec = WaveFormat.nBlockAlign * WaveFormat.nSamplesPerSec;
|
|
|
|
nprintf(( "Sound", "SOUND ==> Coverted sound from ADPCM to PCM successfully\n" ));
|
|
break;
|
|
|
|
case OGG_FORMAT_VORBIS:
|
|
nprintf(( "Sound", "SOUND ==> converting sound from OGG to PCM\n" ));
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
byte_order = 1;
|
|
#endif
|
|
src_bytes_used = 0;
|
|
convert_buffer = (byte*) vm_malloc(si->size);
|
|
Assert(convert_buffer != NULL);
|
|
|
|
while (src_bytes_used < si->size) {
|
|
rc = ov_read(&si->ogg_info, (char *) convert_buffer + src_bytes_used, si->size - src_bytes_used, byte_order, si->bits / 8, 1, §ion);
|
|
|
|
// fail if the bitstream changes, shouldn't get this far if that's the case though
|
|
if ((last_section != -1) && (last_section != section)) {
|
|
nprintf(( "Sound", "SOUND ==> OGG reading error: We don't support bitstream changes!\n" ));
|
|
vm_free(convert_buffer);
|
|
convert_buffer = NULL;
|
|
DSOUND_load_buffer_result = -1;
|
|
goto DSOUND_load_buffer_done;
|
|
}
|
|
|
|
if (rc == OV_EBADLINK) {
|
|
vm_free(convert_buffer);
|
|
convert_buffer = NULL;
|
|
DSOUND_load_buffer_result = -1;
|
|
goto DSOUND_load_buffer_done;
|
|
} else if (rc == 0) {
|
|
break;
|
|
} else if (rc > 0) {
|
|
last_section = section;
|
|
src_bytes_used += rc;
|
|
}
|
|
}
|
|
|
|
final_sound_size = (int)src_bytes_used;
|
|
|
|
WaveFormat.wFormatTag = (unsigned short) WAVE_FORMAT_PCM;
|
|
WaveFormat.nChannels = (unsigned short) si->n_channels;
|
|
WaveFormat.nSamplesPerSec = si->sample_rate;
|
|
WaveFormat.wBitsPerSample = (unsigned short) si->bits;
|
|
WaveFormat.cbSize = 0;
|
|
WaveFormat.nBlockAlign = (unsigned short) si->n_block_align;
|
|
WaveFormat.nAvgBytesPerSec = si->avg_bytes_per_sec;
|
|
|
|
//The ogg handle isn't needed no more
|
|
ov_clear(&si->ogg_info);
|
|
|
|
nprintf(( "Sound", "SOUND ==> Coverted sound from OGG to PCM successfully\n" ));
|
|
break;
|
|
default:
|
|
nprintf(( "Sound", "Unsupported sound encoding\n" ));
|
|
DSOUND_load_buffer_result = -1;
|
|
goto DSOUND_load_buffer_done;
|
|
break;
|
|
}
|
|
|
|
WaveFormat.wFormatTag = WAVE_FORMAT_PCM; // DirectSound only used PCM wave files
|
|
|
|
// Set up a DirectSound buffer
|
|
ZeroMemory(&BufferDesc, sizeof(BufferDesc));
|
|
BufferDesc.dwSize = sizeof(BufferDesc);
|
|
BufferDesc.dwBufferBytes = final_sound_size;
|
|
BufferDesc.lpwfxFormat = &WaveFormat;
|
|
|
|
// check if DirectSound3D is enabled and the sound is flagged for 3D
|
|
if ((ds_using_ds3d()) && (flags & DS_USE_DS3D)) {
|
|
// if (ds_using_ds3d()) {
|
|
BufferDesc.dwFlags = DSBCAPS_STATIC | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRL3D | DSBCAPS_MUTE3DATMAXDISTANCE;
|
|
} else {
|
|
BufferDesc.dwFlags = DSBCAPS_STATIC | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLVOLUME;
|
|
}
|
|
|
|
// Create a new software buffer using the settings for this wave
|
|
// All sounds are required to have a software buffer
|
|
*sid = ds_get_sid();
|
|
if ( *sid == -1 ) {
|
|
nprintf(("Sound","SOUND ==> No more software secondary buffers available\n"));
|
|
return -1;
|
|
}
|
|
DSReturn = pDirectSound->CreateSoundBuffer(&BufferDesc, &ds_software_buffers[*sid].pdsb, NULL );
|
|
|
|
if ( DSReturn == DS_OK && ds_software_buffers[*sid].pdsb != NULL ) {
|
|
|
|
ds_software_buffers[*sid].desc = BufferDesc;
|
|
ds_software_buffers[*sid].wfx = *BufferDesc.lpwfxFormat;
|
|
|
|
// Lock the buffer and copy in the data
|
|
if ((ds_software_buffers[*sid].pdsb)->Lock(0, final_sound_size, (void**)(&pData), &DataSize, (void**)(&pData2), &DataSize2, 0) == DS_OK) {
|
|
|
|
if ( convert_buffer )
|
|
memcpy(pData, convert_buffer, final_sound_size); // use converted data (PCM format)
|
|
else
|
|
memcpy(pData, si->data, final_sound_size);
|
|
|
|
(ds_software_buffers[*sid].pdsb)->Unlock(pData, DataSize, 0, 0);
|
|
}
|
|
DSOUND_load_buffer_result = 0;
|
|
|
|
// update ram used for sound
|
|
Snd_sram += final_sound_size;
|
|
*final_size = final_sound_size;
|
|
}
|
|
else {
|
|
nprintf(("Sound","SOUND => fatal error in DSOUND_load_buffer\n"));
|
|
*sid = -1;
|
|
DSOUND_load_buffer_result = -1;
|
|
}
|
|
|
|
DSOUND_load_buffer_done:
|
|
if ( convert_buffer )
|
|
vm_free( convert_buffer );
|
|
return DSOUND_load_buffer_result;
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_init_channels()
|
|
//
|
|
// init the Channels[] array
|
|
//
|
|
void ds_init_channels()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int i, n;
|
|
ALuint *sids;
|
|
sids = new ALuint[MAX_CHANNELS];
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Begin crazy little error check... This will try and generate up to MAX_CHANNELS worth
|
|
// of sources and if there is an error break off and reset MAX_CHANNELS to that number. This
|
|
// makes sure that we don't try to make more sources than the implementation can handle.
|
|
//
|
|
// NOTE: This doesn't protect against bad drivers so it's possible to generate more than
|
|
// we could actually use. In that case it will likely segfault.
|
|
|
|
// clear the current error buffer before doing anything else
|
|
n = alGetError();
|
|
while ( n != AL_NO_ERROR ) {
|
|
n = alGetError();
|
|
}
|
|
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
alGenSources( 1, &sids[i] );
|
|
n = alGetError();
|
|
if ( n != AL_NO_ERROR )
|
|
break;
|
|
}
|
|
|
|
// if we didn't make all of them then reset the max and give a Warning message
|
|
if ( i != MAX_CHANNELS ) {
|
|
nprintf(("Warning", "OpenAL: Restricting MAX_CHANNELS to %i (default: %i)\n", i, MAX_CHANNELS));
|
|
MAX_CHANNELS = i;
|
|
}
|
|
|
|
// now we have to delete them of course so that the game can make the real ones
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
if ( (sids[i] != 0) && alIsSource(sids[i]) )
|
|
OpenAL_ErrorPrint( alDeleteSources(1, &sids[i]) );
|
|
}
|
|
|
|
// cleanup
|
|
delete[] sids;
|
|
|
|
// ... End crazy little error check
|
|
// -------------------------------------------------------------------------
|
|
|
|
Channels = (channel*) vm_malloc(sizeof(channel) * MAX_CHANNELS);
|
|
if (Channels == NULL) {
|
|
Error(LOCATION, "Unable to allocate %d bytes for %d audio channels.", sizeof(channel) * MAX_CHANNELS, MAX_CHANNELS);
|
|
}
|
|
|
|
memset( Channels, 0, sizeof(channel) * MAX_CHANNELS );
|
|
|
|
// init the channels
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
OpenAL_ErrorPrint( alGenSources(1, &Channels[i].source_id) );
|
|
Channels[i].buf_id = -1;
|
|
Channels[i].sig = -1;
|
|
Channels[i].snd_id = -1;
|
|
}
|
|
#else
|
|
int i;
|
|
|
|
// detect how many channels we can support
|
|
DSCAPS caps;
|
|
ds_get_soundcard_caps(&caps);
|
|
|
|
// caps.dwSize = sizeof(DSCAPS);
|
|
// pDirectSound->GetCaps(&caps);
|
|
|
|
// minimum 16 channels
|
|
MAX_CHANNELS = caps.dwMaxHwMixingStaticBuffers;
|
|
#ifndef NDEBUG
|
|
int dbg_channels = MAX_CHANNELS;
|
|
#endif
|
|
if (MAX_CHANNELS < 16) {
|
|
MAX_CHANNELS = 16;
|
|
}
|
|
|
|
// allocate the channels array
|
|
Channels = (channel*) vm_malloc(sizeof(channel) * MAX_CHANNELS);
|
|
if (Channels == NULL) {
|
|
Error(LOCATION, "Unable to allocate %d bytes for %d audio channels.", sizeof(channel) * MAX_CHANNELS, MAX_CHANNELS);
|
|
}
|
|
|
|
// init the channels
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
Channels[i].pdsb = NULL;
|
|
Channels[i].pds3db = NULL;
|
|
Channels[i].vol = 0;
|
|
}
|
|
#ifndef NDEBUG
|
|
mprintf(("** MAX_CHANNELS set to %d. DS reported %d.\n", MAX_CHANNELS, dbg_channels));
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_init_software_buffers()
|
|
//
|
|
// init the software buffers
|
|
//
|
|
void ds_init_software_buffers()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_DS_SOFTWARE_BUFFERS; i++ ) {
|
|
sound_buffers[i].buf_id = 0;
|
|
sound_buffers[i].source_id = -1;
|
|
}
|
|
#else
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_DS_SOFTWARE_BUFFERS; i++ ) {
|
|
ds_software_buffers[i].pdsb = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_init_hardware_buffers()
|
|
//
|
|
// init the hardware buffers
|
|
//
|
|
void ds_init_hardware_buffers()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
// STUB_FUNCTION; // not needed with openal (CM)
|
|
return;
|
|
#else
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_DS_HARDWARE_BUFFERS; i++ ) {
|
|
ds_hardware_buffers[i].pdsb = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_init_buffers()
|
|
//
|
|
// init the both the software and hardware buffers
|
|
//
|
|
void ds_init_buffers()
|
|
{
|
|
ds_init_software_buffers();
|
|
ds_init_hardware_buffers();
|
|
}
|
|
|
|
// Get the current soundcard capabilities
|
|
#ifndef USE_OPENAL
|
|
void ds_get_soundcard_caps(DSCAPS *dscaps)
|
|
{
|
|
HRESULT hr;
|
|
int n_hbuffers, hram;
|
|
|
|
dscaps->dwSize = sizeof(DSCAPS);
|
|
|
|
hr = pDirectSound->GetCaps(dscaps);
|
|
if (hr != DS_OK ) {
|
|
nprintf(("Sound","SOUND ==> DirectSound GetCaps() failed with code %s\n.",get_DSERR_text(hr) ));
|
|
return;
|
|
}
|
|
|
|
n_hbuffers = dscaps->dwMaxHwMixingStaticBuffers;
|
|
hram = dscaps->dwTotalHwMemBytes;
|
|
|
|
if ( !(dscaps->dwFlags & DSCAPS_CERTIFIED) ) {
|
|
nprintf(("Sound","SOUND ==> Warning: audio driver is not Microsoft certified.\n"));
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_show_caps()
|
|
//
|
|
// init the both the software and hardware buffers
|
|
//
|
|
void ds_show_caps(DSCAPS *dscaps)
|
|
{
|
|
nprintf(("Sound", "SOUND => Soundcard Capabilities:\n"));
|
|
nprintf(("Sound", "================================\n"));
|
|
nprintf(("Sound", "Number of primary buffers: %d\n", dscaps->dwPrimaryBuffers ));
|
|
nprintf(("Sound", "Number of total hw mixing buffers: %d\n", dscaps->dwMaxHwMixingAllBuffers ));
|
|
nprintf(("Sound", "Number of total hw mixing static buffers: %d\n", dscaps->dwMaxHwMixingStaticBuffers ));
|
|
nprintf(("Sound", "Number of total hw mixing streaming buffers: %d\n", dscaps->dwMaxHwMixingStreamingBuffers ));
|
|
nprintf(("Sound", "Number of free hw mixing buffers: %d\n", dscaps->dwFreeHwMixingAllBuffers ));
|
|
nprintf(("Sound", "Number of free hw mixing static buffers: %d\n", dscaps->dwFreeHwMixingStaticBuffers ));
|
|
nprintf(("Sound", "Number of free hw mixing streaming buffers: %d\n", dscaps->dwFreeHwMixingStreamingBuffers ));
|
|
nprintf(("Sound", "Number of hw 3D buffers: %d\n", dscaps->dwMaxHw3DAllBuffers ));
|
|
nprintf(("Sound", "Number of hw 3D static buffers: %d\n", dscaps->dwMaxHw3DStaticBuffers ));
|
|
nprintf(("Sound", "Number of hw 3D streaming buffers: %d\n", dscaps->dwMaxHw3DStreamingBuffers ));
|
|
nprintf(("Sound", "Number of free hw 3D buffers: %d\n", dscaps->dwFreeHw3DAllBuffers ));
|
|
nprintf(("Sound", "Number of free hw static 3D buffers: %d\n", dscaps->dwFreeHw3DStaticBuffers ));
|
|
nprintf(("Sound", "Number of free hw streaming 3D buffers: %d\n", dscaps->dwFreeHw3DStreamingBuffers ));
|
|
nprintf(("Sound", "Number of total hw bytes: %d\n", dscaps->dwTotalHwMemBytes ));
|
|
nprintf(("Sound", "Number of free hw bytes: %d\n", dscaps->dwFreeHwMemBytes ));
|
|
nprintf(("Sound", "================================\n"));
|
|
}
|
|
#endif
|
|
|
|
// Fill in the waveformat struct with the primary buffer characteristics.
|
|
#ifndef USE_OPENAL
|
|
void ds_get_primary_format(WAVEFORMATEX *wfx, DWORD sample_rate, WORD sample_bits)
|
|
{
|
|
// Set 16 bit / 22KHz / mono
|
|
wfx->wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx->nChannels = 2;
|
|
wfx->nSamplesPerSec = sample_rate;
|
|
wfx->wBitsPerSample = sample_bits;
|
|
wfx->cbSize = 0;
|
|
wfx->nBlockAlign = (unsigned short)(wfx->nChannels * (wfx->wBitsPerSample / 8));
|
|
wfx->nAvgBytesPerSec = wfx->nBlockAlign * wfx->nSamplesPerSec;
|
|
}
|
|
//XSTR:OFF
|
|
// obtain the function pointers from the dsound.dll
|
|
void ds_dll_get_functions()
|
|
{
|
|
pfn_DirectSoundCreate = (HRESULT(__stdcall *)(LPGUID lpGuid, LPDIRECTSOUND *ppDS, IUnknown FAR *pUnkOuter))GetProcAddress(Ds_dll_handle,"DirectSoundCreate");
|
|
pfn_DirectSoundCaptureCreate = (HRESULT(__stdcall *)(LPGUID lpGuid, LPDIRECTSOUNDCAPTURE *lplpDSC, IUnknown FAR *pUnkOuter))GetProcAddress(Ds_dll_handle,"DirectSoundCaptureCreate");
|
|
}
|
|
#endif
|
|
|
|
// Load the dsound.dll, and get funtion pointers
|
|
// exit: 0 -> dll loaded successfully
|
|
// !0 -> dll could not be loaded
|
|
int ds_dll_load()
|
|
{
|
|
#ifndef USE_OPENAL
|
|
if ( !Ds_dll_loaded ) {
|
|
Ds_dll_handle = LoadLibrary("dsound.dll");
|
|
if ( !Ds_dll_handle ) {
|
|
return -1;
|
|
}
|
|
ds_dll_get_functions();
|
|
Ds_dll_loaded=1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Initialize the property set interface.
|
|
//
|
|
// returns: 0 if successful, otherwise -1. If successful, the global pPropertySet will
|
|
// set to a non-NULL value.
|
|
//
|
|
int ds_init_property_set(DWORD sample_rate, WORD sample_bits)
|
|
{
|
|
#ifndef USE_OPENAL
|
|
HRESULT hr;
|
|
|
|
// Create the secondary buffer required for EAX initialization
|
|
WAVEFORMATEX wf;
|
|
wf.wFormatTag = WAVE_FORMAT_PCM;
|
|
wf.nChannels = 1;
|
|
wf.nSamplesPerSec = sample_rate;
|
|
wf.wBitsPerSample = sample_bits;
|
|
wf.cbSize = 0;
|
|
wf.nBlockAlign = (unsigned short)(wf.nChannels * (wf.wBitsPerSample / 8));
|
|
wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec;
|
|
|
|
DSBUFFERDESC dsbd;
|
|
ZeroMemory(&dsbd, sizeof(dsbd));
|
|
dsbd.dwSize = sizeof(dsbd);
|
|
dsbd.dwFlags = DSBCAPS_CTRLDEFAULT | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STATIC | DSBCAPS_CTRL3D | DSBCAPS_MUTE3DATMAXDISTANCE;
|
|
dsbd.dwBufferBytes = 3 * wf.nAvgBytesPerSec;
|
|
dsbd.lpwfxFormat = &wf;
|
|
|
|
// Create a new buffer using the settings for this wave
|
|
hr = pDirectSound->CreateSoundBuffer(&dsbd, &Ds_property_set_pdsb, NULL);
|
|
if (FAILED(hr)) {
|
|
pPropertySet = NULL;
|
|
return -1;
|
|
}
|
|
|
|
// Get the 3D interface from the secondary buffer, which is used to query the EAX interface
|
|
hr = Ds_property_set_pdsb->QueryInterface(IID_IDirectSound3DBuffer, (void**)&Ds_property_set_pds3db);
|
|
if (FAILED(hr)) {
|
|
Ds_property_set_pds3db = NULL;
|
|
return -1;
|
|
}
|
|
|
|
Assert(Ds_property_set_pds3db != NULL);
|
|
hr = Ds_property_set_pds3db->QueryInterface(IID_IKsPropertySet, (void**)&pPropertySet);
|
|
if ((FAILED(hr)) || (pPropertySet == NULL)) {
|
|
return -1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef USE_OPENAL
|
|
const char *openal_get_best_device(int report = 1)
|
|
{
|
|
int ext_length = 0;
|
|
// FIXME: this is crashing for some reason, a NULL device should be legal here, but who knows
|
|
const char *my_default_device = (const char*) alcGetString( NULL, ALC_DEFAULT_DEVICE_SPECIFIER );
|
|
const char *my_devices = (const char*) alcGetString( NULL, ALC_DEVICE_SPECIFIER );
|
|
|
|
if (report) {
|
|
mprintf((" Default OpenAL device: %s\n", (my_default_device != NULL) ? my_default_device : NOX("<none>")));
|
|
|
|
char *str_list = (char*)my_devices;
|
|
|
|
mprintf((" Available OpenAL devices:\n"));
|
|
|
|
if ( (str_list != NULL) && ((ext_length = strlen(str_list)) > 0) ) {
|
|
while (ext_length) {
|
|
mprintf((" %s\n", str_list));
|
|
str_list += (ext_length + 1);
|
|
ext_length = strlen(str_list);
|
|
}
|
|
} else {
|
|
mprintf((" <none>\n"));
|
|
}
|
|
|
|
mprintf(("\n"));
|
|
}
|
|
|
|
return my_default_device;
|
|
}
|
|
#endif
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_init()
|
|
//
|
|
// returns: -1 => init failed
|
|
// 0 => init success
|
|
int ds_init(int use_a3d, int use_eax, unsigned int sample_rate, unsigned short sample_bits)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
// NOTE: A3D and EAX are unused in OpenAL
|
|
int attr[] = { ALC_FREQUENCY, sample_rate, ALC_SYNC, AL_FALSE, 0 };
|
|
|
|
Ds_use_a3d = 0;
|
|
Ds_use_eax = 0;
|
|
// Ds_use_ds3d = 1;
|
|
Ds_use_ds3d = 0;
|
|
|
|
mprintf(("Initializing OpenAL...\n"));
|
|
|
|
// FIXME: see function for problem!
|
|
// openal_get_best_device();
|
|
|
|
// load OpenAL
|
|
#ifdef _WIN32
|
|
// restrict to DirectSound rather than DirectSound3D (the default) here since we may have 'too many hardware sources'
|
|
// type problems (FIXME for a later date since I don't like this with future code) - taylor
|
|
#ifdef AL_VERSION_1_1
|
|
char *device_spec = os_config_read_string( NULL, "SoundDeviceOAL", "Generic Software" );
|
|
mprintf((" Using '%s' as OpenAL sound device...\n", device_spec));
|
|
|
|
ds_sound_device = alcOpenDevice( (const ALCchar *) device_spec );
|
|
#else
|
|
ds_sound_device = alcOpenDevice( (const ALubyte *) NOX("DirectSound") );
|
|
#endif // AL_VERSION_1_1
|
|
#else
|
|
ds_sound_device = alcOpenDevice( NULL );
|
|
#endif
|
|
|
|
if (ds_sound_device == NULL) {
|
|
mprintf(("ERROR: Couldn't open OpenAL device!\n"));
|
|
return -1;
|
|
}
|
|
|
|
// Create Sound Device
|
|
ds_sound_context = alcCreateContext( ds_sound_device, attr );
|
|
|
|
if (ds_sound_context == NULL) {
|
|
mprintf(("ERROR: Couldn't create OpenAL context!\n"));
|
|
alcCloseDevice( ds_sound_device );
|
|
return -1;
|
|
}
|
|
|
|
OpenAL_C_ErrorCheck( alcMakeContextCurrent( ds_sound_context ), return -1 );
|
|
|
|
mprintf(( " OpenAL Vendor : %s\n", alGetString( AL_VENDOR ) ));
|
|
mprintf(( " OpenAL Renderer : %s\n", alGetString( AL_RENDERER ) ));
|
|
mprintf(( " OpenAL Version : %s\n", alGetString( AL_VERSION ) ));
|
|
mprintf(( "\n" ));
|
|
|
|
// make sure we can actually use AL_BYTE_LOKI (Mac OpenAL doesn't have it)
|
|
#ifdef AL_VERSION_1_1
|
|
AL_play_position = alIsExtensionPresent( (const ALchar*)"AL_LOKI_play_position" );
|
|
#else
|
|
AL_play_position = alIsExtensionPresent( (ALubyte*)"AL_LOKI_play_position" );
|
|
#endif // AL_VERSION_1_1
|
|
|
|
if (AL_play_position)
|
|
mprintf(( " Using extension \"AL_LOKI_play_position\".\n" ));
|
|
|
|
// not a big deal here, but for consitancy sake
|
|
if (Ds_use_ds3d && ds3d_init(0) != 0)
|
|
Ds_use_ds3d = 0;
|
|
|
|
// setup default listener position/orientation
|
|
// this is needed for 2D pan
|
|
OpenAL_ErrorPrint( alListener3f(AL_POSITION, 0.0, 0.0, 0.0) );
|
|
|
|
ALfloat list_orien[] = { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f };
|
|
OpenAL_ErrorPrint( alListenerfv(AL_ORIENTATION, list_orien) );
|
|
|
|
ds_build_vol_lookup();
|
|
ds_init_channels();
|
|
ds_init_buffers();
|
|
|
|
mprintf(("... OpenAL successfully initialized!\n"));
|
|
#else
|
|
HRESULT hr;
|
|
HWND hwnd;
|
|
WAVEFORMATEX wave_format;
|
|
DSBUFFERDESC BufferDesc;
|
|
|
|
nprintf(( "Sound", "SOUND ==> Initializing DirectSound...\n" ));
|
|
|
|
hwnd = (HWND)os_get_window();
|
|
if ( hwnd == NULL ) {
|
|
nprintf(( "Sound", "SOUND ==> No window handle, so no sound...\n" ));
|
|
return -1;
|
|
}
|
|
|
|
if ( ds_dll_load() == -1 ) {
|
|
return -1;
|
|
}
|
|
|
|
pDirectSound = NULL;
|
|
|
|
Ds_use_eax = use_eax;
|
|
|
|
if (Ds_use_eax) {
|
|
Ds_use_ds3d = 1;
|
|
}
|
|
|
|
/*
|
|
if (Ds_use_eax) {
|
|
Ds_use_eax = 0;
|
|
}
|
|
*/
|
|
|
|
if (!pfn_DirectSoundCreate) {
|
|
nprintf(( "Sound", "SOUND ==> Could not get DirectSoundCreate function pointer\n" ));
|
|
return -1;
|
|
}
|
|
|
|
hr = pfn_DirectSoundCreate(NULL, &pDirectSound, NULL);
|
|
if (FAILED(hr)) {
|
|
return -1;
|
|
}
|
|
|
|
// Set up DirectSound for exclusive mode, so we can change the primary buffer if we want to.
|
|
hr = pDirectSound->SetCooperativeLevel(hwnd, DSSCL_EXCLUSIVE);
|
|
if (hr != DS_OK) {
|
|
nprintf(("Sound","SOUND ==> DirectSound pDirectSound->SetCooperativeLevel failed with code %s\n.",get_DSERR_text(hr) ));
|
|
pDirectSound = NULL;
|
|
return -1;
|
|
}
|
|
|
|
// Create the primary buffer
|
|
ZeroMemory(&BufferDesc, sizeof(BufferDesc));
|
|
BufferDesc.dwSize = sizeof(BufferDesc);
|
|
|
|
ds_get_soundcard_caps(&Soundcard_caps);
|
|
|
|
if (Ds_use_ds3d) {
|
|
BufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRL3D;
|
|
|
|
hr = pDirectSound->CreateSoundBuffer(&BufferDesc, &pPrimaryBuffer, 0);
|
|
if (hr != DS_OK) {
|
|
nprintf(("Sound","SOUND ==> Primary Buffer create failed with DSBCAPS_CTRL3D property... disabling DirectSound3D\n"));
|
|
Ds_use_ds3d = 0;
|
|
Ds_use_eax = 0;
|
|
} else {
|
|
nprintf(("Sound","SOUND ==> Primary Buffer created with DirectSound3D enabled\n"));
|
|
}
|
|
}
|
|
|
|
// If not using DirectSound3D, then create a normal primary buffer
|
|
if (Ds_use_ds3d == 0) {
|
|
BufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
|
hr = pDirectSound->CreateSoundBuffer(&BufferDesc, &pPrimaryBuffer, 0);
|
|
if (hr != DS_OK) {
|
|
nprintf(("Sound","SOUND ==> Primary Buffer create failed with error: %s\n",get_DSERR_text(hr) ));
|
|
pDirectSound = NULL;
|
|
return -1;
|
|
}
|
|
else {
|
|
nprintf(("Sound","SOUND ==> Primary Buffer created with without DirectSound3D enabled\n"));
|
|
}
|
|
}
|
|
|
|
// Get the primary buffer format
|
|
ds_get_primary_format(&wave_format, sample_rate, sample_bits);
|
|
|
|
hr = pPrimaryBuffer->SetFormat(&wave_format);
|
|
if (hr != DS_OK) {
|
|
nprintf(("Sound","SOUND ==> pPrimaryBuffer->SetFormat() failed with code %s\n",get_DSERR_text(hr) ));
|
|
}
|
|
|
|
pPrimaryBuffer->GetFormat(&wave_format, sizeof(wave_format), NULL);
|
|
nprintf(("Sound","SOUND ==> Primary Buffer forced to: rate: %d Hz bits: %d n_channels: %d\n",
|
|
wave_format.nSamplesPerSec, wave_format.wBitsPerSample, wave_format.nChannels));
|
|
|
|
// start the primary buffer playing. This will reduce sound latency when playing a sound
|
|
// if no other sounds are playing.
|
|
hr = pPrimaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
|
if (hr != DS_OK) {
|
|
nprintf(("Sound","SOUND ==> pPrimaryBuffer->Play() failed with code %s\n",get_DSERR_text(hr) ));
|
|
}
|
|
|
|
// Initialize DirectSound3D. Since software performance of DirectSound3D is unacceptably
|
|
// slow, we require the voice manger (a DirectSound extension) to be present. The
|
|
// exception is when A3D is being used, since A3D has a resource manager built in.
|
|
if (Ds_use_ds3d) {
|
|
int vm_required = 1; // voice manager
|
|
|
|
if (ds3d_init(vm_required) != 0) {
|
|
Ds_use_ds3d = 0;
|
|
//Ds_use_eax = 0;
|
|
}
|
|
}
|
|
|
|
if (Ds_use_eax == 1) {
|
|
ds_init_property_set(sample_rate, sample_bits);
|
|
if (ds_eax_init() != 0) {
|
|
Ds_use_eax = 0;
|
|
}
|
|
}
|
|
|
|
ds_build_vol_lookup();
|
|
ds_init_channels();
|
|
ds_init_buffers();
|
|
|
|
ds_show_caps(&Soundcard_caps);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// get_DSERR_text()
|
|
//
|
|
// returns the text equivalent for the a DirectSound DSERR_ code
|
|
//
|
|
char *get_DSERR_text(int DSResult)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
STUB_FUNCTION;
|
|
|
|
return "unknown";
|
|
#else
|
|
switch( DSResult ) {
|
|
|
|
case DS_OK:
|
|
return "DS_OK";
|
|
break;
|
|
|
|
case DSERR_ALLOCATED:
|
|
return "DSERR_ALLOCATED";
|
|
break;
|
|
|
|
case DSERR_ALREADYINITIALIZED:
|
|
return "DSERR_ALREADYINITIALIZED";
|
|
break;
|
|
|
|
case DSERR_BADFORMAT:
|
|
return "DSERR_BADFORMAT";
|
|
break;
|
|
|
|
case DSERR_BUFFERLOST:
|
|
return "DSERR_BUFFERLOST";
|
|
break;
|
|
|
|
case DSERR_CONTROLUNAVAIL:
|
|
return "DSERR_CONTROLUNAVAIL";
|
|
break;
|
|
|
|
case DSERR_GENERIC:
|
|
return "DSERR_GENERIC";
|
|
break;
|
|
|
|
case DSERR_INVALIDCALL:
|
|
return "DSERR_INVALIDCALL";
|
|
break;
|
|
|
|
case DSERR_INVALIDPARAM:
|
|
return "DSERR_INVALIDPARAM";
|
|
break;
|
|
|
|
case DSERR_NOAGGREGATION:
|
|
return "DSERR_NOAGGREGATION";
|
|
break;
|
|
|
|
case DSERR_NODRIVER:
|
|
return "DSERR_NODRIVER";
|
|
break;
|
|
|
|
case DSERR_OUTOFMEMORY:
|
|
return "DSERR_OUTOFMEMORY";
|
|
break;
|
|
|
|
case DSERR_OTHERAPPHASPRIO:
|
|
return "DSERR_OTHERAPPHASPRIO";
|
|
break;
|
|
|
|
case DSERR_PRIOLEVELNEEDED:
|
|
return "DSERR_PRIOLEVELNEEDED";
|
|
break;
|
|
|
|
case DSERR_UNINITIALIZED:
|
|
return "DSERR_UNINITIALIZED";
|
|
break;
|
|
|
|
case DSERR_UNSUPPORTED:
|
|
return "DSERR_UNSUPPORTED";
|
|
break;
|
|
|
|
default:
|
|
return "unknown";
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_close_channel()
|
|
//
|
|
// Free a single channel
|
|
//
|
|
void ds_close_channel(int i)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
if(Channels[i].source_id != 0 && alIsSource (Channels[i].source_id)) {
|
|
OpenAL_ErrorPrint( alSourceStop (Channels[i].source_id) );
|
|
|
|
OpenAL_ErrorPrint( alDeleteSources(1, &Channels[i].source_id) );
|
|
|
|
Channels[i].source_id = 0;
|
|
}
|
|
|
|
return;
|
|
#else
|
|
HRESULT hr;
|
|
|
|
// If a 3D interface exists, free it
|
|
if ( Channels[i].pds3db != NULL ) {
|
|
|
|
{
|
|
int attempts = 0;
|
|
while(++attempts < 10) {
|
|
hr = Channels[i].pds3db->Release();
|
|
if ( hr == DS_OK ) {
|
|
break;
|
|
} else {
|
|
// nprintf(("Sound", "SOUND ==> Channels[channel].pds3db->Release() failed with return value %s\n", get_DSERR_text(second_hr) ));
|
|
}
|
|
}
|
|
|
|
Channels[i].pds3db = NULL;
|
|
}
|
|
}
|
|
|
|
if ( Channels[i].pdsb != NULL ) {
|
|
// If a 2D interface exists, free it
|
|
if ( Channels[i].pdsb != NULL ) {
|
|
int attempts = 0;
|
|
while(++attempts < 10) {
|
|
hr = Channels[i].pdsb->Release();
|
|
if ( hr == DS_OK ) {
|
|
break;
|
|
} else {
|
|
nprintf(("Sound", "SOUND ==> Channels[channel].pdsb->Release() failed with return value %s\n", get_DSERR_text(hr) ));
|
|
}
|
|
}
|
|
}
|
|
|
|
Channels[i].pdsb = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_close_all_channels()
|
|
//
|
|
// Free all the channel buffers
|
|
//
|
|
void ds_close_all_channels()
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
ds_close_channel(i);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_unload_buffer()
|
|
//
|
|
//
|
|
void ds_unload_buffer(int sid, int hid)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
if (sid != -1) {
|
|
ALuint buf_id = sound_buffers[sid].buf_id;
|
|
|
|
if (buf_id != 0 && alIsBuffer(buf_id)) {
|
|
OpenAL_ErrorPrint( alDeleteBuffers(1, &buf_id) );
|
|
}
|
|
|
|
sound_buffers[sid].buf_id = 0;
|
|
}
|
|
|
|
/* hid unused */
|
|
|
|
return;
|
|
#else
|
|
HRESULT hr;
|
|
|
|
if ( sid != -1 ) {
|
|
if ( ds_software_buffers[sid].pdsb != NULL ) {
|
|
hr = ds_software_buffers[sid].pdsb->Release();
|
|
if ( hr != DS_OK ) {
|
|
Int3();
|
|
nprintf(("Sound", "SOUND ==> ds_software_buffers[sid]->Release() failed with return value %s\n", get_DSERR_text(hr) ));
|
|
}
|
|
ds_software_buffers[sid].pdsb = NULL;
|
|
}
|
|
}
|
|
|
|
if ( hid != -1 ) {
|
|
if ( ds_hardware_buffers[hid].pdsb != NULL ) {
|
|
hr = ds_hardware_buffers[hid].pdsb->Release();
|
|
if ( hr != DS_OK ) {
|
|
Int3();
|
|
nprintf(("Sound", "SOUND ==> ds_hardware_buffers[hid]->Release() failed with return value %s\n", get_DSERR_text(hr) ));
|
|
}
|
|
ds_hardware_buffers[hid].pdsb = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_close_software_buffers()
|
|
//
|
|
//
|
|
void ds_close_software_buffers()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_DS_SOFTWARE_BUFFERS; i++) {
|
|
ALuint buf_id = sound_buffers[i].buf_id;
|
|
|
|
if (buf_id != 0 && alIsBuffer(buf_id)) {
|
|
OpenAL_ErrorPrint( alDeleteBuffers(1, &buf_id) );
|
|
}
|
|
|
|
sound_buffers[i].buf_id = 0;
|
|
}
|
|
#else
|
|
int i;
|
|
HRESULT hr;
|
|
|
|
for (i = 0; i < MAX_DS_SOFTWARE_BUFFERS; i++) {
|
|
if ( ds_software_buffers[i].pdsb != NULL ) {
|
|
hr = ds_software_buffers[i].pdsb->Release();
|
|
if ( hr != DS_OK ) {
|
|
Int3();
|
|
nprintf(("Sound", "SOUND ==> ds_software_buffers[i]->Release() failed with return value %s\n", get_DSERR_text(hr) ));
|
|
}
|
|
ds_software_buffers[i].pdsb = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_close_hardware_buffers()
|
|
//
|
|
//
|
|
void ds_close_hardware_buffers()
|
|
{
|
|
#ifndef USE_OPENAL
|
|
int i;
|
|
HRESULT hr;
|
|
|
|
for (i = 0; i < MAX_DS_HARDWARE_BUFFERS; i++) {
|
|
if ( ds_hardware_buffers[i].pdsb != NULL ) {
|
|
hr = ds_hardware_buffers[i].pdsb->Release();
|
|
if ( hr != DS_OK ) {
|
|
Int3();
|
|
nprintf(("Sound", "SOUND ==> ds_hardware_buffers[i]->Release() failed with return value %s\n", get_DSERR_text(hr) ));
|
|
}
|
|
ds_hardware_buffers[i].pdsb = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_close_buffers()
|
|
//
|
|
// Free the channel buffers
|
|
//
|
|
void ds_close_buffers()
|
|
{
|
|
ds_close_software_buffers();
|
|
ds_close_hardware_buffers();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_close()
|
|
//
|
|
// Close the DirectSound system
|
|
//
|
|
void ds_close()
|
|
{
|
|
ds_close_all_channels();
|
|
ds_close_buffers();
|
|
|
|
#ifndef USE_OPENAL
|
|
if (pPropertySet != NULL) {
|
|
pPropertySet->Release();
|
|
pPropertySet = NULL;
|
|
}
|
|
|
|
if (Ds_property_set_pdsb != NULL) {
|
|
Ds_property_set_pdsb->Release();
|
|
Ds_property_set_pdsb = NULL;
|
|
}
|
|
|
|
if (Ds_property_set_pds3db != NULL) {
|
|
Ds_property_set_pds3db->Release();
|
|
Ds_property_set_pds3db = NULL;
|
|
}
|
|
|
|
if (pPrimaryBuffer) {
|
|
pPrimaryBuffer->Release();
|
|
pPrimaryBuffer = NULL;
|
|
}
|
|
|
|
if (pDirectSound) {
|
|
pDirectSound->Release();
|
|
pDirectSound = NULL;
|
|
}
|
|
|
|
if ( Ds_dll_loaded ) {
|
|
FreeLibrary(Ds_dll_handle);
|
|
Ds_dll_loaded=0;
|
|
}
|
|
|
|
if (Ds_must_call_couninitialize == 1) {
|
|
CoUninitialize();
|
|
}
|
|
#endif
|
|
// free the Channels[] array, since it was dynamically allocated
|
|
vm_free(Channels);
|
|
Channels = NULL;
|
|
|
|
#ifdef USE_OPENAL
|
|
alcMakeContextCurrent(NULL); // hangs on me for some reason
|
|
|
|
if (ds_sound_context != NULL)
|
|
alcDestroyContext(ds_sound_context);
|
|
|
|
if (ds_sound_device != NULL)
|
|
alcCloseDevice(ds_sound_device);
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_get_3d_interface()
|
|
//
|
|
// Get the 3d interface for a secondary buffer.
|
|
//
|
|
// If the secondary buffer wasn't created with a DSBCAPS_CTRL3D flag, then no 3d interface
|
|
// exists
|
|
//
|
|
#ifndef USE_OPENAL
|
|
void ds_get_3d_interface(LPDIRECTSOUNDBUFFER pdsb, LPDIRECTSOUND3DBUFFER *ppds3db)
|
|
{
|
|
DSBCAPS dsbc;
|
|
HRESULT DSResult;
|
|
|
|
dsbc.dwSize = sizeof(dsbc);
|
|
DSResult = pdsb->GetCaps(&dsbc);
|
|
if ( DSResult == DS_OK && dsbc.dwFlags & DSBCAPS_CTRL3D ) {
|
|
DSResult = pdsb->QueryInterface( IID_IDirectSound3DBuffer, (void**)ppds3db );
|
|
if ( DSResult != DS_OK ) {
|
|
nprintf(("SOUND","Could not obtain 3D interface for hardware buffer: %s\n", get_DSERR_text(DSResult) ));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_get_free_channel()
|
|
//
|
|
// Find a free channel to play a sound on. If no free channels exists, free up one based
|
|
// on volume levels.
|
|
//
|
|
// input: new_volume => volume in DS units for sound to play at
|
|
// snd_id => which kind of sound to play
|
|
// priority => DS_MUST_PLAY
|
|
// DS_LIMIT_ONE
|
|
// DS_LIMIT_TWO
|
|
// DS_LIMIT_THREE
|
|
//
|
|
// returns: channel number to play sound on
|
|
// -1 if no channel could be found
|
|
//
|
|
// NOTE: snd_id is needed since we limit the number of concurrent samples
|
|
//
|
|
//
|
|
#define DS_MAX_SOUND_INSTANCES 2
|
|
|
|
int ds_get_free_channel(int new_volume, int snd_id, int priority)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int i, first_free_channel, limit;
|
|
int lowest_vol = 0, lowest_vol_index = -1;
|
|
int instance_count; // number of instances of sound already playing
|
|
int lowest_instance_vol, lowest_instance_vol_index;
|
|
channel *chp;
|
|
int status;
|
|
|
|
instance_count = 0;
|
|
lowest_instance_vol = 99;
|
|
lowest_instance_vol_index = -1;
|
|
first_free_channel = -1;
|
|
|
|
// Look for a channel to use to play this sample
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
chp = &Channels[i];
|
|
|
|
if ( chp->source_id == 0 ) {
|
|
if ( first_free_channel == -1 )
|
|
first_free_channel = i;
|
|
|
|
continue;
|
|
}
|
|
|
|
OpenAL_ErrorCheck( alGetSourcei(chp->source_id, AL_SOURCE_STATE, &status), continue );
|
|
|
|
if ( status != AL_PLAYING ) {
|
|
if ( first_free_channel == -1 )
|
|
first_free_channel = i;
|
|
|
|
continue;
|
|
}
|
|
else {
|
|
if ( chp->snd_id == snd_id ) {
|
|
instance_count++;
|
|
if ( chp->vol < lowest_instance_vol && chp->looping == FALSE ) {
|
|
lowest_instance_vol = chp->vol;
|
|
lowest_instance_vol_index = i;
|
|
}
|
|
}
|
|
|
|
if ( chp->vol < lowest_vol && chp->looping == FALSE ) {
|
|
lowest_vol_index = i;
|
|
lowest_vol = chp->vol;
|
|
}
|
|
}
|
|
}
|
|
|
|
// determine the limit of concurrent instances of this sound
|
|
switch(priority) {
|
|
case DS_MUST_PLAY:
|
|
limit = 100;
|
|
break;
|
|
case DS_LIMIT_ONE:
|
|
limit = 1;
|
|
break;
|
|
case DS_LIMIT_TWO:
|
|
limit = 2;
|
|
break;
|
|
case DS_LIMIT_THREE:
|
|
limit = 3;
|
|
break;
|
|
default:
|
|
Int3(); // get Alan
|
|
limit = 100;
|
|
break;
|
|
}
|
|
|
|
|
|
// If we've exceeded the limit, then maybe stop the duplicate if it is lower volume
|
|
if ( instance_count >= limit ) {
|
|
// If there is a lower volume duplicate, stop it.... otherwise, don't play the sound
|
|
if ( lowest_instance_vol_index >= 0 && (Channels[lowest_instance_vol_index].vol <= new_volume) ) {
|
|
first_free_channel = lowest_instance_vol_index;
|
|
} else {
|
|
first_free_channel = -1;
|
|
}
|
|
} else {
|
|
// there is no limit barrier to play the sound, so see if we've ran out of channels
|
|
if ( first_free_channel == -1 ) {
|
|
// stop the lowest volume instance to play our sound if priority demands it
|
|
if ( lowest_vol_index != -1 && priority == DS_MUST_PLAY ) {
|
|
// Check if the lowest volume playing is less than the volume of the requested sound.
|
|
// If so, then we are going to trash the lowest volume sound.
|
|
if ( Channels[lowest_vol_index].vol <= new_volume ) {
|
|
first_free_channel = lowest_vol_index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return first_free_channel;
|
|
#else
|
|
int i, first_free_channel, limit;
|
|
int lowest_vol = 0, lowest_vol_index = -1;
|
|
int instance_count; // number of instances of sound already playing
|
|
int lowest_instance_vol, lowest_instance_vol_index;
|
|
unsigned long status;
|
|
HRESULT hr;
|
|
channel *chp;
|
|
|
|
instance_count = 0;
|
|
lowest_instance_vol = 99;
|
|
lowest_instance_vol_index = -1;
|
|
first_free_channel = -1;
|
|
|
|
// Look for a channel to use to play this sample
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
chp = &Channels[i];
|
|
if ( chp->pdsb == NULL ) {
|
|
if ( first_free_channel == -1 )
|
|
first_free_channel = i;
|
|
continue;
|
|
}
|
|
|
|
hr = chp->pdsb->GetStatus(&status);
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> GetStatus failed with return value %s\n", get_DSERR_text(hr) ));
|
|
return -1;
|
|
}
|
|
if ( !(status & DSBSTATUS_PLAYING) ) {
|
|
if ( first_free_channel == -1 )
|
|
first_free_channel = i;
|
|
ds_close_channel(i);
|
|
continue;
|
|
}
|
|
else {
|
|
if ( chp->snd_id == snd_id ) {
|
|
instance_count++;
|
|
if ( chp->vol < lowest_instance_vol && chp->looping == FALSE ) {
|
|
lowest_instance_vol = chp->vol;
|
|
lowest_instance_vol_index = i;
|
|
}
|
|
}
|
|
|
|
if ( chp->vol < lowest_vol && chp->looping == FALSE ) {
|
|
lowest_vol_index = i;
|
|
lowest_vol = chp->vol;
|
|
}
|
|
}
|
|
}
|
|
|
|
// determine the limit of concurrent instances of this sound
|
|
switch(priority) {
|
|
case DS_MUST_PLAY:
|
|
limit = 100;
|
|
break;
|
|
case DS_LIMIT_ONE:
|
|
limit = 1;
|
|
break;
|
|
case DS_LIMIT_TWO:
|
|
limit = 2;
|
|
break;
|
|
case DS_LIMIT_THREE:
|
|
limit = 3;
|
|
break;
|
|
default:
|
|
Int3(); // get Alan
|
|
limit = 100;
|
|
break;
|
|
}
|
|
|
|
|
|
// If we've exceeded the limit, then maybe stop the duplicate if it is lower volume
|
|
if ( instance_count >= limit ) {
|
|
// If there is a lower volume duplicate, stop it.... otherwise, don't play the sound
|
|
if ( lowest_instance_vol_index >= 0 && (Channels[lowest_instance_vol_index].vol <= new_volume) ) {
|
|
ds_close_channel(lowest_instance_vol_index);
|
|
first_free_channel = lowest_instance_vol_index;
|
|
} else {
|
|
first_free_channel = -1;
|
|
}
|
|
} else {
|
|
// there is no limit barrier to play the sound, so see if we've ran out of channels
|
|
if ( first_free_channel == -1 ) {
|
|
// stop the lowest volume instance to play our sound if priority demands it
|
|
if ( lowest_vol_index != -1 && priority == DS_MUST_PLAY ) {
|
|
// Check if the lowest volume playing is less than the volume of the requested sound.
|
|
// If so, then we are going to trash the lowest volume sound.
|
|
if ( Channels[lowest_vol_index].vol <= new_volume ) {
|
|
ds_close_channel(lowest_vol_index);
|
|
first_free_channel = lowest_vol_index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return first_free_channel;
|
|
#endif
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_channel_dup()
|
|
//
|
|
// Find a free channel to play a sound on. If no free channels exists, free up one based
|
|
// on volume levels.
|
|
//
|
|
// returns: 0 => dup was successful
|
|
// -1 => dup failed (Channels[channel].pdsb will be NULL)
|
|
//
|
|
#ifndef USE_OPENAL
|
|
int ds_channel_dup(LPDIRECTSOUNDBUFFER pdsb, int channel, int use_ds3d)
|
|
{
|
|
HRESULT DSResult;
|
|
|
|
// Duplicate the master buffer into a channel buffer.
|
|
DSResult = pDirectSound->DuplicateSoundBuffer(pdsb, &Channels[channel].pdsb );
|
|
if ( DSResult != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> DuplicateSoundBuffer failed with return value %s\n", get_DSERR_text(DSResult) ));
|
|
Channels[channel].pdsb = NULL;
|
|
return -1;
|
|
}
|
|
|
|
// get the 3d interface for the buffer if it exists
|
|
if ( use_ds3d ) {
|
|
if (Channels[channel].pds3db == NULL) {
|
|
ds_get_3d_interface(Channels[channel].pdsb, &Channels[channel].pds3db);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_restore_buffer()
|
|
//
|
|
//
|
|
void ds_restore_buffer(LPDIRECTSOUNDBUFFER pdsb)
|
|
{
|
|
HRESULT hr;
|
|
|
|
Int3(); // get Alan, he wants to see this
|
|
hr = pdsb->Restore();
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "Sound ==> Lost a buffer, tried restoring but got %s\n", get_DSERR_text(hr) ));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Create a direct sound buffer in software, without locking any data in
|
|
int ds_create_buffer(int frequency, int bits_per_sample, int nchannels, int nseconds)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
ALuint i;
|
|
int sid;
|
|
|
|
if (!ds_initialized) {
|
|
return -1;
|
|
}
|
|
|
|
sid = ds_get_sid();
|
|
if ( sid == -1 ) {
|
|
nprintf(("Sound","SOUND ==> No more OpenAL buffers available\n"));
|
|
return -1;
|
|
}
|
|
|
|
OpenAL_ErrorCheck( alGenBuffers (1, &i), return -1 );
|
|
|
|
sound_buffers[sid].buf_id = i;
|
|
sound_buffers[sid].source_id = -1;
|
|
sound_buffers[sid].frequency = frequency;
|
|
sound_buffers[sid].bits_per_sample = bits_per_sample;
|
|
sound_buffers[sid].nchannels = nchannels;
|
|
sound_buffers[sid].nseconds = nseconds;
|
|
sound_buffers[sid].nbytes = nseconds * (bits_per_sample / 8) * nchannels * frequency;
|
|
|
|
return sid;
|
|
#else
|
|
HRESULT dsrval;
|
|
DSBUFFERDESC dsbd;
|
|
WAVEFORMATEX wfx;
|
|
int sid;
|
|
|
|
if (!ds_initialized) {
|
|
return -1;
|
|
}
|
|
|
|
sid = ds_get_sid();
|
|
if ( sid == -1 ) {
|
|
nprintf(("Sound","SOUND ==> No more software secondary buffers available\n"));
|
|
return -1;
|
|
}
|
|
|
|
// Set up buffer format
|
|
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.nChannels = (unsigned short)nchannels;
|
|
wfx.nSamplesPerSec = frequency;
|
|
wfx.wBitsPerSample = (unsigned short)bits_per_sample;
|
|
wfx.cbSize = 0;
|
|
wfx.nBlockAlign = (unsigned short)(wfx.nChannels * (wfx.wBitsPerSample / 8));
|
|
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
|
|
|
|
memset(&dsbd, 0, sizeof(DSBUFFERDESC));
|
|
dsbd.dwSize = sizeof(DSBUFFERDESC);
|
|
dsbd.dwBufferBytes = wfx.nAvgBytesPerSec * nseconds;
|
|
dsbd.lpwfxFormat = &wfx;
|
|
dsbd.dwFlags = DSBCAPS_STATIC | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLDEFAULT;
|
|
|
|
dsrval = pDirectSound->CreateSoundBuffer(&dsbd, &ds_software_buffers[sid].pdsb, NULL);
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
ds_software_buffers[sid].desc = dsbd;
|
|
return sid;
|
|
#endif
|
|
}
|
|
|
|
// Lock data into an existing buffer
|
|
int ds_lock_data(int sid, unsigned char *data, int size)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
Assert(sid >= 0);
|
|
|
|
ALuint buf_id = sound_buffers[sid].buf_id;
|
|
ALenum format;
|
|
|
|
if (sound_buffers[sid].bits_per_sample == 16) {
|
|
if (sound_buffers[sid].nchannels == 2) {
|
|
format = AL_FORMAT_STEREO16;
|
|
} else if (sound_buffers[sid].nchannels == 1) {
|
|
format = AL_FORMAT_MONO16;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (sound_buffers[sid].bits_per_sample == 8) {
|
|
if (sound_buffers[sid].nchannels == 2) {
|
|
format = AL_FORMAT_STEREO8;
|
|
} else if (sound_buffers[sid].nchannels == 1) {
|
|
format = AL_FORMAT_MONO8;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
sound_buffers[sid].nbytes = size;
|
|
|
|
OpenAL_ErrorCheck( alBufferData(buf_id, format, data, size, sound_buffers[sid].frequency), return -1 );
|
|
|
|
return 0;
|
|
#else
|
|
HRESULT dsrval;
|
|
LPDIRECTSOUNDBUFFER pdsb;
|
|
DSBCAPS caps;
|
|
void *buffer_data, *buffer_data2;
|
|
DWORD buffer_size, buffer_size2;
|
|
|
|
Assert(sid >= 0);
|
|
pdsb = ds_software_buffers[sid].pdsb;
|
|
|
|
memset(&caps, 0, sizeof(DSBCAPS));
|
|
caps.dwSize = sizeof(DSBCAPS);
|
|
dsrval = pdsb->GetCaps(&caps);
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
pdsb->SetCurrentPosition(0);
|
|
|
|
// lock the entire buffer
|
|
dsrval = pdsb->Lock(0, caps.dwBufferBytes, &buffer_data, &buffer_size, &buffer_data2, &buffer_size2, 0 );
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
// first clear it out with silence
|
|
memset(buffer_data, 0x80, buffer_size);
|
|
memcpy(buffer_data, data, size);
|
|
|
|
dsrval = pdsb->Unlock(buffer_data, buffer_size, 0, 0);
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
// Stop a buffer from playing directly
|
|
void ds_stop_easy(int sid)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
Assert(sid >= 0);
|
|
|
|
int cid = sound_buffers[sid].source_id;
|
|
|
|
if (cid != -1) {
|
|
ALuint source_id = Channels[cid].source_id;
|
|
|
|
OpenAL_ErrorPrint( alSourceStop(source_id) );
|
|
}
|
|
#else
|
|
HRESULT dsrval;
|
|
LPDIRECTSOUNDBUFFER pdsb;
|
|
|
|
Assert(sid >= 0);
|
|
pdsb = ds_software_buffers[sid].pdsb;
|
|
dsrval = pdsb->Stop();
|
|
#endif
|
|
}
|
|
|
|
// Play a sound without the usual baggage (used for playing back real-time voice)
|
|
//
|
|
// parameters:
|
|
// sid => software id of sound
|
|
// volume => volume of sound effect in DirectSound units
|
|
int ds_play_easy(int sid, int volume)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
if (!ds_initialized)
|
|
return -1;
|
|
|
|
int channel = ds_get_free_channel(volume, -1, DS_MUST_PLAY);
|
|
|
|
if (channel > -1) {
|
|
ALuint source_id = Channels[channel].source_id;
|
|
|
|
OpenAL_ErrorPrint( alSourceStop(source_id) );
|
|
|
|
if (Channels[channel].buf_id != sid) {
|
|
ALuint buffer_id = sound_buffers[sid].buf_id;
|
|
|
|
OpenAL_ErrorCheck( alSourcei(source_id, AL_BUFFER, buffer_id), return -1 );
|
|
}
|
|
|
|
Channels[channel].buf_id = sid;
|
|
|
|
ALfloat alvol = (volume != -10000) ? powf(10.0f, (float)volume / (-600.0f / log10f(.5f))): 0.0f;
|
|
|
|
OpenAL_ErrorPrint( alSourcef(source_id, AL_GAIN, alvol) );
|
|
|
|
OpenAL_ErrorPrint( alSourcei(source_id, AL_LOOPING, AL_FALSE) );
|
|
|
|
OpenAL_ErrorPrint( alSourcePlay(source_id) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
#else
|
|
HRESULT dsrval;
|
|
LPDIRECTSOUNDBUFFER pdsb;
|
|
|
|
Assert(sid >= 0);
|
|
pdsb = ds_software_buffers[sid].pdsb;
|
|
|
|
pdsb->SetVolume(volume);
|
|
dsrval=pdsb->Play(0, 0, 0);
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
//extern void HUD_add_to_scrollback(char *text, int source);
|
|
//extern void HUD_printf(char *format, ...);
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// Play a DirectSound secondary buffer.
|
|
//
|
|
//
|
|
// parameters:
|
|
// sid => software id of sound
|
|
// hid => hardware id of sound ( -1 if not in hardware )
|
|
// snd_id => what kind of sound this is
|
|
// priority => DS_MUST_PLAY
|
|
// DS_LIMIT_ONE
|
|
// DS_LIMIT_TWO
|
|
// DS_LIMIT_THREE
|
|
// volume => volume of sound effect in DirectSound units
|
|
// pan => pan of sound in DirectSound units
|
|
// looping => whether the sound effect is looping or not
|
|
//
|
|
// returns: -1 => sound effect could not be started
|
|
// >=0 => sig for sound effect successfully started
|
|
//
|
|
int ds_play(int sid, int hid, int snd_id, int priority, int volume, int pan, int looping, bool is_voice_msg)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int channel;
|
|
|
|
if (!ds_initialized)
|
|
return -1;
|
|
|
|
channel = ds_get_free_channel(volume, snd_id, priority);
|
|
|
|
if (channel > -1) {
|
|
if ( Channels[channel].source_id == 0 ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( ds_using_ds3d() ) {
|
|
}
|
|
|
|
// Actually play it
|
|
Channels[channel].vol = volume;
|
|
Channels[channel].looping = looping;
|
|
Channels[channel].priority = priority;
|
|
|
|
// set new position for pan or zero out if none
|
|
ALfloat alpan = (float)pan / MAX_PAN;
|
|
|
|
if ( alpan ) {
|
|
OpenAL_ErrorPrint( alSource3f(Channels[channel].source_id, AL_POSITION, alpan, 0.0, 1.0) );
|
|
} else {
|
|
OpenAL_ErrorPrint( alSource3f(Channels[channel].source_id, AL_POSITION, 0.0, 0.0, 0.0) );
|
|
}
|
|
|
|
OpenAL_ErrorPrint( alSource3f(Channels[channel].source_id, AL_VELOCITY, 0.0, 0.0, 0.0) );
|
|
|
|
OpenAL_ErrorPrint( alSourcef(Channels[channel].source_id, AL_PITCH, 1.0) );
|
|
|
|
ALfloat alvol = (volume != -10000) ? powf(10.0f, (float)volume / (-600.0f / log10f(.5f))): 0.0f;
|
|
OpenAL_ErrorPrint( alSourcef(Channels[channel].source_id, AL_GAIN, alvol) );
|
|
|
|
Channels[channel].is_voice_msg = is_voice_msg;
|
|
|
|
|
|
ALint status;
|
|
OpenAL_ErrorCheck( alGetSourcei(Channels[channel].source_id, AL_SOURCE_STATE, &status), return -1 );
|
|
|
|
if (status == AL_PLAYING)
|
|
OpenAL_ErrorPrint( alSourceStop(Channels[channel].source_id) );
|
|
|
|
|
|
OpenAL_ErrorCheck( alSourcei(Channels[channel].source_id, AL_BUFFER, sound_buffers[sid].buf_id), return -1 );
|
|
|
|
|
|
// setup default listener position/orientation
|
|
// this is needed for 2D pan
|
|
OpenAL_ErrorPrint( alListener3f(AL_POSITION, 0.0, 0.0, 0.0) );
|
|
|
|
ALfloat list_orien[] = { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f };
|
|
OpenAL_ErrorPrint( alListenerfv(AL_ORIENTATION, list_orien) );
|
|
|
|
OpenAL_ErrorPrint( alSourcei(Channels[channel].source_id, AL_SOURCE_RELATIVE, AL_FALSE) );
|
|
|
|
OpenAL_ErrorPrint( alSourcei(Channels[channel].source_id, AL_LOOPING, (looping) ? AL_TRUE : AL_FALSE) );
|
|
|
|
OpenAL_ErrorPrint( alSourcePlay(Channels[channel].source_id) );
|
|
|
|
sound_buffers[sid].source_id = channel;
|
|
Channels[channel].buf_id = sid;
|
|
}
|
|
else {
|
|
// nprintf(( "Sound", "SOUND ==> Not playing sound requested at volume %.2f\n", ds_get_percentage_vol(volume) ));
|
|
return -1;
|
|
}
|
|
|
|
Channels[channel].snd_id = snd_id;
|
|
Channels[channel].sig = channel_next_sig++;
|
|
if (channel_next_sig < 0 ) {
|
|
channel_next_sig = 1;
|
|
}
|
|
|
|
Channels[channel].last_position = 0;
|
|
|
|
// make sure there aren't any looping voice messages
|
|
for (int i=0; i<MAX_CHANNELS; i++) {
|
|
if (Channels[i].is_voice_msg == true) {
|
|
if (Channels[i].source_id == 0) {
|
|
continue;
|
|
}
|
|
|
|
DWORD current_position = ds_get_play_position(i);
|
|
if (current_position != 0) {
|
|
if (current_position < Channels[i].last_position) {
|
|
ds_stop_channel(i);
|
|
} else {
|
|
Channels[i].last_position = current_position;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Channels[channel].sig;
|
|
#else
|
|
int channel;
|
|
HRESULT DSResult;
|
|
|
|
if (!ds_initialized)
|
|
return -1;
|
|
|
|
channel = ds_get_free_channel(volume, snd_id, priority);
|
|
|
|
if (channel > -1) {
|
|
if ( Channels[channel].pdsb != NULL ) {
|
|
return -1;
|
|
}
|
|
|
|
// First check if the sound is in hardware, and try to duplicate from there
|
|
if ( hid != -1 ) {
|
|
Int3();
|
|
if ( ds_channel_dup(ds_hardware_buffers[hid].pdsb, channel, 0) == 0 ) {
|
|
// nprintf(("Sound", "SOUND ==> Played sound in hardware..\n"));
|
|
}
|
|
}
|
|
|
|
// Channel will be NULL if hardware dup failed, or there was no hardware dup attempted
|
|
if ( Channels[channel].pdsb == NULL ) {
|
|
if ( ds_channel_dup(ds_software_buffers[sid].pdsb, channel, 0) == 0 ) {
|
|
// nprintf(("Sound", "SOUND ==> Played sound in software..\n"));
|
|
}
|
|
}
|
|
|
|
if ( Channels[channel].pdsb == NULL ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( ds_using_ds3d() ) {
|
|
if ( ds_is_3d_buffer(Channels[channel].pdsb) ) {
|
|
if (Channels[channel].pds3db == NULL) {
|
|
ds_get_3d_interface(Channels[channel].pdsb, &Channels[channel].pds3db);
|
|
}
|
|
if ( Channels[channel].pds3db ) {
|
|
Channels[channel].pds3db->SetMode(DS3DMODE_DISABLE,DS3D_IMMEDIATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Actually play it
|
|
Channels[channel].vol = volume;
|
|
Channels[channel].looping = looping;
|
|
Channels[channel].priority = priority;
|
|
Channels[channel].pdsb->SetPan(pan);
|
|
Channels[channel].pdsb->SetVolume(volume);
|
|
Channels[channel].is_voice_msg = is_voice_msg;
|
|
|
|
int ds_flags = 0;
|
|
if ( looping )
|
|
ds_flags |= DSBPLAY_LOOPING;
|
|
|
|
DSResult = Channels[channel].pdsb->Play(0, 0, ds_flags );
|
|
|
|
/*
|
|
if (Stop_logging_sounds == false) {
|
|
char buf[256];
|
|
sprintf(buf, "channel %d, address: %x, ds_flags: %d", channel, Channels[channel].pdsb, ds_flags);
|
|
HUD_add_to_scrollback(buf, 3);
|
|
}
|
|
*/
|
|
|
|
if ( DSResult == DSERR_BUFFERLOST ) {
|
|
ds_restore_buffer(Channels[channel].pdsb);
|
|
DSResult = Channels[channel].pdsb->Play(0, 0, ds_flags );
|
|
}
|
|
|
|
if ( DSResult != DS_OK ) {
|
|
nprintf(("Sound", "Sound ==> Play failed with return value %s\n", get_DSERR_text(DSResult) ));
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
// nprintf(( "Sound", "SOUND ==> Not playing sound requested at volume %.2f\n", ds_get_percentage_vol(volume) ));
|
|
return -1;
|
|
}
|
|
|
|
Channels[channel].snd_id = snd_id;
|
|
Channels[channel].sig = channel_next_sig++;
|
|
if (channel_next_sig < 0 ) {
|
|
channel_next_sig = 1;
|
|
}
|
|
|
|
/*
|
|
if (Stop_logging_sounds == false) {
|
|
if (is_voice_msg) {
|
|
char buf[256];
|
|
sprintf(buf, "VOICE sig: %d, sid: %d, snd_id: %d, ch: %d", Channels[channel].sig, sid, snd_id, channel);
|
|
HUD_add_to_scrollback(buf, 3);
|
|
}
|
|
}
|
|
*/
|
|
|
|
Channels[channel].last_position = 0;
|
|
|
|
// make sure there aren't any looping voice messages
|
|
for (int i=0; i<MAX_CHANNELS; i++) {
|
|
if (Channels[i].is_voice_msg == true) {
|
|
if (Channels[i].pdsb == NULL) {
|
|
continue;
|
|
}
|
|
|
|
DWORD current_position = ds_get_play_position(i);
|
|
if (current_position != 0) {
|
|
if (current_position < Channels[i].last_position) {
|
|
ds_close_channel(i);
|
|
} else {
|
|
Channels[i].last_position = current_position;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Channels[channel].sig;
|
|
#endif
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_get_channel()
|
|
//
|
|
// Return the channel number that is playing the sound identified by sig. If that sound is
|
|
// not playing, return -1.
|
|
//
|
|
int ds_get_channel(int sig)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
if ( Channels[i].source_id && (Channels[i].sig == sig) ) {
|
|
if ( ds_is_channel_playing(i) == TRUE ) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
#else
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
if ( Channels[i].pdsb && Channels[i].sig == sig ) {
|
|
if ( ds_is_channel_playing(i) == TRUE ) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_is_channel_playing()
|
|
//
|
|
//
|
|
int ds_is_channel_playing(int channel)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
if ( Channels[channel].source_id != 0 ) {
|
|
ALint status;
|
|
|
|
OpenAL_ErrorPrint( alGetSourcei(Channels[channel].source_id, AL_SOURCE_STATE, &status) );
|
|
|
|
return (status == AL_PLAYING);
|
|
}
|
|
|
|
return 0;
|
|
#else
|
|
HRESULT hr;
|
|
unsigned long status;
|
|
|
|
if ( !Channels[channel].pdsb ) {
|
|
return 0;
|
|
}
|
|
|
|
hr = Channels[channel].pdsb->GetStatus(&status);
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> GetStatus failed with return value %s\n", get_DSERR_text(hr) ));
|
|
return 0;
|
|
}
|
|
|
|
if ( status & DSBSTATUS_PLAYING )
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_stop_channel()
|
|
//
|
|
//
|
|
void ds_stop_channel(int channel)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
if ( Channels[channel].source_id != 0 ) {
|
|
OpenAL_ErrorPrint( alSourceStop(Channels[channel].source_id) );
|
|
}
|
|
#else
|
|
ds_close_channel(channel);
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_stop_channel_all()
|
|
//
|
|
//
|
|
void ds_stop_channel_all()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int i;
|
|
|
|
for ( i=0; i<MAX_CHANNELS; i++ ) {
|
|
if ( Channels[i].source_id != 0 ) {
|
|
OpenAL_ErrorPrint( alSourceStop(Channels[i].source_id) );
|
|
}
|
|
}
|
|
#else
|
|
int i;
|
|
|
|
for ( i=0; i<MAX_CHANNELS; i++ ) {
|
|
if ( Channels[i].pdsb != NULL ) {
|
|
ds_stop_channel(i);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_set_volume()
|
|
//
|
|
// Set the volume for a channel. The volume is expected to be in DirectSound units
|
|
//
|
|
// If the sound is a 3D sound buffer, this is like re-establishing the maximum
|
|
// volume.
|
|
//
|
|
void ds_set_volume( int channel, int vol )
|
|
{
|
|
#ifdef USE_OPENAL
|
|
ALuint source_id = Channels[channel].source_id;
|
|
|
|
if (source_id != 0) {
|
|
ALfloat alvol = (vol != -10000) ? powf(10.0f, (float)vol / (-600.0f / log10f(.5f))): 0.0f;
|
|
|
|
OpenAL_ErrorPrint( alSourcef(source_id, AL_GAIN, alvol) );
|
|
}
|
|
#else
|
|
HRESULT hr;
|
|
unsigned long status;
|
|
|
|
hr = Channels[channel].pdsb->GetStatus(&status);
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> GetStatus failed with return value %s\n", get_DSERR_text(hr) ));
|
|
return;
|
|
}
|
|
|
|
if ( status & DSBSTATUS_PLAYING ) {
|
|
Channels[channel].pdsb->SetVolume(vol);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_set_pan()
|
|
//
|
|
// Set the pan for a channel. The pan is expected to be in DirectSound units
|
|
//
|
|
void ds_set_pan( int channel, int pan )
|
|
{
|
|
#ifdef USE_OPENAL
|
|
ALint state;
|
|
|
|
OpenAL_ErrorCheck( alGetSourcei(Channels[channel].source_id, AL_SOURCE_STATE, &state), return );
|
|
|
|
if (state == AL_PLAYING) {
|
|
ALfloat alpan = (pan != 0) ? ((float)pan / MAX_PAN) : 0.0f;
|
|
OpenAL_ErrorPrint( alSource3f(Channels[channel].source_id, AL_POSITION, alpan, 0.0, 1.0) );
|
|
}
|
|
#else
|
|
HRESULT hr;
|
|
unsigned long status;
|
|
|
|
hr = Channels[channel].pdsb->GetStatus(&status);
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> GetStatus failed with return value %s\n", get_DSERR_text(hr) ));
|
|
return;
|
|
}
|
|
|
|
if ( status & DSBSTATUS_PLAYING ) {
|
|
Channels[channel].pdsb->SetPan(pan);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_get_pitch()
|
|
//
|
|
// Get the pitch of a channel
|
|
//
|
|
int ds_get_pitch(int channel)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
ALint status;
|
|
ALfloat alpitch = 0;
|
|
int pitch;
|
|
|
|
OpenAL_ErrorCheck( alGetSourcei(Channels[channel].source_id, AL_SOURCE_STATE, &status), return -1 );
|
|
|
|
if (status == AL_PLAYING)
|
|
OpenAL_ErrorPrint( alGetSourcef(Channels[channel].source_id, AL_PITCH, &alpitch) );
|
|
|
|
// convert OpenAL values to DirectSound values and return
|
|
pitch = fl2i( pow(10.0, (alpitch + 2.0)) );
|
|
|
|
return pitch;
|
|
#else
|
|
unsigned long status, pitch = 0;
|
|
HRESULT hr;
|
|
|
|
hr = Channels[channel].pdsb->GetStatus(&status);
|
|
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> GetStatus failed with return value %s\n", get_DSERR_text(hr) ));
|
|
return -1;
|
|
}
|
|
|
|
if ( status & DSBSTATUS_PLAYING ) {
|
|
hr = Channels[channel].pdsb->GetFrequency(&pitch);
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> GetFrequency failed with return value %s\n", get_DSERR_text(hr) ));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return (int)pitch;
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_set_pitch()
|
|
//
|
|
// Set the pitch of a channel
|
|
//
|
|
void ds_set_pitch(int channel, int pitch)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
ALint status;
|
|
|
|
if ( pitch < MIN_PITCH )
|
|
pitch = MIN_PITCH;
|
|
|
|
if ( pitch > MAX_PITCH )
|
|
pitch = MAX_PITCH;
|
|
|
|
OpenAL_ErrorCheck( alGetSourcei(Channels[channel].source_id, AL_SOURCE_STATE, &status), return );
|
|
|
|
if (status == AL_PLAYING) {
|
|
ALfloat alpitch = log10f((float)pitch) - 2.0f;
|
|
OpenAL_ErrorPrint( alSourcef(Channels[channel].source_id, AL_PITCH, alpitch) );
|
|
}
|
|
#else
|
|
unsigned long status;
|
|
HRESULT hr;
|
|
|
|
hr = Channels[channel].pdsb->GetStatus(&status);
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> GetStatus failed with return value %s\n", get_DSERR_text(hr) ));
|
|
return;
|
|
}
|
|
|
|
if ( pitch < MIN_PITCH )
|
|
pitch = MIN_PITCH;
|
|
|
|
if ( pitch > MAX_PITCH )
|
|
pitch = MAX_PITCH;
|
|
|
|
if ( status & DSBSTATUS_PLAYING ) {
|
|
Channels[channel].pdsb->SetFrequency((unsigned long)pitch);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds_chg_loop_status()
|
|
//
|
|
//
|
|
void ds_chg_loop_status(int channel, int loop)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
ALuint source_id = Channels[channel].source_id;
|
|
|
|
OpenAL_ErrorPrint( alSourcei(source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE) );
|
|
#else
|
|
unsigned long status;
|
|
HRESULT hr;
|
|
|
|
hr = Channels[channel].pdsb->GetStatus(&status);
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "SOUND ==> GetStatus failed with return value %s\n", get_DSERR_text(hr) ));
|
|
return;
|
|
}
|
|
|
|
if ( !(status & DSBSTATUS_PLAYING) )
|
|
return; // sound is not playing anymore
|
|
|
|
if ( status & DSBSTATUS_LOOPING ) {
|
|
if ( loop )
|
|
return; // we are already looping
|
|
else {
|
|
// stop the sound from looping
|
|
hr = Channels[channel].pdsb->Play(0,0,0);
|
|
}
|
|
}
|
|
else {
|
|
if ( !loop )
|
|
return; // the sound is already not looping
|
|
else {
|
|
// start the sound looping
|
|
hr = Channels[channel].pdsb->Play(0,0,DSBPLAY_LOOPING);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// ds3d_play()
|
|
//
|
|
// Starts a ds3d sound playing
|
|
//
|
|
// input:
|
|
//
|
|
// sid => software id for sound to play
|
|
// hid => hardware id for sound to play (-1 if not in hardware)
|
|
// snd_id => identifies what type of sound is playing
|
|
// pos => world pos of sound
|
|
// vel => velocity of object emitting sound
|
|
// min => distance at which sound doesn't get any louder
|
|
// max => distance at which sound becomes inaudible
|
|
// looping => boolean, whether to loop the sound or not
|
|
// max_volume => volume (-10000 to 0) for 3d sound at maximum
|
|
// estimated_vol => manual estimated volume
|
|
// priority => DS_MUST_PLAY
|
|
// DS_LIMIT_ONE
|
|
// DS_LIMIT_TWO
|
|
// DS_LIMIT_THREE
|
|
//
|
|
// returns: 0 => sound started successfully
|
|
// -1 => sound could not be played
|
|
//
|
|
int ds3d_play(int sid, int hid, int snd_id, vec3d *pos, vec3d *vel, int min, int max, int looping, int max_volume, int estimated_vol, int priority )
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int channel;
|
|
|
|
if (!ds_initialized)
|
|
return -1;
|
|
|
|
channel = ds_get_free_channel(estimated_vol, snd_id, priority);
|
|
|
|
if (channel > -1) {
|
|
if ( Channels[channel].source_id == 0 ) {
|
|
return -1;
|
|
}
|
|
|
|
// alDistanceModel(AL_INVERSE_DISTANCE);
|
|
|
|
// reset pitch value since it could have been changed for this source
|
|
OpenAL_ErrorPrint( alSourcef(Channels[channel].source_id, AL_PITCH, 1.0) );
|
|
|
|
// set up 3D sound data here
|
|
ds3d_update_buffer(channel, i2fl(min), i2fl(max), pos, vel);
|
|
|
|
// Actually play it
|
|
Channels[channel].vol = estimated_vol;
|
|
Channels[channel].looping = looping;
|
|
Channels[channel].priority = priority;
|
|
|
|
// set volume
|
|
ALfloat alvol = (estimated_vol != -10000) ? powf(10.0f, (float)estimated_vol / (-600.0f / log10f(.5f))): 0.0f;
|
|
OpenAL_ErrorPrint( alSourcef(Channels[channel].source_id, AL_GAIN, alvol) );
|
|
|
|
// set maximum "inner cone" volume
|
|
ALfloat max_vol = (max_volume != -10000) ? powf(10.0f, (float)max_volume / (-600.0f / log10f(.5f))): 0.0f;
|
|
OpenAL_ErrorPrint( alSourcef(Channels[channel].source_id, AL_MAX_GAIN, max_vol) );
|
|
|
|
ALint status;
|
|
OpenAL_ErrorCheck( alGetSourcei(Channels[channel].source_id, AL_SOURCE_STATE, &status), return -1 );
|
|
|
|
if (status == AL_PLAYING)
|
|
OpenAL_ErrorPrint( alSourceStop(Channels[channel].source_id) );
|
|
|
|
OpenAL_ErrorCheck( alSourcei(Channels[channel].source_id, AL_BUFFER, sound_buffers[sid].buf_id), return -1 );
|
|
|
|
OpenAL_ErrorPrint( alSourcei(Channels[channel].source_id, AL_LOOPING, (looping) ? AL_TRUE : AL_FALSE) );
|
|
|
|
OpenAL_ErrorPrint( alSourcei(Channels[channel].source_id, AL_SOURCE_RELATIVE, AL_TRUE) );
|
|
|
|
OpenAL_ErrorPrint( alSourcePlay(Channels[channel].source_id) );
|
|
|
|
sound_buffers[sid].source_id = channel;
|
|
Channels[channel].buf_id = sid;
|
|
}
|
|
else {
|
|
// nprintf(( "Sound", "SOUND ==> Not playing sound requested at volume %.2f\n", ds_get_percentage_vol(volume) ));
|
|
return -1;
|
|
}
|
|
|
|
Channels[channel].snd_id = snd_id;
|
|
Channels[channel].sig = channel_next_sig++;
|
|
if (channel_next_sig < 0 ) {
|
|
channel_next_sig = 1;
|
|
}
|
|
|
|
Channels[channel].last_position = 0;
|
|
|
|
return Channels[channel].sig;
|
|
|
|
#else
|
|
int channel;
|
|
HRESULT hr;
|
|
|
|
if (!ds_initialized)
|
|
return -1;
|
|
|
|
channel = ds_get_free_channel(estimated_vol, snd_id, priority);
|
|
|
|
if (channel > -1) {
|
|
Assert(Channels[channel].pdsb == NULL);
|
|
|
|
// First check if the sound is in hardware, and try to duplicate from there
|
|
if ( hid != -1 ) {
|
|
Int3();
|
|
if ( ds_is_3d_buffer(ds_hardware_buffers[hid].pdsb) == FALSE ) {
|
|
nprintf(("Sound", "SOUND ==> Tried to play non-3d buffer in ds3d_play()..\n"));
|
|
return -1;
|
|
}
|
|
|
|
if ( ds_channel_dup(ds_hardware_buffers[hid].pdsb, channel, 1) == 0 ) {
|
|
nprintf(("Sound", "SOUND ==> Played sound using DirectSound3D in hardware..\n"));
|
|
}
|
|
}
|
|
|
|
// Channel will be NULL if hardware dup failed, or there was no hardware dup attempted
|
|
if ( Channels[channel].pdsb == NULL ) {
|
|
|
|
/*
|
|
if ( ds_is_3d_buffer(ds_software_buffers[sid].pdsb) == FALSE ) {
|
|
nprintf(("Sound", "SOUND ==> Tried to play non-3d buffer in ds3d_play()..\n"));
|
|
return -1;
|
|
}
|
|
*/
|
|
|
|
if ( ds_channel_dup(ds_software_buffers[sid].pdsb, channel, 1) == 0 ) {
|
|
// nprintf(("Sound", "SOUND ==> Played sound using DirectSound3D \n"));
|
|
}
|
|
}
|
|
|
|
if ( Channels[channel].pdsb == NULL ) {
|
|
return -1;
|
|
/*
|
|
DSBUFFERDESC desc;
|
|
|
|
desc = ds_software_buffers[sid].desc;
|
|
desc.lpwfxFormat = &ds_software_buffers[sid].wfx;
|
|
|
|
// duplicate buffer failed, so call CreateBuffer instead
|
|
|
|
hr = pDirectSound->CreateSoundBuffer(&desc, &Channels[channel].pdsb, NULL );
|
|
// lock the data in
|
|
if ( (hr == DS_OK) && (Channels[channel].pdsb) ) {
|
|
BYTE *pdest, *pdest2;
|
|
BYTE *psrc, *psrc2;
|
|
DWORD src_ds_size, dest_ds_size, not_used;
|
|
int src_size;
|
|
|
|
if ( ds_get_size(sid, &src_size) != 0 ) {
|
|
Int3();
|
|
Channels[channel].pdsb->Release();
|
|
return -1;
|
|
}
|
|
|
|
// lock the src buffer
|
|
hr = ds_software_buffers[sid].pdsb->Lock(0, src_size, (void**)&psrc, &src_ds_size, (void**)&psrc2, ¬_used, 0);
|
|
if ( hr != DS_OK ) {
|
|
mprintf(("err: %s\n", get_DSERR_text(hr)));
|
|
Int3();
|
|
Channels[channel].pdsb->Release();
|
|
return -1;
|
|
}
|
|
|
|
if ( Channels[channel].pdsb->Lock(0, src_ds_size, (void**)(&pdest), &dest_ds_size, (void**)&pdest2, ¬_used, 0) == DS_OK) {
|
|
memcpy(pdest, psrc, src_ds_size);
|
|
Channels[channel].pdsb->Unlock(pdest, dest_ds_size, 0, 0);
|
|
ds_get_3d_interface(Channels[channel].pdsb, &Channels[channel].pds3db);
|
|
} else {
|
|
Channels[channel].pdsb->Release();
|
|
return -1;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
Assert(Channels[channel].pds3db );
|
|
Channels[channel].pds3db->SetMode(DS3DMODE_NORMAL,DS3D_IMMEDIATE);
|
|
|
|
// set up 3D sound data here
|
|
ds3d_update_buffer(channel, i2fl(min), i2fl(max), pos, vel);
|
|
|
|
Channels[channel].vol = estimated_vol;
|
|
Channels[channel].looping = looping;
|
|
|
|
// sets the maximum "inner cone" volume
|
|
Channels[channel].pdsb->SetVolume(max_volume);
|
|
|
|
int ds_flags = 0;
|
|
if ( looping )
|
|
ds_flags |= DSBPLAY_LOOPING;
|
|
|
|
// Actually play it
|
|
hr = Channels[channel].pdsb->Play(0, 0, ds_flags );
|
|
|
|
if ( hr == DSERR_BUFFERLOST ) {
|
|
ds_restore_buffer(Channels[channel].pdsb);
|
|
hr = Channels[channel].pdsb->Play(0, 0, ds_flags );
|
|
}
|
|
|
|
if ( hr != DS_OK ) {
|
|
nprintf(("Sound", "Sound ==> Play failed with return value %s\n", get_DSERR_text(hr) ));
|
|
if ( Channels[channel].pdsb ) {
|
|
int attempts = 0;
|
|
while(++attempts < 10) {
|
|
hr = Channels[channel].pdsb->Release();
|
|
if ( hr == DS_OK ) {
|
|
break;
|
|
} else {
|
|
nprintf(("Sound","SOUND ==> DirectSound Release() failed with code %s\n.",get_DSERR_text(hr) ));
|
|
continue;
|
|
}
|
|
}
|
|
Channels[channel].pdsb = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
nprintf(( "Sound", "SOUND ==> Not playing requested 3D sound\n"));
|
|
return -1;
|
|
}
|
|
|
|
Channels[channel].snd_id = snd_id;
|
|
Channels[channel].sig = channel_next_sig++;
|
|
if (channel_next_sig < 0 ) {
|
|
channel_next_sig = 1;
|
|
}
|
|
return Channels[channel].sig;
|
|
#endif
|
|
}
|
|
|
|
void ds_set_position(int channel, DWORD offset)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
|
|
#ifdef AL_VERSION_1_1
|
|
OpenAL_ErrorPrint( alSourcei(Channels[channel].source_id, AL_BYTE_OFFSET, offset) );
|
|
#endif
|
|
|
|
// STUB_FUNCTION;
|
|
|
|
#else
|
|
// set the position of the sound buffer
|
|
Channels[channel].pdsb->SetCurrentPosition(offset);
|
|
#endif
|
|
}
|
|
|
|
DWORD ds_get_play_position(int channel)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
ALint pos = 0;
|
|
int buf_id;
|
|
|
|
buf_id = Channels[channel].buf_id;
|
|
|
|
if (buf_id == -1)
|
|
return 0;
|
|
|
|
if (AL_play_position) {
|
|
OpenAL_ErrorCheck( alGetSourcei( Channels[channel].source_id, AL_BYTE_LOKI, &pos), return 0 );
|
|
|
|
if ( pos < 0 ) {
|
|
pos = 0;
|
|
} else if ( pos > 0 ) {
|
|
// AL_BYTE_LOKI returns position in canon format which may differ
|
|
// from our sample, so we may have to scale it
|
|
ALuint buf = sound_buffers[buf_id].buf_id;
|
|
ALint size;
|
|
|
|
OpenAL_ErrorCheck( alGetBufferi(buf, AL_SIZE, &size), return 0 );
|
|
|
|
pos = (ALint)(pos * ((float)sound_buffers[buf_id].nbytes / size));
|
|
}
|
|
}
|
|
#ifdef AL_VERSION_1_1
|
|
// AL_play_position should only be available under Linux, but OpenAL 1.1 provides a standard way now (except under Linux :()
|
|
else {
|
|
OpenAL_ErrorCheck( alGetSourcei( Channels[channel].source_id, AL_BYTE_OFFSET, &pos), return 0 );
|
|
|
|
if ( pos < 0 )
|
|
pos = 0;
|
|
}
|
|
#endif
|
|
|
|
return pos;
|
|
#else
|
|
DWORD play,write;
|
|
if ( Channels[channel].pdsb ) {
|
|
Channels[channel].pdsb->GetCurrentPosition((LPDWORD)&play,(LPDWORD)&write);
|
|
} else {
|
|
play = 0;
|
|
}
|
|
|
|
return play;
|
|
#endif
|
|
}
|
|
|
|
DWORD ds_get_write_position(int channel)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
// STUB_FUNCTION;
|
|
|
|
return 0;
|
|
#else
|
|
DWORD play,write;
|
|
if ( Channels[channel].pdsb ) {
|
|
Channels[channel].pdsb->GetCurrentPosition((LPDWORD)&play,(LPDWORD)&write);
|
|
} else {
|
|
write = 0;
|
|
}
|
|
|
|
return write;
|
|
#endif
|
|
}
|
|
|
|
int ds_get_channel_size(int channel)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int buf_id = Channels[channel].buf_id;
|
|
|
|
if (buf_id != -1) {
|
|
return sound_buffers[buf_id].nbytes;
|
|
}
|
|
|
|
return 0;
|
|
#else
|
|
int size;
|
|
DSBCAPS caps;
|
|
HRESULT dsrval;
|
|
|
|
if ( Channels[channel].pdsb ) {
|
|
memset(&caps, 0, sizeof(DSBCAPS));
|
|
caps.dwSize = sizeof(DSBCAPS);
|
|
dsrval = Channels[channel].pdsb->GetCaps(&caps);
|
|
if ( dsrval != DS_OK ) {
|
|
return 0;
|
|
}
|
|
size = caps.dwBufferBytes;
|
|
} else {
|
|
size = 0;
|
|
}
|
|
|
|
return size;
|
|
#endif
|
|
}
|
|
|
|
// Returns the number of channels that are actually playing
|
|
int ds_get_number_channels()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
int i,n;
|
|
|
|
if (!ds_initialized) {
|
|
return 0;
|
|
}
|
|
|
|
n = 0;
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
if ( Channels[i].source_id ) {
|
|
if ( ds_is_channel_playing(i) == TRUE ) {
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return n;
|
|
#else
|
|
int i,n;
|
|
|
|
n = 0;
|
|
for ( i = 0; i < MAX_CHANNELS; i++ ) {
|
|
if ( Channels[i].pdsb ) {
|
|
if ( ds_is_channel_playing(i) == TRUE ) {
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return n;
|
|
#endif
|
|
}
|
|
|
|
// retreive raw data from a sound buffer
|
|
int ds_get_data(int sid, char *data)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
STUB_FUNCTION;
|
|
|
|
return -1;
|
|
#else
|
|
HRESULT dsrval;
|
|
LPDIRECTSOUNDBUFFER pdsb;
|
|
DSBCAPS caps;
|
|
void *buffer_data;
|
|
DWORD buffer_size;
|
|
|
|
Assert(sid >= 0);
|
|
pdsb = ds_software_buffers[sid].pdsb;
|
|
|
|
memset(&caps, 0, sizeof(DSBCAPS));
|
|
caps.dwSize = sizeof(DSBCAPS);
|
|
dsrval = pdsb->GetCaps(&caps);
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
// lock the entire buffer
|
|
dsrval = pdsb->Lock(0, caps.dwBufferBytes, &buffer_data, &buffer_size, 0, 0, 0);
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(data, buffer_data, buffer_size);
|
|
|
|
dsrval = pdsb->Unlock(buffer_data, buffer_size, 0, 0);
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
// return the size of the raw sound data
|
|
int ds_get_size(int sid, int *size)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
Assert(sid >= 0);
|
|
|
|
STUB_FUNCTION;
|
|
|
|
return -1;
|
|
#else
|
|
HRESULT dsrval;
|
|
LPDIRECTSOUNDBUFFER pdsb;
|
|
DSBCAPS caps;
|
|
|
|
Assert(sid >= 0);
|
|
pdsb = ds_software_buffers[sid].pdsb;
|
|
|
|
memset(&caps, 0, sizeof(DSBCAPS));
|
|
caps.dwSize = sizeof(DSBCAPS);
|
|
dsrval = pdsb->GetCaps(&caps);
|
|
if ( dsrval != DS_OK ) {
|
|
return -1;
|
|
}
|
|
|
|
*size = caps.dwBufferBytes;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int ds_using_ds3d()
|
|
{
|
|
return Ds_use_ds3d;
|
|
}
|
|
|
|
// Return the primary buffer interface. Note that we cast to a uint to avoid
|
|
// having to include dsound.h (and thus windows.h) in ds.h.
|
|
//
|
|
uint ds_get_primary_buffer_interface()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
// unused
|
|
return 0;
|
|
#else
|
|
return (uint)pPrimaryBuffer;
|
|
#endif
|
|
}
|
|
|
|
// Return the DirectSound Interface.
|
|
//
|
|
uint ds_get_dsound_interface()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
// unused
|
|
return 0;
|
|
#else
|
|
return (uint)pDirectSound;
|
|
#endif
|
|
}
|
|
|
|
uint ds_get_property_set_interface()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return 0;
|
|
#else
|
|
return (uint)pPropertySet;
|
|
#endif
|
|
}
|
|
|
|
// --------------------
|
|
//
|
|
// EAX Functions below
|
|
//
|
|
// --------------------
|
|
|
|
// Set the master volume for the reverb added to all sound sources.
|
|
//
|
|
// volume: volume, range from 0 to 1.0
|
|
//
|
|
// returns: 0 if the volume is set successfully, otherwise return -1
|
|
//
|
|
int ds_eax_set_volume(float volume)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return -1;
|
|
#else
|
|
HRESULT hr;
|
|
|
|
if (Ds_eax_inited == 0) {
|
|
return -1;
|
|
}
|
|
|
|
Assert(Ds_eax_reverb);
|
|
|
|
CAP(volume, 0.0f, 1.0f);
|
|
|
|
hr = Ds_eax_reverb->Set(DSPROPSETID_EAX_ReverbProperties_Def, DSPROPERTY_EAX_VOLUME, NULL, 0, &volume, sizeof(float));
|
|
if (SUCCEEDED(hr)) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Set the decay time for the EAX environment (ie all sound sources)
|
|
//
|
|
// seconds: decay time in seconds
|
|
//
|
|
// returns: 0 if decay time is successfully set, otherwise return -1
|
|
//
|
|
int ds_eax_set_decay_time(float seconds)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return -1;
|
|
#else
|
|
HRESULT hr;
|
|
|
|
if (Ds_eax_inited == 0) {
|
|
return -1;
|
|
}
|
|
|
|
Assert(Ds_eax_reverb);
|
|
|
|
CAP(seconds, 0.1f, 20.0f);
|
|
|
|
hr = Ds_eax_reverb->Set(DSPROPSETID_EAX_ReverbProperties_Def, DSPROPERTY_EAX_DECAYTIME, NULL, 0, &seconds, sizeof(float));
|
|
if (SUCCEEDED(hr)) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Set the damping value for the EAX environment (ie all sound sources)
|
|
//
|
|
// damp: damp value from 0 to 2.0
|
|
//
|
|
// returns: 0 if the damp value is successfully set, otherwise return -1
|
|
//
|
|
int ds_eax_set_damping(float damp)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return -1;
|
|
#else
|
|
HRESULT hr;
|
|
|
|
if (Ds_eax_inited == 0) {
|
|
return -1;
|
|
}
|
|
|
|
Assert(Ds_eax_reverb);
|
|
|
|
CAP(damp, 0.0f, 2.0f);
|
|
|
|
hr = Ds_eax_reverb->Set(DSPROPSETID_EAX_ReverbProperties_Def, DSPROPERTY_EAX_DAMPING, NULL, 0, &damp, sizeof(float));
|
|
if (SUCCEEDED(hr)) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Set up the environment type for all sound sources.
|
|
//
|
|
// envid: value from the EAX_ENVIRONMENT_* enumeration in ds_eax.h
|
|
//
|
|
// returns: 0 if the environment is set successfully, otherwise return -1
|
|
//
|
|
int ds_eax_set_environment(unsigned long envid)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return -1;
|
|
#else
|
|
HRESULT hr;
|
|
|
|
if (Ds_eax_inited == 0) {
|
|
return -1;
|
|
}
|
|
|
|
Assert(Ds_eax_reverb);
|
|
|
|
hr = Ds_eax_reverb->Set(DSPROPSETID_EAX_ReverbProperties_Def, DSPROPERTY_EAX_ENVIRONMENT, NULL, 0, &envid, sizeof(unsigned long));
|
|
if (SUCCEEDED(hr)) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Set up a predefined environment for EAX
|
|
//
|
|
// envid: value from teh EAX_ENVIRONMENT_* enumeration
|
|
//
|
|
// returns: 0 if successful, otherwise return -1
|
|
//
|
|
int ds_eax_set_preset(unsigned long envid)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return -1;
|
|
#else
|
|
HRESULT hr;
|
|
|
|
if (Ds_eax_inited == 0) {
|
|
return -1;
|
|
}
|
|
|
|
Assert(Ds_eax_reverb);
|
|
Assert(envid < EAX_ENVIRONMENT_COUNT);
|
|
|
|
hr = Ds_eax_reverb->Set(DSPROPSETID_EAX_ReverbProperties_Def, DSPROPERTY_EAX_ALL, NULL, 0, &Ds_eax_presets[envid], sizeof(EAX_REVERBPROPERTIES));
|
|
if (SUCCEEDED(hr)) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
// Set up all the parameters for an environment
|
|
//
|
|
// id: value from teh EAX_ENVIRONMENT_* enumeration
|
|
// volume: volume for the environment (0 to 1.0)
|
|
// damping: damp value for the environment (0 to 2.0)
|
|
// decay: decay time in seconds (0.1 to 20.0)
|
|
//
|
|
// returns: 0 if successful, otherwise return -1
|
|
//
|
|
int ds_eax_set_all(unsigned long id, float vol, float damping, float decay)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return -1;
|
|
#else
|
|
HRESULT hr;
|
|
|
|
if (Ds_eax_inited == 0) {
|
|
return -1;
|
|
}
|
|
|
|
Assert(Ds_eax_reverb);
|
|
Assert(id < EAX_ENVIRONMENT_COUNT);
|
|
|
|
EAX_REVERBPROPERTIES er;
|
|
|
|
er.environment = id;
|
|
er.fVolume = vol;
|
|
er.fDecayTime_sec = decay;
|
|
er.fDamping = damping;
|
|
|
|
hr = Ds_eax_reverb->Set(DSPROPSETID_EAX_ReverbProperties_Def, DSPROPERTY_EAX_ALL, NULL, 0, &er, sizeof(EAX_REVERBPROPERTIES));
|
|
if (SUCCEEDED(hr)) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Get up the parameters for the current environment
|
|
//
|
|
// er: (output) hold environment parameters
|
|
//
|
|
// returns: 0 if successful, otherwise return -1
|
|
//
|
|
int ds_eax_get_all(EAX_REVERBPROPERTIES *er)
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return -1;
|
|
#else
|
|
HRESULT hr;
|
|
unsigned long outsize;
|
|
|
|
if (Ds_eax_inited == 0) {
|
|
return -1;
|
|
}
|
|
|
|
Assert(Ds_eax_reverb);
|
|
|
|
hr = Ds_eax_reverb->Get(DSPROPSETID_EAX_ReverbProperties_Def, DSPROPERTY_EAX_ALL, NULL, 0, er, sizeof(EAX_REVERBPROPERTIES), &outsize);
|
|
if (SUCCEEDED(hr)) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Close down EAX, freeing any allocated resources
|
|
//
|
|
void ds_eax_close()
|
|
{
|
|
#ifndef USE_OPENAL
|
|
if (Ds_eax_inited == 0) {
|
|
return;
|
|
}
|
|
|
|
Ds_eax_inited = 0;
|
|
#endif
|
|
}
|
|
|
|
// Initialize EAX
|
|
//
|
|
// returns: 0 if initialization is successful, otherwise return -1
|
|
//
|
|
int ds_eax_init()
|
|
{
|
|
#ifndef USE_OPENAL
|
|
HRESULT hr;
|
|
unsigned long driver_support = 0;
|
|
|
|
if (Ds_eax_inited) {
|
|
return 0;
|
|
}
|
|
|
|
Assert(Ds_eax_reverb == NULL);
|
|
|
|
Ds_eax_reverb = (LPKSPROPERTYSET)ds_get_property_set_interface();
|
|
if (Ds_eax_reverb == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// check if the listener property is supported by the audio driver
|
|
hr = Ds_eax_reverb->QuerySupport(DSPROPSETID_EAX_ReverbProperties_Def, DSPROPERTY_EAX_ALL, &driver_support);
|
|
if (FAILED(hr)) {
|
|
nprintf(("Sound", "QuerySupport for the EAX Listener property set failed.. disabling EAX\n"));
|
|
goto ds_eax_init_failed;
|
|
}
|
|
|
|
if ((driver_support & (KSPROPERTY_SUPPORT_GET | KSPROPERTY_SUPPORT_SET)) != (KSPROPERTY_SUPPORT_GET | KSPROPERTY_SUPPORT_SET)) {
|
|
goto ds_eax_init_failed;
|
|
}
|
|
|
|
ds_eax_set_all(EAX_ENVIRONMENT_GENERIC, 0.0f, 0.0f, 0.0f);
|
|
|
|
Ds_eax_inited = 1;
|
|
return 0;
|
|
|
|
ds_eax_init_failed:
|
|
if (Ds_eax_reverb != NULL) {
|
|
Ds_eax_reverb->Release();
|
|
Ds_eax_reverb = NULL;
|
|
}
|
|
|
|
Ds_eax_inited = 0;
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
int ds_eax_is_inited()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return 0;
|
|
#else
|
|
return Ds_eax_inited;
|
|
#endif
|
|
}
|
|
|
|
bool ds_using_a3d()
|
|
{
|
|
#ifdef USE_OPENAL
|
|
return false;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// Called once per game frame to make sure voice messages aren't looping
|
|
//
|
|
void ds_do_frame()
|
|
{
|
|
if (!ds_initialized)
|
|
return;
|
|
|
|
channel *cp = NULL;
|
|
|
|
for (int i=0; i<MAX_CHANNELS; i++) {
|
|
cp = &Channels[i];
|
|
Assert( cp != NULL );
|
|
if (cp->is_voice_msg == true) {
|
|
#ifdef USE_OPENAL
|
|
if( cp->source_id == 0 ) {
|
|
#else
|
|
if (cp->pdsb == NULL) {
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
DWORD current_position = ds_get_play_position(i);
|
|
if (current_position != 0) {
|
|
if (current_position < cp->last_position) {
|
|
#ifdef USE_OPENAL
|
|
ds_stop_channel(i);
|
|
#else
|
|
ds_close_channel(i);
|
|
#endif
|
|
} else {
|
|
cp->last_position = current_position;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// given a valid channel return the sound id
|
|
int ds_get_sound_id(int channel)
|
|
{
|
|
Assert( channel >= 0 );
|
|
|
|
return Channels[channel].snd_id;
|
|
}
|
|
|
|
|
|
#ifdef USE_OPENAL
|
|
void dscap_close()
|
|
{
|
|
STUB_FUNCTION;
|
|
}
|
|
|
|
int dscap_create_buffer(int freq, int bits_per_sample, int nchannels, int nseconds)
|
|
{
|
|
// STUB_FUNCTION;
|
|
|
|
return -1;
|
|
}
|
|
|
|
int dscap_get_raw_data(unsigned char *outbuf, unsigned int max_size)
|
|
{
|
|
// STUB_FUNCTION;
|
|
|
|
return -1;
|
|
}
|
|
|
|
int dscap_max_buffersize()
|
|
{
|
|
// STUB_FUNCTION;
|
|
|
|
return -1;
|
|
}
|
|
|
|
void dscap_release_buffer()
|
|
{
|
|
// STUB_FUNCTION;
|
|
}
|
|
|
|
int dscap_start_record()
|
|
{
|
|
// STUB_FUNCTION;
|
|
|
|
return -1;
|
|
}
|
|
|
|
int dscap_stop_record()
|
|
{
|
|
// STUB_FUNCTION;
|
|
|
|
return -1;
|
|
}
|
|
|
|
int dscap_supported()
|
|
{
|
|
// STUB_FUNCTION;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|