e9060e7e |
1 | /* -*-c-*- |
2 | * |
3 | * $Id: au.c,v 1.1 2002/02/02 19:16:28 mdw Exp $ |
4 | * |
5 | * High-level audio subsystem |
6 | * |
7 | * (c) 2002 Mark Wooding |
8 | */ |
9 | |
10 | /*----- Licensing notice --------------------------------------------------* |
11 | * |
12 | * This file is part of Jog: Programming for a jogging machine. |
13 | * |
14 | * Jog is free software; you can redistribute it and/or modify |
15 | * it under the terms of the GNU General Public License as published by |
16 | * the Free Software Foundation; either version 2 of the License, or |
17 | * (at your option) any later version. |
18 | * |
19 | * Jog is distributed in the hope that it will be useful, |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22 | * GNU General Public License for more details. |
23 | * |
24 | * You should have received a copy of the GNU General Public License |
25 | * along with Jog; if not, write to the Free Software Foundation, |
26 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
27 | */ |
28 | |
29 | /*----- Revision history --------------------------------------------------* |
30 | * |
31 | * $Log: au.c,v $ |
32 | * Revision 1.1 2002/02/02 19:16:28 mdw |
33 | * New audio subsystem. |
34 | * |
35 | */ |
36 | |
37 | /*----- Header files ------------------------------------------------------*/ |
38 | |
39 | #ifdef HAVE_CONFIG_H |
40 | # include "config.h" |
41 | #endif |
42 | |
43 | #include <assert.h> |
44 | #include <errno.h> |
45 | #include <stdio.h> |
46 | #include <stdlib.h> |
47 | #include <string.h> |
48 | |
49 | #include <sys/types.h> |
50 | #include <unistd.h> |
51 | #include <sys/stat.h> |
52 | #include <fcntl.h> |
53 | |
54 | #include <mLib/alloc.h> |
55 | #include <mLib/dstr.h> |
56 | #include <mLib/sub.h> |
57 | #include <mLib/sym.h> |
58 | #include <mLib/trace.h> |
59 | |
60 | #include "au.h" |
61 | #include "ausys.h" |
62 | #include "err.h" |
63 | #include "jog.h" |
64 | |
65 | /*----- Static variables --------------------------------------------------*/ |
66 | |
67 | static unsigned au_flags = 0; |
68 | static sym_table au_tab; /* Sample cache, by name */ |
69 | static const char *au_dir = AUDIODIR; /* Directory containing samples */ |
70 | static struct { au_data *next, *prev; } au_spare; /* Lists for sample data */ |
71 | static size_t au_sz, au_max; /* Size of cached samples */ |
72 | |
73 | #define AU_SPARE ((au_data*)&au_spare) |
74 | #define f_init 1u |
75 | |
76 | /*----- Utility functions -------------------------------------------------*/ |
77 | |
78 | /* --- @filename@ --- * |
79 | * |
80 | * Arguments: @const char *tag@ = sample tag string |
81 | * |
82 | * Returns: A pointer to a filename for the sample. |
83 | * |
84 | * Use: Converts tag strings to filenames. |
85 | */ |
86 | |
87 | static const char *filename(const char *tag) |
88 | { |
89 | static dstr d = DSTR_INIT; |
90 | |
91 | DRESET(&d); |
92 | dstr_putf(&d, "%s/%s.%s", au_dir, tag, ausys_suffix); |
93 | T( trace(T_AU, "au: map tag `%s' -> `%s'", tag, d.buf); ) |
94 | return (d.buf); |
95 | } |
96 | |
97 | /* --- @prune@ --- * |
98 | * |
99 | * Arguments: --- |
100 | * |
101 | * Returns: --- |
102 | * |
103 | * Use: Prunes the cache of old sample data. |
104 | */ |
105 | |
106 | static void prune(void) |
107 | { |
108 | T( trace(T_AU, "au: pruning cache (%lu/%lu)", |
109 | (unsigned long)au_sz, (unsigned long)au_max); ) |
110 | while (au_sz > au_max && AU_SPARE->next != AU_SPARE) { |
111 | au_data *a = AU_SPARE->next; |
112 | au_sample *s = a->s; |
113 | assert(!a->ref); |
114 | AU_SPARE->next = a->next; |
115 | a->next->prev = AU_SPARE; |
116 | s->a = 0; |
117 | au_sz -= a->sz; |
118 | ausys_free(a); |
119 | T( trace(T_AU, "au: ... discarded `%s' (%lu/%lu)", SYM_NAME(s), |
120 | (unsigned long)au_sz, (unsigned long)au_max); ) |
121 | } |
122 | } |
123 | |
124 | /*----- Main code ---------------------------------------------------------*/ |
125 | |
126 | /* --- @au_init@ --- * |
127 | * |
128 | * Arguments: @const char *dir@ = samples directory, or null |
129 | * @size_t max@ = maximum cache size |
130 | * |
131 | * Returns: --- |
132 | * |
133 | * Use: Initializes the audio subsystem. |
134 | */ |
135 | |
136 | void au_init(const char *dir, size_t max) |
137 | { |
138 | if (au_flags & f_init) |
139 | return; |
140 | |
141 | /* --- Set up the sound directory --- */ |
142 | |
143 | if (!dir) |
144 | dir = getenv("JOG_AUDIR"); |
145 | if (dir) |
146 | au_dir = dir; |
147 | |
148 | /* --- Initialize the sample cache --- */ |
149 | |
150 | sym_create(&au_tab); |
151 | AU_SPARE->next = AU_SPARE->prev = AU_SPARE; |
152 | au_max = max; |
153 | |
154 | /* --- Initialize the system-specific subsystem --- */ |
155 | |
156 | ausys_init(); |
157 | T( trace(T_AU, "au: initialized ok (dir = `%s')", au_dir); ) |
158 | |
159 | /* --- Done --- */ |
160 | |
161 | au_flags |= f_init; |
162 | } |
163 | |
164 | /* --- @au_shutdown@ --- * |
165 | * |
166 | * Arguments: --- |
167 | * |
168 | * Returns: --- |
169 | * |
170 | * Use: Shuts down the audio subsystem. |
171 | */ |
172 | |
173 | void au_shutdown(void) |
174 | { |
175 | if (!(au_flags & f_init)) |
176 | return; |
177 | ausys_shutdown(); |
178 | T( trace(T_AU, "au: shutdown ok"); ) |
179 | } |
180 | |
181 | /* --- @au_find@ --- * |
182 | * |
183 | * Arguments: @const char *tag@ = sample tag string |
184 | * |
185 | * Returns: A pointer to the sample corresponding to the tag, or null if |
186 | * the sample wasn't found. |
187 | * |
188 | * Use: Looks up a sample by its name. |
189 | */ |
190 | |
191 | au_sample *au_find(const char *tag) |
192 | { |
193 | unsigned f; |
194 | au_sample *s = sym_find(&au_tab, tag, -1, sizeof(*s), &f); |
195 | |
196 | if (!f) { |
197 | struct stat st; |
198 | |
199 | s->f = 0; |
200 | s->a = 0; |
201 | if (stat(filename(tag), &st)) |
202 | s->f |= AUF_NOMATCH; |
203 | } |
204 | if (s->f & AUF_NOMATCH) { |
205 | T( trace(T_AU, "au: sample `%s' not found%s", tag, f ? " (hit)": ""); ) |
206 | return (0); |
207 | } |
208 | T( trace(T_AU, "au: sample `%s' found%s", tag, f ? " (hit)" : ""); ) |
209 | return (s); |
210 | } |
211 | |
212 | /* --- @au_fetch@ --- * |
213 | * |
214 | * Arguments: @au_sample *s@ = sample pointer |
215 | * |
216 | * Returns: A pointer to the audio data for the sample. |
217 | * |
218 | * Use: Fetches a sample, and decodes it, if necessary. The decoded |
219 | * sample data is left with an outstanding reference to it, so |
220 | * it won't be freed. This is used internally by @au_queue@, |
221 | * before passing the fetched sample data to the system-specific |
222 | * layer for playback. It can also be used to lock sample data |
223 | * in memory (e.g., for the `abort' error message), or (by |
224 | * freeing it immediately afterwards) to prefetch a sample which |
225 | * will be used soon, to reduce the latency before the sample is |
226 | * heard. |
227 | */ |
228 | |
229 | au_data *au_fetch(au_sample *s) |
230 | { |
231 | dstr d = DSTR_INIT; |
232 | struct stat st; |
233 | const char *fn; |
234 | size_t n; |
235 | ssize_t r; |
236 | au_data *a; |
237 | int fd; |
238 | |
239 | /* --- If we already have the sample data --- * |
240 | * |
241 | * If it's currently languishing in the spare bin, rescue it. If this |
242 | * doesn't work, we can release the audio lock, because nothing else messes |
243 | * with the spare list. |
244 | */ |
245 | |
246 | ausys_lock(); |
247 | a = s->a; |
248 | if (a) { |
249 | if (!a->ref) { |
250 | assert(a->next); |
251 | a->prev->next = a->next; |
252 | a->next->prev = a->prev; |
253 | a->next = a->prev = 0; |
254 | } |
255 | a->ref++; |
256 | T( trace(T_AU, "au: reusing sample `%s'", SYM_NAME(s)); ) |
257 | ausys_unlock(); |
258 | return (a); |
259 | } |
260 | ausys_unlock(); |
261 | |
262 | /* --- Read the file --- * |
263 | * |
264 | * Buffered I/O will just involve more copying. |
265 | */ |
266 | |
267 | T( trace(T_AU, "au: fetching sample `%s'", SYM_NAME(s)); ) |
268 | fn = filename(SYM_NAME(s)); |
269 | if ((fd = open(fn, O_RDONLY)) < 0) { |
270 | err_report(ERR_AUDIO, ERRAU_OPEN, errno, |
271 | "couldn't open sample `%s': %s", fn, strerror(errno)); |
272 | goto fail_0; |
273 | } |
274 | if (fstat(fd, &st)) { |
275 | err_report(ERR_AUDIO, ERRAU_OPEN, errno, |
276 | "couldn't fstat `%s': %s", fn, strerror(errno)); |
277 | goto fail_1; |
278 | } |
279 | n = st.st_size + 1; |
280 | if (n < 4096) |
281 | n = 4096; |
282 | for (;;) { |
283 | dstr_ensure(&d, n); |
284 | again: |
285 | if ((r = read(fd, d.buf + d.len, n)) < 0) { |
286 | if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) |
287 | goto again; |
288 | err_report(ERR_AUDIO, ERRAU_OPEN, errno, |
289 | "couldn't read `%s': %s", fn, strerror(errno)); |
290 | goto fail_1; |
291 | } |
292 | if (!r) |
293 | break; |
294 | d.len += r; |
295 | n -= r; |
296 | if (!n) |
297 | n = d.len; |
298 | } |
299 | close(fd); |
300 | |
301 | /* --- Convert it into internal form --- */ |
302 | |
303 | if ((a = ausys_decode(s, d.buf, d.len)) == 0) |
304 | goto fail_0; |
305 | au_sz += a->sz; |
306 | a->ref = 1; |
307 | a->next = a->prev = 0; |
308 | a->s = s; |
309 | s->a = a; |
310 | |
311 | /* --- Done --- */ |
312 | |
313 | ausys_lock(); |
314 | prune(); |
315 | ausys_unlock(); |
316 | goto done; |
317 | done: |
318 | dstr_destroy(&d); |
319 | return (a); |
320 | |
321 | /* --- Tidy up after botched file I/O --- */ |
322 | |
323 | fail_1: |
324 | close(fd); |
325 | fail_0: |
326 | dstr_destroy(&d); |
327 | return (0); |
328 | } |
329 | |
330 | /* --- @au_queue@ --- * |
331 | * |
332 | * Arguments: @au_sample *s@ = sample pointer |
333 | * |
334 | * Returns: Zero on success, nonzero on failure. |
335 | * |
336 | * Use: Queues a sample to be played. |
337 | */ |
338 | |
339 | int au_queue(au_sample *s) |
340 | { |
341 | au_data *a; |
342 | |
343 | assert(s); |
344 | if ((a = au_fetch(s)) == 0) |
345 | return (-1); |
346 | T( trace(T_AU, "au: queuing sample `%s'", SYM_NAME(s)); ) |
347 | ausys_queue(s->a); |
348 | return (0); |
349 | } |
350 | |
351 | /* --- @au_free@, @au_free_unlocked@ --- * |
352 | * |
353 | * Arguments: @au_data *a@ = pointer to audio data block |
354 | * |
355 | * Returns: --- |
356 | * |
357 | * Use: Frees a sample data block when it's no longer required. |
358 | */ |
359 | |
360 | void au_free(au_data *a) |
361 | { |
362 | ausys_lock(); |
363 | au_free_unlocked(a); |
364 | ausys_unlock(); |
365 | } |
366 | |
367 | void au_free_unlocked(au_data *a) |
368 | { |
369 | /* --- If the sample is unreferenced, throw it in the spare bin --- * |
370 | * |
371 | * This can be called from a background audio processing thread, so we need |
372 | * to acquire the lock. |
373 | */ |
374 | |
375 | assert(a->ref); |
376 | assert(!a->next); |
377 | assert(!a->prev); |
378 | a->ref--; |
379 | if (a->ref) { |
380 | T( trace(T_AU, "au: drop ref to `%s' (other refs remain)", |
381 | SYM_NAME(a->s)); ) |
382 | ; |
383 | } else { |
384 | a->next = AU_SPARE; |
385 | a->prev = AU_SPARE->prev; |
386 | AU_SPARE->prev->next = a; |
387 | AU_SPARE->prev = a; |
388 | T( trace(T_AU, "au: drop last ref to `%s'", SYM_NAME(a->s)); ) |
389 | prune(); |
390 | } |
391 | } |
392 | |
393 | /* --- @au_play@, @au_tryplay@ --- * |
394 | * |
395 | * Arguments: @const char *tag@ = sample tag string |
396 | * |
397 | * Returns: Zero on success, nonzero on failure. |
398 | * |
399 | * Use: Convenience functions for queueing samples by tag. |
400 | * If @au_tryplay@ cannot find the requested sample, it returns |
401 | * a zero value; if @au_play@ cannot find the sample, it reports |
402 | * an error. |
403 | */ |
404 | |
405 | int au_tryplay(const char *tag) |
406 | { |
407 | au_sample *s; |
408 | |
409 | if (!(au_flags & f_init)) |
410 | return (0); |
411 | if ((s = au_find(tag)) == 0 || au_queue(s)) |
412 | return (-1); |
413 | return (0); |
414 | } |
415 | |
416 | int au_play(const char *tag) |
417 | { |
418 | int rc; |
419 | |
420 | if ((rc = au_tryplay(tag)) != 0) |
421 | err_report(ERR_AUDIO, ERRAU_NOTFOUND, 0, "sample `%s' not found", tag); |
422 | return (rc); |
423 | } |
424 | |
425 | /*----- That's all, folks -------------------------------------------------*/ |