1 /*
2  * ============================================================================
3  *  Title:    Sound Interface Abstraction
4  *  Author:   J. Zbiciak
5  * ============================================================================
6  *
7  * ============================================================================
8  *  SND_TICK     -- Update state of the sound universe.  Drains audio data
9  *                  from the PSGs
10  *  SND_REGISTER -- Registers a PSG with the SND module.
11  *  SND_INIT     -- Initialize a SND_T
12  * ============================================================================
13  */
14 
15 #include "config.h"
16 #include "periph/periph.h"
17 #include "snd.h"
18 #include "avi/avi.h"
19 
20 LOCAL int32_t *mixbuf = NULL;
21 LOCAL uint32_t snd_tick(periph_t *const periph, uint32_t len);
22 
23 /* ======================================================================== */
24 /*  SND Private structure                                                   */
25 /* ======================================================================== */
26 typedef struct snd_pvt_t
27 {
28     avi_writer_t    *avi;
29 } snd_pvt_t;
30 
31 /* ======================================================================== */
32 /*  WAV header.                                                             */
33 /* ======================================================================== */
34 LOCAL const uint8_t snd_wav_hdr[44] =
35 {
36     0x52, 0x49, 0x46, 0x46, /*  0  "RIFF"                                   */
37     0x00, 0x00, 0x00, 0x00, /*  4  Total file length - 8                    */
38     0x57, 0x41, 0x56, 0x45, /*  8  "WAVE"                                   */
39     0x66, 0x6D, 0x74, 0x20, /* 12  "fmt "                                   */
40     0x10, 0x00, 0x00, 0x00, /* 16  0x10 == PCM                              */
41     0x01, 0x00,             /* 20  0x01 == PCM                              */
42     0x01, 0x00,             /* 22  1 channel                                */
43     0x00, 0x00, 0x00, 0x00, /* 24  Sample rate                              */
44     0x00, 0x00, 0x00, 0x00, /* 28  Byte rate:  sample rate * block align    */
45     0x02, 0x00,             /* 30  Block align:  channels * bits/sample / 8 */
46     0x10, 0x00,             /* 32  Bits/sample = 16.                        */
47     0x64, 0x61, 0x74, 0x61, /* 36  "data"                                   */
48     0x00, 0x00, 0x00, 0x00  /* 40  Total sample data length                 */
49 };
50 
51 /* ======================================================================== */
52 /*  SND_UPDATE_WAV_HDR -- Keep the WAV header up to date.  Stupid format.   */
53 /* ======================================================================== */
snd_update_wav_hdr(FILE * f,int rate)54 LOCAL void snd_update_wav_hdr(FILE *f, int rate)
55 {
56     long     filepos;
57     uint32_t tot_smp_bytes, tot_file_bytes;
58     uint8_t  upd_wav_hdr[44];
59 
60     /* -------------------------------------------------------------------- */
61     /*  Poke the size information into the header both places it appears.   */
62     /*  Do this to a private copy of the header.                            */
63     /* -------------------------------------------------------------------- */
64     memcpy(upd_wav_hdr, snd_wav_hdr, 44);
65 
66     filepos = ftell(f);
67     if (filepos < 0)      /* do nothing if ftell() failed. */
68         return;
69 
70     tot_smp_bytes  = filepos > 44 ? filepos - 44 : 0;
71     tot_file_bytes = filepos >  8 ? filepos -  8 : 0;
72 
73     /* Total file size, minus first 8 bytes */
74     upd_wav_hdr[ 4] = (tot_file_bytes >>  0) & 0xFF;
75     upd_wav_hdr[ 5] = (tot_file_bytes >>  8) & 0xFF;
76     upd_wav_hdr[ 6] = (tot_file_bytes >> 16) & 0xFF;
77     upd_wav_hdr[ 7] = (tot_file_bytes >> 24) & 0xFF;
78 
79     /* Total size of sample-data payload */
80     upd_wav_hdr[40] = (tot_smp_bytes  >>  0) & 0xFF;
81     upd_wav_hdr[41] = (tot_smp_bytes  >>  8) & 0xFF;
82     upd_wav_hdr[42] = (tot_smp_bytes  >> 16) & 0xFF;
83     upd_wav_hdr[43] = (tot_smp_bytes  >> 24) & 0xFF;
84 
85     /* Poke in the sample rate / byte rate too */
86     upd_wav_hdr[24] = (rate           >>  0) & 0xFF;
87     upd_wav_hdr[25] = (rate           >>  8) & 0xFF;
88     upd_wav_hdr[26] = (rate           >> 16) & 0xFF;
89     upd_wav_hdr[27] = (rate           >> 24) & 0xFF;
90     upd_wav_hdr[28] = (rate * 2       >>  0) & 0xFF;
91     upd_wav_hdr[29] = (rate * 2       >>  8) & 0xFF;
92     upd_wav_hdr[30] = (rate * 2       >> 16) & 0xFF;
93     upd_wav_hdr[31] = (rate * 2       >> 24) & 0xFF;
94 
95     /* -------------------------------------------------------------------- */
96     /*  Rewind to the beginning of the file to write the header.  Skip      */
97     /*  it if the seek fails for some reason.                               */
98     /* -------------------------------------------------------------------- */
99     if (fseek(f, 0, SEEK_SET) == 0)
100     {
101         fwrite(upd_wav_hdr, 44, 1, f);
102         fseek(f,  0, SEEK_END);
103     }
104 }
105 
106 /*
107  * ============================================================================
108  *  SND_TICK     -- Update state of the sound universe.  Drains audio data
109  *                  from the PSGs
110  * ============================================================================
111  */
snd_tick(periph_t * const periph,uint32_t len)112 LOCAL uint32_t snd_tick(periph_t *const periph, uint32_t len)
113 {
114     snd_t *const snd = PERIPH_AS(snd_t, periph);
115     int min_num_dirty;
116     int i, j, k, mix;
117     int try_drop = 0, did_drop = 0, dly_drop = 0;
118     int16_t *clean;
119     uint64_t new_now;
120     int not_silent = snd->raw_start;
121     const int avi_active = avi_is_active(snd->pvt->avi);
122 
123     /* -------------------------------------------------------------------- */
124     /*  Check for volume up/down requests.                                  */
125     /* -------------------------------------------------------------------- */
126     if (snd->change_vol == 1) snd->atten -= (snd->atten > 0);
127     if (snd->change_vol == 2) snd->atten += (snd->atten < 32);
128     snd->change_vol = 0;
129 
130     /* -------------------------------------------------------------------- */
131     /*  Trival case:  No sound devices == no work for us.                   */
132     /* -------------------------------------------------------------------- */
133     if (snd->src_cnt == 0)
134         return len;
135 
136     /* -------------------------------------------------------------------- */
137     /*  If all of our buffers are dirty, we can't do anything, so return    */
138     /*  the fact that we've made no progress.                               */
139     /* -------------------------------------------------------------------- */
140     if (snd->mixbuf.num_clean == 0)
141         return 0;
142 
143     /* -------------------------------------------------------------------- */
144     /*  Calculate the minimum number of dirty buffers to process.  We try   */
145     /*  to pull as many dirty buffers as we can while keeping all sources   */
146     /*  synchronized.                                                       */
147     /* -------------------------------------------------------------------- */
148     min_num_dirty = snd->src[0]->num_dirty;
149     for (i = 1; i < snd->src_cnt; i++)
150     {
151         if (min_num_dirty > snd->src[i]->num_dirty)
152             min_num_dirty = snd->src[i]->num_dirty;
153     }
154 
155     /* -------------------------------------------------------------------- */
156     /*  Try to drop everything coming to us.  However, if we're dumping     */
157     /*  sound to a raw audio file or AVI, don't actually drop anything      */
158     /*  until after we've written the audio out to file.                    */
159     /* -------------------------------------------------------------------- */
160     try_drop = min_num_dirty;
161     if (snd->raw_file || avi_active)
162     {
163         dly_drop = try_drop;
164         try_drop = 0;
165     }
166 
167     /* -------------------------------------------------------------------- */
168     /*  Update the drop count by the number of buffers that we can drop     */
169     /*  during this update pass.                                            */
170     /* -------------------------------------------------------------------- */
171     did_drop = try_drop;
172     //snd->mixbuf.tot_drop += did_drop;  /* doesn't make sense to report it */
173 
174     /* -------------------------------------------------------------------- */
175     /*  Merge the dirty buffers together into mix buffers, and place the    */
176     /*  dirty buffers back on the clean list.  This will allows the sound   */
177     /*  devices to continue generating sound while we wait for the mixed    */
178     /*  data to play.                                                       */
179     /* -------------------------------------------------------------------- */
180     assert(try_drop == 0 || dly_drop == 0);
181     for (i = try_drop; i < min_num_dirty; i++)
182     {
183         int clean_idx;
184         /* ---------------------------------------------------------------- */
185         /*  Remove a buffer from the clean list for mixing.                 */
186         /* ---------------------------------------------------------------- */
187         clean_idx = --snd->mixbuf.num_clean;
188         clean = snd->mixbuf.clean[clean_idx];
189         snd->mixbuf.clean[clean_idx] = NULL;    /*  spot bugs!              */
190 
191         /* ---------------------------------------------------------------- */
192         /*  Simple case:  One source -- no mixing required.                 */
193         /* ---------------------------------------------------------------- */
194         if (snd->src_cnt == 1)
195         {
196             int16_t *tmp;
197 
198             /*memcpy(clean, snd->src[0]->dirty[i], snd->buf_size * 2);*/
199 
200             /* ------------------------------------------------------------ */
201             /*  XXX: THIS IS AN EVIL HACK.  I SHOULD BE DOING THE MEMCPY!   */
202             /* ------------------------------------------------------------ */
203             tmp = snd->src[0]->dirty[i];
204             snd->src[0]->dirty[i] = clean;
205             clean = tmp;
206 
207             /* ------------------------------------------------------------ */
208             /*  Handle writing sound to raw files.                          */
209             /* ------------------------------------------------------------ */
210             if (snd->raw_file || !snd->raw_start)
211                 for (j = 0; j < snd->buf_size && !not_silent; j++)
212                     not_silent = clean[j];
213 
214             goto one_source;
215         }
216 
217         /* ---------------------------------------------------------------- */
218         /*  Accumulate all of the source buffers at 32-bit precision.       */
219         /* ---------------------------------------------------------------- */
220         memset(mixbuf, 0, snd->buf_size * sizeof(int));
221         for (j = 0; j < snd->src_cnt; j++)
222         {
223             for (k = 0; k < snd->buf_size; k++)
224                 mixbuf[k] += snd->src[j]->dirty[i][k];
225         }
226 
227         /* ---------------------------------------------------------------- */
228         /*  Saturate the mix results to 16-bit precision and place them     */
229         /*  in the formerly-clean mix buffer.                               */
230         /* ---------------------------------------------------------------- */
231         for (j = 0; j < snd->buf_size; j++)
232         {
233             mix = mixbuf[j];
234             if (mix >  0x7FFF) mix =  0x7FFF;
235             if (mix < -0x8000) mix = -0x8000;
236             clean[j] = mix;
237             not_silent |= mix;
238         }
239 
240         /* ---------------------------------------------------------------- */
241         /*  "Atomically" place this in the dirty buffer list so that the    */
242         /*  snd_fill() routine can get to it.  Handle delayed-drops due to  */
243         /*  file writing here.                                              */
244         /* ---------------------------------------------------------------- */
245 one_source:
246 
247         /* ---------------------------------------------------------------- */
248         /*  If an AVI is open, send the audio to the AVI file.              */
249         /* ---------------------------------------------------------------- */
250         if (avi_active && avi_start_time(snd->pvt->avi) < snd->periph.now)
251             avi_record_audio(snd->pvt->avi, clean, snd->buf_size,
252                              !not_silent);
253 
254         /* ---------------------------------------------------------------- */
255         /*  If we're also writing this out to an audio file, do that last.  */
256         /* ---------------------------------------------------------------- */
257         if (snd->raw_file && not_silent)
258         {
259             fwrite(clean, sizeof(int16_t), snd->buf_size,
260                     snd->raw_file);
261             snd->raw_start = 1;
262         }
263 
264         /* ---------------------------------------------------------------- */
265         /*  If this frame wasn't actually put on the mixer's dirty list     */
266         /*  because we're dropping it, then put it back on the clean list.  */
267         /* ---------------------------------------------------------------- */
268         assert(dly_drop > 0);
269         if (dly_drop > 0)
270         {
271             snd->mixbuf.clean[snd->mixbuf.num_clean++] = clean;
272             dly_drop--;
273             did_drop++;
274         }
275     }
276 
277     /* -------------------------------------------------------------------- */
278     /*  Update our sources with how much we actually _did_ drop.            */
279     /* -------------------------------------------------------------------- */
280     for (i = 0; i < snd->src_cnt; i++)
281     {
282         snd->src[i]->tot_drop += did_drop;
283         if (snd->src[i]->drop >= did_drop)
284             snd->src[i]->drop -= did_drop;
285         else
286             snd->src[i]->drop = 0;
287     }
288 
289     /* -------------------------------------------------------------------- */
290     /*  Now fix up our sources' clean and dirty lists.                      */
291     /* -------------------------------------------------------------------- */
292     for (i = 0; i < snd->src_cnt; i++)
293     {
294         /* ---------------------------------------------------------------- */
295         /*  Place the formerly dirty buffers on the clean list.             */
296         /* ---------------------------------------------------------------- */
297         for (j = 0; j < min_num_dirty; j++)
298             snd->src[i]->clean[snd->src[i]->num_clean++] =
299                 snd->src[i]->dirty[j];
300 
301         /* ---------------------------------------------------------------- */
302         /*  Remove the formerly dirty buffers from the dirty list.          */
303         /* ---------------------------------------------------------------- */
304         snd->src[i]->num_dirty -= min_num_dirty;
305 
306         if (min_num_dirty > 0)
307         {
308             for (j = 0; j < snd->src[i]->num_dirty; j++)
309             {
310                 snd->src[i]->dirty[j] = snd->src[i]->dirty[min_num_dirty + j];
311                 snd->src[i]->dirty[min_num_dirty + j] = NULL;
312             }
313         }
314     }
315 
316     /* -------------------------------------------------------------------- */
317     /*  Finally, figure out how many system ticks this accounted for.       */
318     /* -------------------------------------------------------------------- */
319     snd->samples += min_num_dirty * snd->buf_size;
320     if (snd->time_scale > 0)
321     {
322         new_now = (double)snd->samples * snd->cyc_per_sec * snd->time_scale
323                                                                   / snd->rate;
324         if (new_now >= snd->periph.now)  /* Uhoh... are we slipping away? */
325             len = new_now - snd->periph.now;
326     }
327 
328     /* -------------------------------------------------------------------- */
329     /*  If we're writing a sound sample file, update the file length.       */
330     /* -------------------------------------------------------------------- */
331     if (snd->raw_file && not_silent)
332         snd_update_wav_hdr(snd->raw_file, snd->rate);
333 
334     return len;
335 }
336 
337 /*
338  * ============================================================================
339  *  SND_REGISTER -- Registers a sound input buffer with the sound object
340  * ============================================================================
341  */
snd_register(periph_t * const per,snd_buf_t * const src)342 int snd_register
343 (
344     periph_t    *const per,     /* Sound object.                            */
345     snd_buf_t   *const src      /* Sound input buffer.                      */
346 )
347 {
348     int i;
349     snd_t *const snd = PERIPH_AS(snd_t, per);
350 
351     /* -------------------------------------------------------------------- */
352     /*  Initialize the sound buffer to all 0's and make ourselves parent    */
353     /* -------------------------------------------------------------------- */
354     memset(src, 0, sizeof(snd_buf_t));
355     src->snd       = snd;
356 
357     /* -------------------------------------------------------------------- */
358     /*  Set up its buffers as 'clean'.                                      */
359     /* -------------------------------------------------------------------- */
360     src->num_clean = snd->buf_cnt;
361     src->tot_buf   = snd->buf_cnt;
362     src->num_dirty = 0;
363 
364     src->buf   = CALLOC(int16_t,   snd->buf_size * src->num_clean);
365     src->clean = CALLOC(int16_t *, src->num_clean);
366     src->dirty = CALLOC(int16_t *, src->num_clean);
367 
368     if (!src->buf || !src->clean || !src->dirty)
369     {
370         fprintf(stderr, "snd_register: Out of memory allocating sndbuf.\n");
371         return -1;
372     }
373 
374     for (i = 0; i < src->num_clean; i++)
375     {
376         src->clean[i] = src->buf + i * snd->buf_size;
377         src->dirty[i] = NULL;
378     }
379 
380     /* -------------------------------------------------------------------- */
381     /*  Add this sound source to our list of sound sources.                 */
382     /* -------------------------------------------------------------------- */
383     snd->src_cnt++;
384     snd->src = (snd_buf_t**) realloc(snd->src,
385                                      snd->src_cnt * sizeof(snd_buf_t*));
386     if (!snd->src)
387     {
388         fprintf(stderr, "Error:  Out of memory in snd_register()\n");
389         return -1;
390     }
391     snd->src[snd->src_cnt - 1] = src;
392 
393     return 0;
394 }
395 
396 LOCAL void snd_dtor(periph_t *const p);
397 
398 /*
399  * ============================================================================
400  *  SND_INIT     -- Initialize a SND_T
401  * ============================================================================
402  */
snd_init(snd_t * snd,int rate,char * raw_file,int user_snd_buf_size,int user_snd_buf_cnt,struct avi_writer_t * const avi,int pal_mode,double time_scale)403 int snd_init(snd_t *snd, int rate, char *raw_file,
404              int user_snd_buf_size, int user_snd_buf_cnt,
405              struct avi_writer_t *const avi, int pal_mode, double time_scale)
406 {
407     int i;
408 
409     /* -------------------------------------------------------------------- */
410     /*  Prepare to fill up SND structure.                                   */
411     /* -------------------------------------------------------------------- */
412     memset(snd, 0, sizeof(snd_t));
413     if (!(snd->pvt = CALLOC(snd_pvt_t, 1)))
414     {
415         fprintf(stderr, "snd_init: Out of memory allocating snd_pvt_t.\n");
416         goto fail;
417     }
418 
419     snd->buf_size = user_snd_buf_size > 0 ? user_snd_buf_size
420                   :                         SND_BUF_SIZE_DEFAULT;
421 
422     snd->buf_cnt  = user_snd_buf_cnt  > 0 ? user_snd_buf_cnt
423                   :                         SND_BUF_CNT_DEFAULT;
424 
425 
426     /* -------------------------------------------------------------------- */
427     /*  Hook in AVI writer.                                                 */
428     /* -------------------------------------------------------------------- */
429     snd->pvt->avi         = avi;
430 
431     /* -------------------------------------------------------------------- */
432     /*  Set up SND's internal varables.                                     */
433     /* -------------------------------------------------------------------- */
434     snd->rate             = rate;
435     snd->atten            = 0;
436     snd->cyc_per_sec      = pal_mode ? 1000000 : 894886;
437     snd->time_scale       = time_scale;
438 
439     /* -------------------------------------------------------------------- */
440     /*  Set up SND as a peripheral.                                         */
441     /* -------------------------------------------------------------------- */
442     snd->periph.read      = NULL;
443     snd->periph.write     = NULL;
444     snd->periph.peek      = NULL;
445     snd->periph.poke      = NULL;
446     snd->periph.tick      = snd_tick;
447     snd->periph.min_tick  = snd->buf_size * snd->cyc_per_sec / (2*rate);
448     snd->periph.max_tick  = snd->periph.min_tick * 3;
449     snd->periph.addr_base = ~0U;
450     snd->periph.addr_mask = ~0U;
451     snd->periph.dtor      = snd_dtor;
452 
453     /* -------------------------------------------------------------------- */
454     /*  Set up our mix buffers as 'clean'.                                  */
455     /* -------------------------------------------------------------------- */
456     snd->mixbuf.tot_buf   = snd->buf_cnt;
457     snd->mixbuf.num_clean = snd->buf_cnt;
458     snd->mixbuf.num_dirty = 0;
459     snd->mixbuf.buf   = CALLOC(int16_t, snd->buf_size * snd->mixbuf.num_clean);
460     snd->mixbuf.clean = CALLOC(int16_t *, snd->mixbuf.num_clean);
461     snd->mixbuf.dirty = CALLOC(int16_t *, snd->mixbuf.num_clean);
462 
463     if (mixbuf) free(mixbuf);
464     mixbuf            = CALLOC(int32_t,   snd->buf_size);
465 
466     if (!snd->mixbuf.buf || !snd->mixbuf.clean || !snd->mixbuf.dirty || !mixbuf)
467     {
468         fprintf(stderr, "snd_init: Out of memory allocating mixbuf.\n");
469         goto fail;
470     }
471 
472     for (i = 0; i < snd->mixbuf.num_clean; i++)
473         snd->mixbuf.clean[i] = snd->mixbuf.buf + i * snd->buf_size;
474 
475     /* -------------------------------------------------------------------- */
476     /*  If the user is dumping audio to a raw-audio file, open 'er up.      */
477     /* -------------------------------------------------------------------- */
478     if (raw_file)
479     {
480         snd->raw_file = fopen(raw_file, "wb");
481         if (!snd->raw_file)
482         {
483             fprintf(stderr,"snd:  Error opening '%s' for writing.\n",raw_file);
484             perror("fopen");
485             goto fail;
486         }
487         snd_update_wav_hdr(snd->raw_file, snd->rate);
488         return 0;
489     }
490 
491     return 0;
492 
493 
494 fail:
495     CONDFREE(snd->pvt);
496     CONDFREE(snd->mixbuf.buf);
497     CONDFREE(snd->mixbuf.clean);
498     CONDFREE(snd->mixbuf.dirty);
499     CONDFREE(mixbuf);
500 
501     return -1;
502 }
503 
504 /* ======================================================================== */
505 /*  SND_DTOR     -- Tear down the sound device.                             */
506 /* ======================================================================== */
snd_dtor(periph_t * const p)507 LOCAL void snd_dtor(periph_t *const p)
508 {
509     snd_t *const snd = PERIPH_AS(snd_t, p);
510     int i;
511 
512     CONDFREE(mixbuf);
513     CONDFREE(snd->mixbuf.buf);
514     CONDFREE(snd->mixbuf.clean);
515     CONDFREE(snd->mixbuf.dirty);
516 
517     CONDFREE(snd->pvt);
518 
519     for (i = 0; i < snd->src_cnt; i++)
520         if (snd->src[i])
521         {
522             CONDFREE(snd->src[i]->buf);
523             CONDFREE(snd->src[i]->clean);
524             CONDFREE(snd->src[i]->dirty);
525         }
526 
527     CONDFREE(snd->src);
528 }
529 
530 /*
531  * ============================================================================
532  *  SND_PLAY_SILENCE -- Pump silent audio frame. (Used during reset.)
533  *  SND_PLAY_STATIC  -- A silly bit of fun.
534  * ============================================================================
535  */
snd_play_silence(snd_t * const snd)536 void snd_play_silence(snd_t *const snd) { UNUSED(snd); }
snd_play_static(snd_t * const snd)537 void snd_play_static (snd_t *const snd) { UNUSED(snd); }
538 
539 /* ======================================================================== */
540 /*  This program is free software; you can redistribute it and/or modify    */
541 /*  it under the terms of the GNU General Public License as published by    */
542 /*  the Free Software Foundation; either version 2 of the License, or       */
543 /*  (at your option) any later version.                                     */
544 /*                                                                          */
545 /*  This program is distributed in the hope that it will be useful,         */
546 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of          */
547 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       */
548 /*  General Public License for more details.                                */
549 /*                                                                          */
550 /*  You should have received a copy of the GNU General Public License along */
551 /*  with this program; if not, write to the Free Software Foundation, Inc., */
552 /*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             */
553 /* ======================================================================== */
554 /*                 Copyright (c) 1998-1999, Joseph Zbiciak                  */
555 /* ======================================================================== */
556