Sounds.OpenAL.pas 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit Sounds.OpenAL;
  5. (*
  6. OpenAL based sound-manager http://www.openal.org
  7. OpenAL drivers can be download from the OpenAL site or your soundcard
  8. manufacturer's website.
  9. Unsupported feature(s) :
  10. Accepts only simple *uncompressed* WAV files (8/16 bits, mono/stereo)
  11. Dynamic loading/unloading
  12. Global 3D parameters
  13. Environments
  14. CPUUsagePercent
  15. No system in place to limit number of sources playing simultaneously,
  16. can crash if too playing at once.
  17. *)
  18. interface
  19. {$I GLScene.inc}
  20. uses
  21. System.Classes,
  22. System.SysUtils,
  23. Vcl.Forms,
  24. Vcl.Dialogs,
  25. GLS.VectorTypes,
  26. GLS.Scene,
  27. GLS.VectorGeometry,
  28. GLS.Coordinates,
  29. GLS.Sound,
  30. GLS.SoundFileObjects;
  31. type
  32. TGLSMOpenAL = class(TGLSoundManager)
  33. private
  34. FActivated: Boolean;
  35. protected
  36. function DoActivate: Boolean; override;
  37. procedure DoDeActivate; override;
  38. procedure NotifyMasterVolumeChange; override;
  39. procedure Notify3DFactorsChanged; override;
  40. procedure NotifyEnvironmentChanged; override;
  41. procedure KillSource(aSource: TGLBaseSoundSource); override;
  42. procedure UpdateSource(aSource: TGLBaseSoundSource); override;
  43. procedure MuteSource(aSource: TGLBaseSoundSource; muted: Boolean); override;
  44. procedure PauseSource(aSource: TGLBaseSoundSource; paused: Boolean); override;
  45. function GetDefaultFrequency(aSource: TGLBaseSoundSource): Integer;
  46. function GetALFormat(sampling: TGLSoundSampling): Integer;
  47. public
  48. constructor Create(AOwner: TComponent); override;
  49. destructor Destroy; override;
  50. procedure UpdateSources; override;
  51. function EAXSupported: Boolean; override;
  52. end;
  53. EOpenALError = Exception;
  54. // ---------------------------------------------------------------------
  55. implementation
  56. // ---------------------------------------------------------------------
  57. uses
  58. Sounds.OpenALImport {al, alut, alTypes};
  59. // checks for an error and raises an exception if necessary
  60. procedure CheckOpenALError;
  61. var
  62. error: Integer;
  63. begin
  64. error := alGetError;
  65. if error <> AL_NO_ERROR then
  66. raise EOpenALError.Create('OpenAL Error #' + IntToStr(error) + ' (HEX: $' + IntToHex(error, 4) + ')');
  67. end;
  68. // clears the error-states
  69. procedure ClearOpenALError;
  70. begin
  71. alGetError;
  72. end;
  73. // ------------------
  74. // ------------------ TGLSMOpenAL ------------------
  75. // ------------------
  76. constructor TGLSMOpenAL.Create(AOwner: TComponent);
  77. begin
  78. inherited Create(AOwner);
  79. end;
  80. destructor TGLSMOpenAL.Destroy;
  81. begin
  82. inherited Destroy;
  83. end;
  84. function TGLSMOpenAL.DoActivate: Boolean;
  85. var
  86. dummy: array of PALbyte;
  87. begin
  88. Result := false;
  89. // Setup OpenAL
  90. if not InitOpenAL() then
  91. Exit;
  92. dummy := nil;
  93. alutInit(nil, dummy);
  94. CheckOpenALError;
  95. alDistanceModel(AL_INVERSE_DISTANCE);
  96. CheckOpenALError;
  97. ReadOpenALExtensions();
  98. // Set any global states
  99. FActivated := true;
  100. NotifyMasterVolumeChange;
  101. Notify3DFactorsChanged;
  102. if Environment <> seDefault then
  103. NotifyEnvironmentChanged;
  104. Result := true;
  105. end;
  106. procedure TGLSMOpenAL.DoDeActivate;
  107. var
  108. i: Integer;
  109. begin
  110. FActivated := false;
  111. for i := 0 to Sources.Count - 1 do
  112. begin
  113. Sources[i].Sample.ManagerTag := 0;
  114. end;
  115. alutExit;
  116. end;
  117. procedure TGLSMOpenAL.NotifyMasterVolumeChange;
  118. begin
  119. if FActivated then
  120. begin
  121. alListenerf(AL_GAIN, MasterVolume);
  122. end;
  123. end;
  124. procedure TGLSMOpenAL.Notify3DFactorsChanged;
  125. begin
  126. if FActivated then
  127. begin
  128. alDopplerFactor(DopplerFactor);
  129. end;
  130. end;
  131. procedure TGLSMOpenAL.NotifyEnvironmentChanged;
  132. begin
  133. if FActivated then
  134. begin
  135. // check extension is available + update
  136. if EAXSupported then
  137. begin
  138. // nothing yet
  139. end;
  140. end;
  141. end;
  142. procedure TGLSMOpenAL.KillSource(aSource: TGLBaseSoundSource);
  143. var
  144. i, currentBufferTag, bufferCount: Integer;
  145. begin
  146. if aSource.ManagerTag <> 0 then
  147. begin
  148. alSourceStop(aSource.ManagerTag);
  149. alDeleteSources(1, PALuint(@aSource.ManagerTag));
  150. aSource.ManagerTag := 0;
  151. // We can't just delete buffer, because other sources may be using it
  152. // so we count how many sources are using, then delete if it's the only one
  153. // using.
  154. // Same for ASource.Sample.ManagerTag, we set to zero once it's no longer
  155. // being used by any other sources
  156. currentBufferTag := aSource.Sample.ManagerTag;
  157. bufferCount := 0;
  158. if currentBufferTag <> 0 then
  159. begin
  160. for i := 0 to Sources.Count - 1 do
  161. begin
  162. if Sources[i].Sample.ManagerTag = currentBufferTag then
  163. begin
  164. bufferCount := bufferCount + 1;
  165. end;
  166. end;
  167. if bufferCount = 1 then
  168. begin
  169. alDeleteBuffers(1, PALuint(@aSource.Sample.ManagerTag));
  170. aSource.Sample.ManagerTag := 0;
  171. end;
  172. end;
  173. end;
  174. end;
  175. procedure TGLSMOpenAL.UpdateSource(aSource: TGLBaseSoundSource);
  176. var
  177. a: TALint;
  178. begin
  179. // Clear any errors we may enter into procedure with
  180. ClearOpenALError;
  181. // Creates an OpenAL source object if needed, and put ID into aSource.ManagerTag
  182. if aSource.ManagerTag = 0 then
  183. begin
  184. alGenSources(1, PALuint(@aSource.ManagerTag));
  185. CheckOpenALError;
  186. end
  187. else
  188. begin
  189. // Check to see if source has stopped, if so free it as limited number of sources allowed
  190. alGetSourcei(aSource.ManagerTag, AL_SOURCE_STATE, @a);
  191. CheckOpenALError;
  192. if a = AL_STOPPED then
  193. begin
  194. aSource.Free;
  195. Exit;
  196. end;
  197. end;
  198. // if sscTransformation in aSource.Changes then begin
  199. alSourcefv(aSource.ManagerTag, AL_POSITION, PALFloat(aSource.Origin.Position.asAddress));
  200. CheckOpenALError;
  201. alSourcefv(aSource.ManagerTag, AL_DIRECTION, PALFloat(aSource.Origin.Direction.asAddress));
  202. CheckOpenALError;
  203. // end;
  204. if aSource.SoundName <> '' then
  205. begin
  206. // If the sample doesn't have a reference to an OpenAL buffer
  207. // we need to create a buffer, and load the sample data into it
  208. if (aSource.Sample.ManagerTag = 0) and Assigned(aSource.Sample.Data) then
  209. begin
  210. alGenBuffers(1, PALuint(@aSource.Sample.ManagerTag));
  211. CheckOpenALError;
  212. // fill buffer (once buffer filled, can't fill buffer again, unless no other sources playing)
  213. alBufferData(aSource.Sample.ManagerTag, GetALFormat(aSource.Sample.sampling), aSource.Sample.Data.PCMData,
  214. aSource.Sample.Data.LengthInBytes, aSource.Sample.Data.sampling.Frequency);
  215. CheckOpenALError;
  216. end;
  217. if (sscSample in aSource.Changes) and Assigned(aSource.Sample.Data) then
  218. begin
  219. // Associate buffer with source, buffer may have either been recently
  220. // created, or already existing if being used by another source
  221. alSourcei(aSource.ManagerTag, AL_BUFFER, aSource.Sample.ManagerTag);
  222. CheckOpenALError;
  223. // If NbLoops>1 the source will constantly loop the sample, otherwise only play once
  224. alSourcei(aSource.ManagerTag, AL_LOOPING, Integer(aSource.NbLoops > 1));
  225. CheckOpenALError;
  226. // Start the source playing!
  227. alSourcePlay(aSource.ManagerTag);
  228. CheckOpenALError;
  229. end;
  230. end;
  231. if sscStatus in aSource.Changes then
  232. begin
  233. alSourcef(aSource.ManagerTag, AL_PITCH, 1.0);
  234. CheckOpenALError;
  235. alSourcef(aSource.ManagerTag, AL_GAIN, 1.0);
  236. CheckOpenALError;
  237. alSourcef(aSource.ManagerTag, AL_MAX_DISTANCE, aSource.MaxDistance);
  238. CheckOpenALError;
  239. alSourcef(aSource.ManagerTag, AL_ROLLOFF_FACTOR, 1.0);
  240. CheckOpenALError;
  241. alSourcef(aSource.ManagerTag, AL_REFERENCE_DISTANCE, aSource.MinDistance);
  242. CheckOpenALError;
  243. alSourcef(aSource.ManagerTag, AL_CONE_INNER_ANGLE, aSource.InsideConeAngle);
  244. CheckOpenALError;
  245. alSourcef(aSource.ManagerTag, AL_CONE_OUTER_ANGLE, aSource.OutsideConeAngle);
  246. CheckOpenALError;
  247. alSourcef(aSource.ManagerTag, AL_CONE_OUTER_GAIN, aSource.ConeOutsideVolume);
  248. end;
  249. inherited UpdateSource(aSource);
  250. end;
  251. procedure TGLSMOpenAL.MuteSource(aSource: TGLBaseSoundSource; muted: Boolean);
  252. begin
  253. if muted then
  254. alSourcef(aSource.ManagerTag, AL_MAX_GAIN, 0.0)
  255. else
  256. alSourcef(aSource.ManagerTag, AL_MAX_GAIN, 1.0);
  257. end;
  258. procedure TGLSMOpenAL.PauseSource(aSource: TGLBaseSoundSource; paused: Boolean);
  259. begin
  260. if not paused then
  261. begin
  262. alSourceRewind(aSource.ManagerTag);
  263. alSourcePlay(aSource.ManagerTag);
  264. end
  265. else
  266. alSourcePause(aSource.ManagerTag);
  267. end;
  268. procedure TGLSMOpenAL.UpdateSources;
  269. var
  270. pos, dir, up, vel: TGLVector;
  271. DirUp: array [0 .. 5] of TALfloat; // orientation
  272. begin
  273. ListenerCoordinates(pos, vel, dir, up);
  274. alListenerfv(AL_POSITION, PALFloat(@pos));
  275. alListenerfv(AL_VELOCITY, PALFloat(@vel));
  276. DirUp[0] := dir.X;
  277. DirUp[1] := dir.Y;
  278. DirUp[2] := dir.Z;
  279. DirUp[3] := up.X;
  280. DirUp[4] := up.Y;
  281. DirUp[5] := up.Z;
  282. alListenerfv(AL_ORIENTATION, PALFloat(@DirUp));
  283. inherited;
  284. end;
  285. function TGLSMOpenAL.EAXSupported: Boolean;
  286. begin
  287. Result := alIsExtensionPresent(PAnsiChar('EAX2.0'));
  288. end;
  289. function TGLSMOpenAL.GetDefaultFrequency(aSource: TGLBaseSoundSource): Integer;
  290. begin
  291. Result := -1;
  292. end;
  293. function TGLSMOpenAL.GetALFormat(sampling: TGLSoundSampling): Integer;
  294. begin
  295. Result := 0;
  296. // mono
  297. if sampling.NbChannels = 1 then
  298. case sampling.BitsPerSample of
  299. 8:
  300. Result := AL_FORMAT_MONO8;
  301. 16:
  302. Result := AL_FORMAT_MONO16;
  303. end
  304. else
  305. case sampling.BitsPerSample of // stereo
  306. 8:
  307. Result := AL_FORMAT_STEREO8;
  308. 16:
  309. Result := AL_FORMAT_STEREO16;
  310. end;
  311. end;
  312. end.