libacfutils
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
Loading...
Searching...
No Matches
wav.c
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license in the file COPYING
10 * or http://www.opensource.org/licenses/CDDL-1.0.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file COPYING.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2019 Saso Kiselkov. All rights reserved.
24 */
25
26#include <stdio.h>
27#include <stddef.h>
28#include <stdlib.h>
29#include <string.h>
30#include <errno.h>
31
32#include <alc.h>
33#include <alext.h>
34#include <efx.h>
35
36#include <opusfile.h>
37
38#include <acfutils/assert.h>
39#include <acfutils/helpers.h>
40#include <acfutils/list.h>
41#include <acfutils/log.h>
42#include <acfutils/riff.h>
43#include <acfutils/safe_alloc.h>
44#include <acfutils/time.h>
45#include <acfutils/types.h>
46#include <acfutils/wav.h>
47
48#include "minimp3.h"
49
50#define WAVE_ID FOURCC("WAVE")
51#define FMT_ID FOURCC("fmt ")
52#define DATA_ID FOURCC("data")
53
54#define READ_BUFSZ ((1024 * 1024) / sizeof (opus_int16)) /* bytes */
55
56#define WAV_OP_PARAM(al_op, al_param_name, err_ret, ...) \
57 do { \
58 ALuint err; \
59 alc_t sav; \
60 memset(&sav, 0, sizeof (sav)); \
61 if (wav == NULL || wav->alsrc == 0) \
62 return err_ret; \
63 VERIFY(ctx_save(wav->alc, &sav)); \
64 al_op(wav->alsrc, al_param_name, __VA_ARGS__); \
65 if ((err = alGetError()) != AL_NO_ERROR) { \
66 logMsg("Error performing " #al_op "(" #al_param_name \
67 ") on WAV %s, error 0x%x.", wav->name, err); \
68 VERIFY(ctx_restore(wav->alc, &sav)); \
69 return err_ret; \
70 } \
71 VERIFY(ctx_restore(wav->alc, &sav)); \
72 } while (0)
73
74#define WAV_SET_PARAM(al_op, al_param_name, ...) \
75 WAV_OP_PARAM(al_op, al_param_name, , __VA_ARGS__)
76
77#define LISTENER_OP_PARAM(al_op, al_param_name, err_ret, ...) \
78 do { \
79 ALuint err; \
80 alc_t sav; \
81 memset(&sav, 0, sizeof (sav)); \
82 VERIFY(ctx_save(alc, &sav)); \
83 al_op(al_param_name, __VA_ARGS__); \
84 if ((err = alGetError()) != AL_NO_ERROR) { \
85 logMsg("Error changing listener param " \
86 #al_param_name ", error 0x%x.", err); \
87 VERIFY(ctx_restore(alc, &sav)); \
88 return err_ret; \
89 } \
90 VERIFY(ctx_restore(alc, &sav)); \
91 } while (0)
92
93#define LISTENER_SET_PARAM(al_op, al_param_name, ...) \
94 LISTENER_OP_PARAM(al_op, al_param_name, , __VA_ARGS__)
95
96struct alc {
97 ALCdevice *dev;
98 ALCcontext *ctx;
99 bool_t thr_local;
100};
101
102/*
103 * ctx_save/ctx_restore must be used to bracket all OpenAL calls. This makes
104 * sure private contexts are handled properly (when in use). If shared
105 * contexts are used, these functions are no-ops.
106 */
107static bool_t
108ctx_save(alc_t *alc, alc_t *sav)
109{
110 ALuint err;
111
112 ASSERT(sav != NULL);
113
114 /* Thread-local contexts do not switch */
115 if (alc != NULL && alc->thr_local)
116 return (B_TRUE);
117
118 (void) alGetError(); /* cleanup after other OpenAL users */
119
120 if (alc != NULL && alc->ctx == NULL)
121 return (B_TRUE);
122
123 sav->ctx = alcGetCurrentContext();
124 /* Avoid ctx_save recursion */
125 if (alc != NULL && sav->ctx == alc->ctx)
126 return (B_TRUE);
127
128 if (sav->ctx != NULL) {
129 sav->dev = alcGetContextsDevice(sav->ctx);
130 VERIFY(sav->dev != NULL);
131 } else {
132 sav->dev = NULL;
133 }
134
135 if (alc != NULL) {
136 ASSERT(alc->ctx != NULL);
137 alcMakeContextCurrent(alc->ctx);
138 if ((err = alcGetError(alc->dev)) != ALC_NO_ERROR) {
139 logMsg("Error switching to my audio context (0x%x)",
140 err);
141 return (B_FALSE);
142 }
143 }
144
145 return (B_TRUE);
146}
147
148static bool_t
149ctx_restore(alc_t *alc, alc_t *sav)
150{
151 ALuint err;
152
153 ASSERT(sav != NULL);
154
155 /* Thread-local contexts do not switch, or nothing to restore */
156 if (alc != NULL && (alc->thr_local || alc->ctx == NULL))
157 return (B_TRUE);
158
159 /* Avoid ctx_restore recursion */
160 if (alc != NULL && sav->ctx == alc->ctx)
161 return (B_TRUE);
162
163 if (sav->ctx != NULL) {
164 alcMakeContextCurrent(sav->ctx);
165 VERIFY(sav->dev != NULL);
166 if ((err = alcGetError(sav->dev)) != ALC_NO_ERROR) {
167 logMsg("Error restoring shared audio context (0x%x)",
168 err);
169 return (B_FALSE);
170 }
171 }
172
173 return (B_TRUE);
174}
175
176char **
177openal_list_output_devs(size_t *num_p)
178{
179 char **devs = NULL;
180 size_t num = 0;
181
182 for (const char *device = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
183 device != NULL && *device != 0; device += strlen(device) + 1) {
184 devs = realloc(devs, (num + 1) * sizeof (*devs));
185 devs[num] = strdup(device);
186 num++;
187 }
188
189 *num_p = num;
190 return (devs);
191}
192
193alc_t *
194openal_init(const char *devname, bool_t shared)
195{
196 return (openal_init2(devname, shared, NULL, B_FALSE));
197}
198
199alc_t *
200openal_init2(const char *devname, bool_t shared, const int *attrs,
201 bool_t thr_local)
202{
203 alc_t *alc;
204 alc_t sav;
205
206 VERIFY(!shared || !thr_local);
207 /* Clear error state */
208 if (shared)
209 alGetError();
210
211 memset(&sav, 0, sizeof (sav));
212 if (!thr_local && !ctx_save(NULL, &sav))
213 return (NULL);
214
215 alc = safe_calloc(1, sizeof (*alc));
216 alc->thr_local = thr_local;
217
218 if (!shared || sav.ctx == NULL) {
219 ALCdevice *dev = NULL;
220 ALCcontext *ctx = NULL;
221 ALuint err;
222
223 dev = alcOpenDevice(devname);
224 if (dev == NULL) {
225 logMsg("Cannot init audio system: device open failed.");
226 free(alc);
227 if (!thr_local)
228 (void) ctx_restore(NULL, &sav);
229 return (B_FALSE);
230 }
231 ctx = alcCreateContext(dev, attrs);
232 if ((err = alcGetError(dev)) != ALC_NO_ERROR) {
233 logMsg("Cannot init audio system: create context "
234 "failed (0x%x)", err);
235 alcCloseDevice(dev);
236 free(alc);
237 (void) ctx_restore(NULL, &sav);
238 return (B_FALSE);
239 }
240 VERIFY(ctx != NULL);
241 /* No current context, install our own */
242 if (!thr_local && shared && sav.ctx == NULL) {
243 sav.ctx = ctx;
244 sav.dev = dev;
245 alcMakeContextCurrent(sav.ctx);
246 VERIFY(sav.dev != NULL);
247 if ((err = alcGetError(sav.dev)) != ALC_NO_ERROR) {
248 logMsg("Error installing shared audio context "
249 "(0x%x)", err);
250 return (B_FALSE);
251 }
252 }
253 if (!shared) {
254 alc->dev = dev;
255 alc->ctx = ctx;
256 }
257 if (thr_local) {
258 VERIFY3U(alcSetThreadContext(ctx), ==, ALC_TRUE);
259 }
260 }
261
262 if (!thr_local && !ctx_restore(alc, &sav)) {
263 if (!shared) {
264 alcDestroyContext(alc->ctx);
265 alcCloseDevice(alc->dev);
266 }
267 free(alc);
268 return (NULL);
269 }
270
271 return (alc);
272}
273
274void
275openal_fini(alc_t *alc)
276{
277 ASSERT(alc != NULL);
278
279 if (alc->thr_local)
280 alcSetThreadContext(NULL);
281 if (alc->dev != NULL) {
282 alcDestroyContext(alc->ctx);
283 alcCloseDevice(alc->dev);
284 }
285 free(alc);
286}
287
288static bool_t
289check_audio_fmt(const wav_fmt_hdr_t *fmt, const char *filename)
290{
291 /* format support check */
292 if (fmt->datafmt != 1 ||
293 (fmt->n_channels != 1 && fmt->n_channels != 2) ||
294 (fmt->bps != 8 && fmt->bps != 16)) {
295 logMsg("Error loading WAV file \"%s\": unsupported audio "
296 "format.", filename);
297 return (B_FALSE);
298 }
299 return (B_TRUE);
300}
301
302static bool_t
303wav_gen_al_bufs(wav_t *wav, const void *buf, size_t bufsz, const char *filename)
304{
305 ALuint err;
306 ALfloat zeroes[3] = { 0.0, 0.0, 0.0 };
307 alc_t sav;
308
309 if (!ctx_save(wav->alc, &sav))
310 return (B_FALSE);
311
312 alGenBuffers(1, &wav->albuf);
313 if ((err = alGetError()) != AL_NO_ERROR) {
314 logMsg("Error loading WAV file %s: alGenBuffers failed (0x%x).",
315 filename, err);
316 (void) ctx_restore(wav->alc, &sav);
317 return (B_FALSE);
318 }
319 if (wav->fmt.bps == 16) {
320 alBufferData(wav->albuf, wav->fmt.n_channels == 2 ?
321 AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, buf, bufsz,
322 wav->fmt.srate);
323 } else {
324 alBufferData(wav->albuf, wav->fmt.n_channels == 2 ?
325 AL_FORMAT_STEREO8 : AL_FORMAT_MONO8, buf, bufsz,
326 wav->fmt.srate);
327 }
328
329 if ((err = alGetError()) != AL_NO_ERROR) {
330 logMsg("Error loading WAV file %s: alBufferData failed (0x%x).",
331 filename, err);
332 (void) ctx_restore(wav->alc, &sav);
333 return (B_FALSE);
334 }
335
336 alGenSources(1, &wav->alsrc);
337 if ((err = alGetError()) != AL_NO_ERROR) {
338 logMsg("Error loading WAV file %s: alGenSources failed (0x%x).",
339 filename, err);
340 (void) ctx_restore(wav->alc, &sav);
341 return (B_FALSE);
342 }
343#define CHECK_ERROR(stmt) \
344 do { \
345 stmt; \
346 if ((err = alGetError()) != AL_NO_ERROR) { \
347 logMsg("Error loading WAV file %s, \"%s\" failed " \
348 "with error 0x%x", filename, #stmt, err); \
349 alDeleteSources(1, &wav->alsrc); \
350 VERIFY3S(alGetError(), ==, AL_NO_ERROR); \
351 wav->alsrc = 0; \
352 (void) ctx_restore(wav->alc, &sav); \
353 return (B_FALSE); \
354 } \
355 } while (0)
356 CHECK_ERROR(alSourcei(wav->alsrc, AL_BUFFER, wav->albuf));
357 CHECK_ERROR(alSourcef(wav->alsrc, AL_PITCH, 1.0));
358 CHECK_ERROR(alSourcef(wav->alsrc, AL_GAIN, 1.0));
359 CHECK_ERROR(alSourcei(wav->alsrc, AL_LOOPING, 0));
360 CHECK_ERROR(alSourcefv(wav->alsrc, AL_POSITION, zeroes));
361 CHECK_ERROR(alSourcefv(wav->alsrc, AL_VELOCITY, zeroes));
362
363 (void) ctx_restore(wav->alc, &sav);
364
365 return (B_TRUE);
366}
367
368static wav_t *
369wav_load_opus(const char *filename, alc_t *alc)
370{
371 wav_t *wav;
372 int error;
373 OggOpusFile *file = op_open_file(filename, &error);
374 const OpusHead *head;
375 unsigned sz = 0, cap = 0;
376 opus_int16 *pcm = NULL;
377
378 if (file == NULL) {
379 logMsg("Error reading OPUS file \"%s\": op_open_file error %d",
380 filename, error);
381 return (NULL);
382 }
383 head = op_head(file, 0);
384 VERIFY(head != NULL);
385
386 wav = safe_calloc(1, sizeof (*wav));
387 wav->alc = alc;
388
389 /* fake a wav_fmt_hdr_t from the OpusHead object */
390 wav->fmt.datafmt = 1;
391 wav->fmt.n_channels = head->channel_count;
392 wav->fmt.srate = 48000; /* Opus always outputs 48 kHz! */
393 wav->fmt.bps = 16;
394 wav->fmt.byte_rate = (wav->fmt.srate * wav->fmt.bps *
395 wav->fmt.n_channels) / 8;
396
397 if (!check_audio_fmt(&wav->fmt, filename))
398 goto errout;
399
400 for (;;) {
401 int op_read_sz;
402
403 /*
404 * opusfile asks us to keep at least 120ms of buffer space
405 * available, so /8 gives us 125ms
406 */
407 if (sz + ((wav->fmt.srate * wav->fmt.n_channels) / 8) >= cap) {
408 cap += READ_BUFSZ;
409 pcm = realloc(pcm, cap * sizeof (*pcm));
410 }
411 op_read_sz = op_read(file, &pcm[sz], cap - sz, 0);
412 if (op_read_sz > 0)
413 sz += op_read_sz * wav->fmt.n_channels;
414 else
415 break;
416 }
417 wav->duration = ((double)((sz - head->pre_skip) /
418 wav->fmt.n_channels)) / wav->fmt.srate;
419
420 VERIFY3S(head->pre_skip, <, sz);
421 wav_gen_al_bufs(wav, &pcm[head->pre_skip],
422 (sz - head->pre_skip) * sizeof (*pcm), filename);
423
424 free(pcm);
425 op_free(file);
426
427 return (wav);
428errout:
429 if (wav != NULL)
430 wav_free(wav);
431 if (file != NULL)
432 op_free(file);
433 return (NULL);
434}
435
436static wav_t *
437wav_load_mp3(const char *filename, alc_t *alc)
438{
439 wav_t *wav;
440 mp3_decoder_t mp3;
441 mp3_info_t info;
442 long len;
443 char *contents = file2str_name(&len, filename);
444 int16_t *pcm = NULL;
445 int bytes, n_bytes, audio_bytes;
446
447 if (contents == NULL) {
448 logMsg("Error reading MP3 file \"%s\": %s", filename,
449 strerror(errno));
450 return (NULL);
451 }
452
453 wav = safe_calloc(1, sizeof (*wav));
454 wav->alc = alc;
455
456 mp3 = mp3_create();
457 pcm = safe_malloc(1024 * 1024);
458
459 bytes = mp3_decode(mp3, contents, len, pcm, &info);
460 if (bytes == 0) {
461 logMsg("Error decoding MP3 file %s", filename);
462 mp3_done(&mp3);
463 goto errout;
464 }
465
466 /* fake a wav_fmt_hdr_t from the OpusHead object */
467 wav->fmt.datafmt = 1;
468 wav->fmt.n_channels = info.channels;
469 wav->fmt.srate = info.sample_rate;
470 wav->fmt.bps = 16;
471 wav->fmt.byte_rate = (wav->fmt.srate * wav->fmt.bps *
472 wav->fmt.n_channels) / 8;
473
474 audio_bytes = 0;
475 while ((n_bytes = mp3_decode(mp3, &contents[bytes], len - bytes,
476 &pcm[audio_bytes / sizeof (*pcm)], &info)) > 0) {
477 bytes += n_bytes;
478 audio_bytes += info.audio_bytes;
479 }
480
481 if (!check_audio_fmt(&wav->fmt, filename)) {
482 mp3_done(&mp3);
483 goto errout;
484 }
485
486 wav->duration = ((double)((bytes / sizeof (*pcm)) /
487 wav->fmt.n_channels)) / wav->fmt.srate;
488
489 wav_gen_al_bufs(wav, pcm, bytes, filename);
490
491 mp3_done(&mp3);
492 free(contents);
493 free(pcm);
494
495 return (wav);
496errout:
497 if (wav != NULL)
498 wav_free(wav);
499 free(contents);
500 free(pcm);
501
502 return (NULL);
503}
504
505static wav_t *
506wav_load_wav(const char *filename, alc_t *alc)
507{
508 wav_t *wav = NULL;
509 FILE *fp;
510 size_t filesz;
511 riff_chunk_t *riff = NULL;
512 uint8_t *filebuf = NULL;
513 riff_chunk_t *chunk;
514 int sample_sz;
515
516 if ((fp = fopen(filename, "rb")) == NULL) {
517 logMsg("Error loading WAV file \"%s\": can't open file: %s",
518 filename, strerror(errno));
519 return (NULL);
520 }
521
522 fseek(fp, 0, SEEK_END);
523 filesz = ftell(fp);
524 fseek(fp, 0, SEEK_SET);
525
526 if ((wav = safe_calloc(1, sizeof (*wav))) == NULL)
527 goto errout;
528 wav->alc = alc;
529 if ((filebuf = safe_malloc(filesz)) == NULL)
530 goto errout;
531 if (fread(filebuf, 1, filesz, fp) != filesz)
532 goto errout;
533 if ((riff = riff_parse(WAVE_ID, filebuf, filesz)) == NULL) {
534 logMsg("Error loading WAV file \"%s\": file doesn't appear "
535 "to be valid RIFF.", filename);
536 goto errout;
537 }
538
539 chunk = riff_find_chunk(riff, FMT_ID, 0);
540 if (chunk == NULL || chunk->datasz < sizeof (wav->fmt)) {
541 logMsg("Error loading WAV file \"%s\": file missing or "
542 "malformed `fmt ' chunk.", filename);
543 goto errout;
544 }
545 memcpy(&wav->fmt, chunk->data, sizeof (wav->fmt));
546 if (riff->bswap) {
547 wav->fmt.datafmt = BSWAP16(wav->fmt.datafmt);
548 wav->fmt.n_channels = BSWAP16(wav->fmt.n_channels);
549 wav->fmt.srate = BSWAP32(wav->fmt.srate);
550 wav->fmt.byte_rate = BSWAP32(wav->fmt.byte_rate);
551 wav->fmt.bps = BSWAP16(wav->fmt.bps);
552 }
553
554 if (!check_audio_fmt(&wav->fmt, filename))
555 goto errout;
556
557 /*
558 * Check the DATA chunk is present and contains the correct number
559 * of samples.
560 */
561 sample_sz = (wav->fmt.n_channels * wav->fmt.bps) / 8;
562 chunk = riff_find_chunk(riff, DATA_ID, 0);
563 if (chunk == NULL || (chunk->datasz & (sample_sz - 1)) != 0) {
564 logMsg("Error loading WAV file %s: `data' chunk missing or "
565 "contains bad number of samples.", filename);
566 goto errout;
567 }
568
569 wav->duration = ((double)(chunk->datasz / sample_sz)) / wav->fmt.srate;
570
571 /* BSWAP the samples if necessary */
572 if (riff->bswap && wav->fmt.bps == 16) {
573 for (uint16_t *s = (uint16_t *)chunk->data;
574 (uint8_t *)s < chunk->data + chunk->datasz;
575 s++)
576 *s = BSWAP16(*s);
577 }
578
579 if (!wav_gen_al_bufs(wav, chunk->data, chunk->datasz, filename))
580 goto errout;
581
582 riff_free_chunk(riff);
583 free(filebuf);
584 fclose(fp);
585
586 return (wav);
587
588errout:
589 if (filebuf != NULL)
590 free(filebuf);
591 wav_free(wav);
592 fclose(fp);
593
594 return (NULL);
595}
596
597/*
598 * Loads a WAV file from a file and returns a buffered representation
599 * ready to be passed to OpenAL. Currently we only support mono or
600 * stereo raw PCM (uncompressed) WAV files.
601 */
602wav_t *
603wav_load(const char *filename, const char *descr_name, alc_t *alc)
604{
605 const char *dot = strrchr(filename, '.');
606 wav_t *wav;
607
608 ASSERT(alc != NULL);
609
610 if (dot != NULL && (strcmp(&dot[1], "opus") == 0 ||
611 strcmp(&dot[1], "OPUS") == 0)) {
612 wav = wav_load_opus(filename, alc);
613 } else if (dot != NULL && (strcmp(&dot[1], "mp3") == 0 ||
614 strcmp(&dot[1], "MP3") == 0)) {
615 wav = wav_load_mp3(filename, alc);
616 } else {
617 wav = wav_load_wav(filename, alc);
618 }
619 if (wav != NULL) {
620 wav->name = strdup(descr_name);
621
622 /* set up some defaults */
623 wav->cone_outer = 360;
624 wav->cone_inner = 360;
625 wav->ref_dist = 1.0;
626 wav->max_dist = 1e10;
627 wav->rolloff_fact = 1.0;
628 wav->gain = 1.0;
629 wav->pitch = 1.0;
630 }
631
632 return (wav);
633}
634
635/*
636 * Destroys a WAV file as returned by wav_load().
637 */
638void
639wav_free(wav_t *wav)
640{
641 alc_t sav;
642
643 if (wav == NULL)
644 return;
645
646 VERIFY(ctx_save(wav->alc, &sav));
647 free(wav->name);
648 if (wav->alsrc != 0) {
649 alSourceStop(wav->alsrc);
650 alDeleteSources(1, &wav->alsrc);
651 }
652 if (wav->albuf != 0)
653 alDeleteBuffers(1, &wav->albuf);
654 VERIFY(ctx_restore(wav->alc, &sav));
655
656 free(wav);
657}
658
659void
660wav_set_offset(wav_t *wav, float offset_sec)
661{
662 WAV_SET_PARAM(alSourcef, AL_SEC_OFFSET, offset_sec);
663}
664
665float
666wav_get_offset(wav_t *wav)
667{
668 float offset;
669 WAV_OP_PARAM(alGetSourcef, AL_SEC_OFFSET, 0, &offset);
670 return (offset);
671}
672
673/*
674 * Sets the audio gain (volume) of a WAV file from 0.0 (silent) to 1.0
675 * (full volume).
676 */
677void
678wav_set_gain(wav_t *wav, float gain)
679{
680 /* This MUST go first, because if `wav' is NULL, we want to return */
681 WAV_SET_PARAM(alSourcef, AL_GAIN, gain);
682 wav->gain = gain;
683}
684
685float
686wav_get_gain(wav_t *wav)
687{
688 if (wav == NULL)
689 return (0);
690 return (wav->gain);
691}
692
693/*
694 * Sets the whether the WAV will loop continuously while playing.
695 */
696void
697wav_set_loop(wav_t *wav, bool_t loop)
698{
699 WAV_SET_PARAM(alSourcei, AL_LOOPING, loop);
700 wav->loop = loop;
701}
702
703bool_t
704wav_get_loop(wav_t *wav)
705{
706 if (wav == NULL)
707 return (B_FALSE);
708 return (wav->loop);
709}
710
711void
712wav_set_pitch(wav_t *wav, float pitch)
713{
714 WAV_SET_PARAM(alSourcef, AL_PITCH, pitch);
715 wav->pitch = pitch;
716}
717
718float
719wav_get_pitch(wav_t *wav)
720{
721 if (wav == NULL)
722 return (0);
723 return (wav->pitch);
724}
725
726void
727wav_set_position(wav_t *wav, vect3_t pos)
728{
729 WAV_SET_PARAM(alSource3f, AL_POSITION, pos.x, pos.y, pos.z);
730 wav->pos = pos;
731}
732
734wav_get_position(wav_t *wav)
735{
736 if (wav == NULL)
737 return (NULL_VECT3);
738 return (wav->pos);
739}
740
741void
742wav_set_velocity(wav_t *wav, vect3_t vel)
743{
744 WAV_SET_PARAM(alSource3f, AL_VELOCITY, vel.x, vel.y, vel.z);
745 wav->vel = vel;
746}
747
749wav_get_velocity(wav_t *wav)
750{
751 if (wav == NULL)
752 return (NULL_VECT3);
753 return (wav->vel);
754}
755
756void
757wav_set_ref_dist(wav_t *wav, double d)
758{
759 WAV_SET_PARAM(alSourcef, AL_REFERENCE_DISTANCE, d);
760 wav->ref_dist = d;
761}
762
763double
764wav_get_ref_dist(wav_t *wav)
765{
766 if (wav == NULL)
767 return (0);
768 return (wav->ref_dist);
769}
770
771void
772wav_set_max_dist(wav_t *wav, double d)
773{
774 WAV_SET_PARAM(alSourcef, AL_MAX_DISTANCE, d);
775 wav->max_dist = d;
776}
777
778double
779wav_get_max_dist(wav_t *wav)
780{
781 if (wav == NULL)
782 return (0);
783 return (wav->max_dist);
784}
785
786void
787wav_set_spatialize(wav_t *wav, bool_t flag)
788{
789 WAV_SET_PARAM(alSourcei, AL_SOURCE_SPATIALIZE_SOFT, flag);
790}
791
792void
793wav_set_rolloff_fact(wav_t *wav, double r)
794{
795 WAV_SET_PARAM(alSourcef, AL_ROLLOFF_FACTOR, r);
796 wav->rolloff_fact = r;
797}
798
799double
800wav_get_rolloff_fact(wav_t *wav)
801{
802 if (wav == NULL)
803 return (0);
804 return (wav->rolloff_fact);
805}
806
807void
808wav_set_dir(wav_t *wav, vect3_t dir)
809{
810 if (wav != NULL && !VECT3_EQ(wav->dir, dir)) {
811 WAV_SET_PARAM(alSource3f, AL_DIRECTION, dir.x, dir.y, dir.z);
812 wav->dir = dir;
813 }
814}
815
816void
817wav_set_cone_inner(wav_t *wav, double cone_inner)
818{
819 if (wav != NULL && wav->cone_inner != cone_inner) {
820 WAV_SET_PARAM(alSourcef, AL_CONE_INNER_ANGLE, cone_inner);
821 wav->cone_inner = cone_inner;
822 }
823}
824
825void
826wav_set_cone_outer(wav_t *wav, double cone_outer)
827{
828 if (wav != NULL && wav->cone_outer != cone_outer) {
829 WAV_SET_PARAM(alSourcef, AL_CONE_OUTER_ANGLE, cone_outer);
830 wav->cone_outer = cone_outer;
831 }
832}
833
834void
835wav_set_gain_outer(wav_t *wav, double gain_outer)
836{
837 if (wav != NULL && wav->gain_outer != gain_outer) {
838 WAV_SET_PARAM(alSourcef, AL_CONE_OUTER_GAIN, gain_outer);
839 wav->gain_outer = gain_outer;
840 }
841}
842
843void
844wav_set_gain_outerhf(wav_t *wav, double gain_outerhf)
845{
846 WAV_SET_PARAM(alSourcef, AL_CONE_OUTER_GAINHF, gain_outerhf);
847}
848
849void
850wav_set_stereo_angles(wav_t *wav, double a1, double a2)
851{
852 ALfloat a[] = {a1, a2};
853 WAV_SET_PARAM(alSourcefv, AL_EXT_STEREO_ANGLES, a);
854}
855
856void
857wav_set_air_absorption_fact(wav_t *wav, double fact)
858{
859 WAV_SET_PARAM(alSourcei, AL_AIR_ABSORPTION_FACTOR, fact);
860}
861
862/*
863 * Starts playback of a WAV file loaded through wav_load.
864 * Playback volume is full (1.0) or the last value set by wav_set_gain.
865 */
866bool_t
867wav_play(wav_t *wav)
868{
869 ALuint err;
870 alc_t sav;
871
872 if (wav == NULL)
873 return (B_FALSE);
874
875 VERIFY(ctx_save(wav->alc, &sav));
876
877 alSourcePlay(wav->alsrc);
878 if ((err = alGetError()) != AL_NO_ERROR) {
879 logMsg("Can't play sound: alSourcePlay failed (0x%x).", err);
880 VERIFY(ctx_restore(wav->alc, &sav));
881 return (B_FALSE);
882 }
883 wav->play_start = microclock();
884
885 VERIFY(ctx_restore(wav->alc, &sav));
886
887 return (B_TRUE);
888}
889
890bool_t
891wav_is_playing(wav_t *wav)
892{
893 return (wav != NULL && wav->play_start != 0 && (wav_get_loop(wav) ||
894 USEC2SEC(microclock() - wav->play_start) < wav->duration));
895}
896
897/*
898 * Stops playback of a WAV file started via wav_play and resets the
899 * playback position back to the start of the file.
900 */
901void
902wav_stop(wav_t *wav)
903{
904 ALuint err;
905 alc_t sav;
906
907 if (wav == NULL || wav->alsrc == 0)
908 return;
909
910 VERIFY(ctx_save(wav->alc, &sav));
911 alSourceStop(wav->alsrc);
912 if ((err = alGetError()) != AL_NO_ERROR)
913 logMsg("Can't stop sound, alSourceStop failed (0x%x).", err);
914 VERIFY(ctx_restore(wav->alc, &sav));
915 wav->play_start = 0;
916}
917
918void
919alc_set_dist_model(alc_t *alc, ALenum model)
920{
921 alc_t sav;
922 VERIFY(ctx_save(alc, &sav));
923 alDistanceModel(model);
924 VERIFY(ctx_restore(alc, &sav));
925}
926
927void
928alc_listener_set_pos(alc_t *alc, vect3_t pos)
929{
930 LISTENER_SET_PARAM(alListener3f, AL_POSITION, pos.x, pos.y, pos.z);
931}
932
934alc_listener_get_pos(alc_t *alc)
935{
936 ALfloat x, y, z;
937 LISTENER_OP_PARAM(alGetListener3f, AL_POSITION, NULL_VECT3, &x, &y, &z);
938 return (VECT3(x, y, z));
939}
940
941void
942alc_listener_set_orient(alc_t *alc, vect3_t orient)
943{
944 vect3_t at = vect3_rot(vect3_rot(VECT3(0, 0, -1), orient.x, 0),
945 orient.y, 1);
947 orient.x, 0), orient.z, 2), orient.y, 1);
948 ALfloat v[6] = { at.x, at.y, at.z, up.x, up.y, up.z };
949 LISTENER_SET_PARAM(alListenerfv, AL_ORIENTATION, v);
950}
951
952void
953alc_listener_set_velocity(alc_t *alc, vect3_t vel)
954{
955 LISTENER_SET_PARAM(alListener3f, AL_VELOCITY, vel.x, vel.y, vel.z);
956}
957
958alc_t *
959alc_global_save(alc_t *new_alc)
960{
961 alc_t *old_alc = calloc(1, sizeof (*old_alc));
962
963 VERIFY(ctx_save(new_alc, old_alc));
964 VERIFY3P(new_alc->ctx, !=, old_alc->ctx);
965
966 return (old_alc);
967}
968
969void
970alc_global_restore(alc_t *new_alc, alc_t *old_alc)
971{
972 VERIFY3P(new_alc->ctx, !=, old_alc->ctx);
973 VERIFY(ctx_restore(new_alc, old_alc));
974 free(old_alc);
975}
#define VERIFY(x)
Definition assert.h:78
#define VERIFY3P(x, op, y)
Definition assert.h:149
#define ASSERT(x)
Definition assert.h:208
#define VERIFY3S(x, op, y)
Definition assert.h:125
#define VERIFY3U(x, op, y)
Definition assert.h:136
#define NULL_VECT3
Definition geom.h:216
#define VECT3(x, y, z)
Definition geom.h:192
#define VECT3_EQ(a, b)
Definition geom.h:198
vect3_t vect3_rot(vect3_t v, double angle, unsigned axis)
Definition geom.c:479
char * file2str_name(long *len_p, const char *filename)
Definition helpers.c:1087
ssize_t filesz(const char *filename)
Definition helpers.c:1174
#define logMsg(...)
Definition log.h:112
static void * safe_calloc(size_t nmemb, size_t size)
Definition safe_alloc.h:71
static void * safe_malloc(size_t size)
Definition safe_alloc.h:56
Definition wav.c:96
Definition geom.h:89
Definition wav.h:51