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