🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

688 lines
26 KiB

  1. /*
  2. * OpenAL Multi-Zone Reverb Example
  3. *
  4. * Copyright (c) 2018 by Chris Robinson <chris.kcat@gmail.com>
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. /* This file contains an example for controlling multiple reverb zones to
  25. * smoothly transition between reverb environments. The general concept is to
  26. * extend single-reverb by also tracking the closest adjacent environment, and
  27. * utilize EAX Reverb's panning vectors to position them relative to the
  28. * listener.
  29. */
  30. #include <assert.h>
  31. #include <inttypes.h>
  32. #include <limits.h>
  33. #include <math.h>
  34. #include <stdio.h>
  35. #include <stdlib.h>
  36. #include <string.h>
  37. #include "sndfile.h"
  38. #include "AL/al.h"
  39. #include "AL/alc.h"
  40. #include "AL/efx.h"
  41. #include "AL/efx-presets.h"
  42. #include "common/alhelpers.h"
  43. #ifndef M_PI
  44. #define M_PI 3.14159265358979323846
  45. #endif
  46. /* Filter object functions */
  47. static LPALGENFILTERS alGenFilters;
  48. static LPALDELETEFILTERS alDeleteFilters;
  49. static LPALISFILTER alIsFilter;
  50. static LPALFILTERI alFilteri;
  51. static LPALFILTERIV alFilteriv;
  52. static LPALFILTERF alFilterf;
  53. static LPALFILTERFV alFilterfv;
  54. static LPALGETFILTERI alGetFilteri;
  55. static LPALGETFILTERIV alGetFilteriv;
  56. static LPALGETFILTERF alGetFilterf;
  57. static LPALGETFILTERFV alGetFilterfv;
  58. /* Effect object functions */
  59. static LPALGENEFFECTS alGenEffects;
  60. static LPALDELETEEFFECTS alDeleteEffects;
  61. static LPALISEFFECT alIsEffect;
  62. static LPALEFFECTI alEffecti;
  63. static LPALEFFECTIV alEffectiv;
  64. static LPALEFFECTF alEffectf;
  65. static LPALEFFECTFV alEffectfv;
  66. static LPALGETEFFECTI alGetEffecti;
  67. static LPALGETEFFECTIV alGetEffectiv;
  68. static LPALGETEFFECTF alGetEffectf;
  69. static LPALGETEFFECTFV alGetEffectfv;
  70. /* Auxiliary Effect Slot object functions */
  71. static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
  72. static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
  73. static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
  74. static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
  75. static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
  76. static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
  77. static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
  78. static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
  79. static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
  80. static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
  81. static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;
  82. /* LoadEffect loads the given initial reverb properties into the given OpenAL
  83. * effect object, and returns non-zero on success.
  84. */
  85. static int LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES *reverb)
  86. {
  87. ALenum err;
  88. alGetError();
  89. /* Prepare the effect for EAX Reverb (standard reverb doesn't contain
  90. * the needed panning vectors).
  91. */
  92. alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
  93. if((err=alGetError()) != AL_NO_ERROR)
  94. {
  95. fprintf(stderr, "Failed to set EAX Reverb: %s (0x%04x)\n", alGetString(err), err);
  96. return 0;
  97. }
  98. /* Load the reverb properties. */
  99. alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity);
  100. alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion);
  101. alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain);
  102. alEffectf(effect, AL_EAXREVERB_GAINHF, reverb->flGainHF);
  103. alEffectf(effect, AL_EAXREVERB_GAINLF, reverb->flGainLF);
  104. alEffectf(effect, AL_EAXREVERB_DECAY_TIME, reverb->flDecayTime);
  105. alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, reverb->flDecayHFRatio);
  106. alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, reverb->flDecayLFRatio);
  107. alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain);
  108. alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay);
  109. alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, reverb->flReflectionsPan);
  110. alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain);
  111. alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay);
  112. alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, reverb->flLateReverbPan);
  113. alEffectf(effect, AL_EAXREVERB_ECHO_TIME, reverb->flEchoTime);
  114. alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, reverb->flEchoDepth);
  115. alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, reverb->flModulationTime);
  116. alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, reverb->flModulationDepth);
  117. alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF);
  118. alEffectf(effect, AL_EAXREVERB_HFREFERENCE, reverb->flHFReference);
  119. alEffectf(effect, AL_EAXREVERB_LFREFERENCE, reverb->flLFReference);
  120. alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor);
  121. alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit);
  122. /* Check if an error occured, and return failure if so. */
  123. if((err=alGetError()) != AL_NO_ERROR)
  124. {
  125. fprintf(stderr, "Error setting up reverb: %s\n", alGetString(err));
  126. return 0;
  127. }
  128. return 1;
  129. }
  130. /* LoadBuffer loads the named audio file into an OpenAL buffer object, and
  131. * returns the new buffer ID.
  132. */
  133. static ALuint LoadSound(const char *filename)
  134. {
  135. ALenum err, format;
  136. ALuint buffer;
  137. SNDFILE *sndfile;
  138. SF_INFO sfinfo;
  139. short *membuf;
  140. sf_count_t num_frames;
  141. ALsizei num_bytes;
  142. /* Open the audio file and check that it's usable. */
  143. sndfile = sf_open(filename, SFM_READ, &sfinfo);
  144. if(!sndfile)
  145. {
  146. fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile));
  147. return 0;
  148. }
  149. if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels)
  150. {
  151. fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames);
  152. sf_close(sndfile);
  153. return 0;
  154. }
  155. /* Get the sound format, and figure out the OpenAL format */
  156. if(sfinfo.channels == 1)
  157. format = AL_FORMAT_MONO16;
  158. else if(sfinfo.channels == 2)
  159. format = AL_FORMAT_STEREO16;
  160. else
  161. {
  162. fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels);
  163. sf_close(sndfile);
  164. return 0;
  165. }
  166. /* Decode the whole audio file to a buffer. */
  167. membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short));
  168. num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames);
  169. if(num_frames < 1)
  170. {
  171. free(membuf);
  172. sf_close(sndfile);
  173. fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames);
  174. return 0;
  175. }
  176. num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short);
  177. /* Buffer the audio data into a new buffer object, then free the data and
  178. * close the file.
  179. */
  180. buffer = 0;
  181. alGenBuffers(1, &buffer);
  182. alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate);
  183. free(membuf);
  184. sf_close(sndfile);
  185. /* Check if an error occured, and clean up if so. */
  186. err = alGetError();
  187. if(err != AL_NO_ERROR)
  188. {
  189. fprintf(stderr, "OpenAL Error: %s\n", alGetString(err));
  190. if(buffer && alIsBuffer(buffer))
  191. alDeleteBuffers(1, &buffer);
  192. return 0;
  193. }
  194. return buffer;
  195. }
  196. /* Helper to calculate the dot-product of the two given vectors. */
  197. static ALfloat dot_product(const ALfloat vec0[3], const ALfloat vec1[3])
  198. {
  199. return vec0[0]*vec1[0] + vec0[1]*vec1[1] + vec0[2]*vec1[2];
  200. }
  201. /* Helper to normalize a given vector. */
  202. static void normalize(ALfloat vec[3])
  203. {
  204. ALfloat mag = sqrtf(dot_product(vec, vec));
  205. if(mag > 0.00001f)
  206. {
  207. vec[0] /= mag;
  208. vec[1] /= mag;
  209. vec[2] /= mag;
  210. }
  211. else
  212. {
  213. vec[0] = 0.0f;
  214. vec[1] = 0.0f;
  215. vec[2] = 0.0f;
  216. }
  217. }
  218. /* The main update function to update the listener and environment effects. */
  219. static void UpdateListenerAndEffects(float timediff, const ALuint slots[2], const ALuint effects[2], const EFXEAXREVERBPROPERTIES reverbs[2])
  220. {
  221. static const ALfloat listener_move_scale = 10.0f;
  222. /* Individual reverb zones are connected via "portals". Each portal has a
  223. * position (center point of the connecting area), a normal (facing
  224. * direction), and a radius (approximate size of the connecting area).
  225. */
  226. const ALfloat portal_pos[3] = { 0.0f, 0.0f, 0.0f };
  227. const ALfloat portal_norm[3] = { sqrtf(0.5f), 0.0f, -sqrtf(0.5f) };
  228. const ALfloat portal_radius = 2.5f;
  229. ALfloat other_dir[3], this_dir[3];
  230. ALfloat listener_pos[3];
  231. ALfloat local_norm[3];
  232. ALfloat local_dir[3];
  233. ALfloat near_edge[3];
  234. ALfloat far_edge[3];
  235. ALfloat dist, edist;
  236. /* Update the listener position for the amount of time passed. This uses a
  237. * simple triangular LFO to offset the position (moves along the X axis
  238. * between -listener_move_scale and +listener_move_scale for each
  239. * transition).
  240. */
  241. listener_pos[0] = (fabsf(2.0f - timediff/2.0f) - 1.0f) * listener_move_scale;
  242. listener_pos[1] = 0.0f;
  243. listener_pos[2] = 0.0f;
  244. alListenerfv(AL_POSITION, listener_pos);
  245. /* Calculate local_dir, which represents the listener-relative point to the
  246. * adjacent zone (should also include orientation). Because EAX Reverb uses
  247. * left-handed coordinates instead of right-handed like the rest of OpenAL,
  248. * negate Z for the local values.
  249. */
  250. local_dir[0] = portal_pos[0] - listener_pos[0];
  251. local_dir[1] = portal_pos[1] - listener_pos[1];
  252. local_dir[2] = -(portal_pos[2] - listener_pos[2]);
  253. /* A normal application would also rotate the portal's normal given the
  254. * listener orientation, to get the listener-relative normal.
  255. */
  256. local_norm[0] = portal_norm[0];
  257. local_norm[1] = portal_norm[1];
  258. local_norm[2] = -portal_norm[2];
  259. /* Calculate the distance from the listener to the portal, and ensure it's
  260. * far enough away to not suffer severe floating-point precision issues.
  261. */
  262. dist = sqrtf(dot_product(local_dir, local_dir));
  263. if(dist > 0.00001f)
  264. {
  265. const EFXEAXREVERBPROPERTIES *other_reverb, *this_reverb;
  266. ALuint other_effect, this_effect;
  267. ALfloat magnitude, dir_dot_norm;
  268. /* Normalize the direction to the portal. */
  269. local_dir[0] /= dist;
  270. local_dir[1] /= dist;
  271. local_dir[2] /= dist;
  272. /* Calculate the dot product of the portal's local direction and local
  273. * normal, which is used for angular and side checks later on.
  274. */
  275. dir_dot_norm = dot_product(local_dir, local_norm);
  276. /* Figure out which zone we're in. */
  277. if(dir_dot_norm <= 0.0f)
  278. {
  279. /* We're in front of the portal, so we're in Zone 0. */
  280. this_effect = effects[0];
  281. other_effect = effects[1];
  282. this_reverb = &reverbs[0];
  283. other_reverb = &reverbs[1];
  284. }
  285. else
  286. {
  287. /* We're behind the portal, so we're in Zone 1. */
  288. this_effect = effects[1];
  289. other_effect = effects[0];
  290. this_reverb = &reverbs[1];
  291. other_reverb = &reverbs[0];
  292. }
  293. /* Calculate the listener-relative extents of the portal. */
  294. /* First, project the listener-to-portal vector onto the portal's plane
  295. * to get the portal-relative direction along the plane that goes away
  296. * from the listener (toward the farthest edge of the portal).
  297. */
  298. far_edge[0] = local_dir[0] - local_norm[0]*dir_dot_norm;
  299. far_edge[1] = local_dir[1] - local_norm[1]*dir_dot_norm;
  300. far_edge[2] = local_dir[2] - local_norm[2]*dir_dot_norm;
  301. edist = sqrtf(dot_product(far_edge, far_edge));
  302. if(edist > 0.0001f)
  303. {
  304. /* Rescale the portal-relative vector to be at the radius edge. */
  305. ALfloat mag = portal_radius / edist;
  306. far_edge[0] *= mag;
  307. far_edge[1] *= mag;
  308. far_edge[2] *= mag;
  309. /* Calculate the closest edge of the portal by negating the
  310. * farthest, and add an offset to make them both relative to the
  311. * listener.
  312. */
  313. near_edge[0] = local_dir[0]*dist - far_edge[0];
  314. near_edge[1] = local_dir[1]*dist - far_edge[1];
  315. near_edge[2] = local_dir[2]*dist - far_edge[2];
  316. far_edge[0] += local_dir[0]*dist;
  317. far_edge[1] += local_dir[1]*dist;
  318. far_edge[2] += local_dir[2]*dist;
  319. /* Normalize the listener-relative extents of the portal, then
  320. * calculate the panning magnitude for the other zone given the
  321. * apparent size of the opening. The panning magnitude affects the
  322. * envelopment of the environment, with 1 being a point, 0.5 being
  323. * half coverage around the listener, and 0 being full coverage.
  324. */
  325. normalize(far_edge);
  326. normalize(near_edge);
  327. magnitude = 1.0f - acosf(dot_product(far_edge, near_edge))/(float)(M_PI*2.0);
  328. /* Recalculate the panning direction, to be directly between the
  329. * direction of the two extents.
  330. */
  331. local_dir[0] = far_edge[0] + near_edge[0];
  332. local_dir[1] = far_edge[1] + near_edge[1];
  333. local_dir[2] = far_edge[2] + near_edge[2];
  334. normalize(local_dir);
  335. }
  336. else
  337. {
  338. /* If we get here, the listener is directly in front of or behind
  339. * the center of the portal, making all aperture edges effectively
  340. * equidistant. Calculating the panning magnitude is simplified,
  341. * using the arctangent of the radius and distance.
  342. */
  343. magnitude = 1.0f - (atan2f(portal_radius, dist) / (float)M_PI);
  344. }
  345. /* Scale the other zone's panning vector. */
  346. other_dir[0] = local_dir[0] * magnitude;
  347. other_dir[1] = local_dir[1] * magnitude;
  348. other_dir[2] = local_dir[2] * magnitude;
  349. /* Pan the current zone to the opposite direction of the portal, and
  350. * take the remaining percentage of the portal's magnitude.
  351. */
  352. this_dir[0] = local_dir[0] * (magnitude-1.0f);
  353. this_dir[1] = local_dir[1] * (magnitude-1.0f);
  354. this_dir[2] = local_dir[2] * (magnitude-1.0f);
  355. /* Now set the effects' panning vectors and gain. Energy is shared
  356. * between environments, so attenuate according to each zone's
  357. * contribution (note: gain^2 = energy).
  358. */
  359. alEffectf(this_effect, AL_EAXREVERB_REFLECTIONS_GAIN, this_reverb->flReflectionsGain * sqrtf(magnitude));
  360. alEffectf(this_effect, AL_EAXREVERB_LATE_REVERB_GAIN, this_reverb->flLateReverbGain * sqrtf(magnitude));
  361. alEffectfv(this_effect, AL_EAXREVERB_REFLECTIONS_PAN, this_dir);
  362. alEffectfv(this_effect, AL_EAXREVERB_LATE_REVERB_PAN, this_dir);
  363. alEffectf(other_effect, AL_EAXREVERB_REFLECTIONS_GAIN, other_reverb->flReflectionsGain * sqrtf(1.0f-magnitude));
  364. alEffectf(other_effect, AL_EAXREVERB_LATE_REVERB_GAIN, other_reverb->flLateReverbGain * sqrtf(1.0f-magnitude));
  365. alEffectfv(other_effect, AL_EAXREVERB_REFLECTIONS_PAN, other_dir);
  366. alEffectfv(other_effect, AL_EAXREVERB_LATE_REVERB_PAN, other_dir);
  367. }
  368. else
  369. {
  370. /* We're practically in the center of the portal. Give the panning
  371. * vectors a 50/50 split, with Zone 0 covering the half in front of
  372. * the normal, and Zone 1 covering the half behind.
  373. */
  374. this_dir[0] = local_norm[0] / 2.0f;
  375. this_dir[1] = local_norm[1] / 2.0f;
  376. this_dir[2] = local_norm[2] / 2.0f;
  377. other_dir[0] = local_norm[0] / -2.0f;
  378. other_dir[1] = local_norm[1] / -2.0f;
  379. other_dir[2] = local_norm[2] / -2.0f;
  380. alEffectf(effects[0], AL_EAXREVERB_REFLECTIONS_GAIN, reverbs[0].flReflectionsGain * sqrtf(0.5f));
  381. alEffectf(effects[0], AL_EAXREVERB_LATE_REVERB_GAIN, reverbs[0].flLateReverbGain * sqrtf(0.5f));
  382. alEffectfv(effects[0], AL_EAXREVERB_REFLECTIONS_PAN, this_dir);
  383. alEffectfv(effects[0], AL_EAXREVERB_LATE_REVERB_PAN, this_dir);
  384. alEffectf(effects[1], AL_EAXREVERB_REFLECTIONS_GAIN, reverbs[1].flReflectionsGain * sqrtf(0.5f));
  385. alEffectf(effects[1], AL_EAXREVERB_LATE_REVERB_GAIN, reverbs[1].flLateReverbGain * sqrtf(0.5f));
  386. alEffectfv(effects[1], AL_EAXREVERB_REFLECTIONS_PAN, other_dir);
  387. alEffectfv(effects[1], AL_EAXREVERB_LATE_REVERB_PAN, other_dir);
  388. }
  389. /* Finally, update the effect slots with the updated effect parameters. */
  390. alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, (ALint)effects[0]);
  391. alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, (ALint)effects[1]);
  392. }
  393. int main(int argc, char **argv)
  394. {
  395. static const int MaxTransitions = 8;
  396. EFXEAXREVERBPROPERTIES reverbs[2] = {
  397. EFX_REVERB_PRESET_CARPETEDHALLWAY,
  398. EFX_REVERB_PRESET_BATHROOM
  399. };
  400. ALCdevice *device = NULL;
  401. ALCcontext *context = NULL;
  402. ALuint effects[2] = { 0, 0 };
  403. ALuint slots[2] = { 0, 0 };
  404. ALuint direct_filter = 0;
  405. ALuint buffer = 0;
  406. ALuint source = 0;
  407. ALCint num_sends = 0;
  408. ALenum state = AL_INITIAL;
  409. ALfloat direct_gain = 1.0f;
  410. int basetime = 0;
  411. int loops = 0;
  412. /* Print out usage if no arguments were specified */
  413. if(argc < 2)
  414. {
  415. fprintf(stderr, "Usage: %s [-device <name>] [options] <filename>\n\n"
  416. "Options:\n"
  417. "\t-nodirect\tSilence direct path output (easier to hear reverb)\n\n",
  418. argv[0]);
  419. return 1;
  420. }
  421. /* Initialize OpenAL, and check for EFX support with at least 2 auxiliary
  422. * sends (if multiple sends are supported, 2 are provided by default; if
  423. * you want more, you have to request it through alcCreateContext).
  424. */
  425. argv++; argc--;
  426. if(InitAL(&argv, &argc) != 0)
  427. return 1;
  428. while(argc > 0)
  429. {
  430. if(strcmp(argv[0], "-nodirect") == 0)
  431. direct_gain = 0.0f;
  432. else
  433. break;
  434. argv++;
  435. argc--;
  436. }
  437. if(argc < 1)
  438. {
  439. fprintf(stderr, "No filename spacified.\n");
  440. CloseAL();
  441. return 1;
  442. }
  443. context = alcGetCurrentContext();
  444. device = alcGetContextsDevice(context);
  445. if(!alcIsExtensionPresent(device, "ALC_EXT_EFX"))
  446. {
  447. fprintf(stderr, "Error: EFX not supported\n");
  448. CloseAL();
  449. return 1;
  450. }
  451. num_sends = 0;
  452. alcGetIntegerv(device, ALC_MAX_AUXILIARY_SENDS, 1, &num_sends);
  453. if(alcGetError(device) != ALC_NO_ERROR || num_sends < 2)
  454. {
  455. fprintf(stderr, "Error: Device does not support multiple sends (got %d, need 2)\n",
  456. num_sends);
  457. CloseAL();
  458. return 1;
  459. }
  460. /* Define a macro to help load the function pointers. */
  461. #define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x)))
  462. LOAD_PROC(LPALGENFILTERS, alGenFilters);
  463. LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters);
  464. LOAD_PROC(LPALISFILTER, alIsFilter);
  465. LOAD_PROC(LPALFILTERI, alFilteri);
  466. LOAD_PROC(LPALFILTERIV, alFilteriv);
  467. LOAD_PROC(LPALFILTERF, alFilterf);
  468. LOAD_PROC(LPALFILTERFV, alFilterfv);
  469. LOAD_PROC(LPALGETFILTERI, alGetFilteri);
  470. LOAD_PROC(LPALGETFILTERIV, alGetFilteriv);
  471. LOAD_PROC(LPALGETFILTERF, alGetFilterf);
  472. LOAD_PROC(LPALGETFILTERFV, alGetFilterfv);
  473. LOAD_PROC(LPALGENEFFECTS, alGenEffects);
  474. LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects);
  475. LOAD_PROC(LPALISEFFECT, alIsEffect);
  476. LOAD_PROC(LPALEFFECTI, alEffecti);
  477. LOAD_PROC(LPALEFFECTIV, alEffectiv);
  478. LOAD_PROC(LPALEFFECTF, alEffectf);
  479. LOAD_PROC(LPALEFFECTFV, alEffectfv);
  480. LOAD_PROC(LPALGETEFFECTI, alGetEffecti);
  481. LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv);
  482. LOAD_PROC(LPALGETEFFECTF, alGetEffectf);
  483. LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv);
  484. LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots);
  485. LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots);
  486. LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot);
  487. LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti);
  488. LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv);
  489. LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf);
  490. LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv);
  491. LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti);
  492. LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv);
  493. LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf);
  494. LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv);
  495. #undef LOAD_PROC
  496. /* Load the sound into a buffer. */
  497. buffer = LoadSound(argv[0]);
  498. if(!buffer)
  499. {
  500. CloseAL();
  501. return 1;
  502. }
  503. /* Generate two effects for two "zones", and load a reverb into each one.
  504. * Note that unlike single-zone reverb, where you can store one effect per
  505. * preset, for multi-zone reverb you should have one effect per environment
  506. * instance, or one per audible zone. This is because we'll be changing the
  507. * effects' properties in real-time based on the environment instance
  508. * relative to the listener.
  509. */
  510. alGenEffects(2, effects);
  511. if(!LoadEffect(effects[0], &reverbs[0]) || !LoadEffect(effects[1], &reverbs[1]))
  512. {
  513. alDeleteEffects(2, effects);
  514. alDeleteBuffers(1, &buffer);
  515. CloseAL();
  516. return 1;
  517. }
  518. /* Create the effect slot objects, one for each "active" effect. */
  519. alGenAuxiliaryEffectSlots(2, slots);
  520. /* Tell the effect slots to use the loaded effect objects, with slot 0 for
  521. * Zone 0 and slot 1 for Zone 1. Note that this effectively copies the
  522. * effect properties. Modifying or deleting the effect object afterward
  523. * won't directly affect the effect slot until they're reapplied like this.
  524. */
  525. alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, (ALint)effects[0]);
  526. alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, (ALint)effects[1]);
  527. assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot");
  528. /* For the purposes of this example, prepare a filter that optionally
  529. * silences the direct path which allows us to hear just the reverberation.
  530. * A filter like this is normally used for obstruction, where the path
  531. * directly between the listener and source is blocked (the exact
  532. * properties depending on the type and thickness of the obstructing
  533. * material).
  534. */
  535. alGenFilters(1, &direct_filter);
  536. alFilteri(direct_filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
  537. alFilterf(direct_filter, AL_LOWPASS_GAIN, direct_gain);
  538. assert(alGetError()==AL_NO_ERROR && "Failed to set direct filter");
  539. /* Create the source to play the sound with, place it in front of the
  540. * listener's path in the left zone.
  541. */
  542. source = 0;
  543. alGenSources(1, &source);
  544. alSourcei(source, AL_LOOPING, AL_TRUE);
  545. alSource3f(source, AL_POSITION, -5.0f, 0.0f, -2.0f);
  546. alSourcei(source, AL_DIRECT_FILTER, (ALint)direct_filter);
  547. alSourcei(source, AL_BUFFER, (ALint)buffer);
  548. /* Connect the source to the effect slots. Here, we connect source send 0
  549. * to Zone 0's slot, and send 1 to Zone 1's slot. Filters can be specified
  550. * to occlude the source from each zone by varying amounts; for example, a
  551. * source within a particular zone would be unfiltered, while a source that
  552. * can only see a zone through a window or thin wall may be attenuated for
  553. * that zone.
  554. */
  555. alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slots[0], 0, AL_FILTER_NULL);
  556. alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slots[1], 1, AL_FILTER_NULL);
  557. assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
  558. /* Get the current time as the base for timing in the main loop. */
  559. basetime = altime_get();
  560. loops = 0;
  561. printf("Transition %d of %d...\n", loops+1, MaxTransitions);
  562. /* Play the sound for a while. */
  563. alSourcePlay(source);
  564. do {
  565. int curtime;
  566. ALfloat timediff;
  567. /* Start a batch update, to ensure all changes apply simultaneously. */
  568. alcSuspendContext(context);
  569. /* Get the current time to track the amount of time that passed.
  570. * Convert the difference to seconds.
  571. */
  572. curtime = altime_get();
  573. timediff = (float)(curtime - basetime) / 1000.0f;
  574. /* Avoid negative time deltas, in case of non-monotonic clocks. */
  575. if(timediff < 0.0f)
  576. timediff = 0.0f;
  577. else while(timediff >= 4.0f*(float)((loops&1)+1))
  578. {
  579. /* For this example, each transition occurs over 4 seconds, and
  580. * there's 2 transitions per cycle.
  581. */
  582. if(++loops < MaxTransitions)
  583. printf("Transition %d of %d...\n", loops+1, MaxTransitions);
  584. if(!(loops&1))
  585. {
  586. /* Cycle completed. Decrease the delta and increase the base
  587. * time to start a new cycle.
  588. */
  589. timediff -= 8.0f;
  590. basetime += 8000;
  591. }
  592. }
  593. /* Update the listener and effects, and finish the batch. */
  594. UpdateListenerAndEffects(timediff, slots, effects, reverbs);
  595. alcProcessContext(context);
  596. al_nssleep(10000000);
  597. alGetSourcei(source, AL_SOURCE_STATE, &state);
  598. } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING && loops < MaxTransitions);
  599. /* All done. Delete resources, and close down OpenAL. */
  600. alDeleteSources(1, &source);
  601. alDeleteAuxiliaryEffectSlots(2, slots);
  602. alDeleteEffects(2, effects);
  603. alDeleteFilters(1, &direct_filter);
  604. alDeleteBuffers(1, &buffer);
  605. CloseAL();
  606. return 0;
  607. }