🛠️🐜 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.

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