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