GXS.Sounds.OpenAL.pas 9.2 KB

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