1 /*
2 * ux_audio.c - Unix interface, sound support
3 *
4 * This file is part of Frotz.
5 *
6 * Frotz is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * Frotz is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 * Or visit http://www.fsf.org/
20 *
21 * This file and only this file is dual licensed under the MIT license.
22 *
23 * Copyright (c) 2019 Mark McCurry
24 */
25
26 #define __UNIX_PORT_FILE
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <pthread.h>
32 #include <assert.h>
33 #include <unistd.h> //pread
34
35 #include "ux_defines.h"
36
37 #ifdef USE_NCURSES_H
38 #include <ncurses.h>
39 #else
40 #include <curses.h>
41 #endif
42
43 #include "ux_sema.h"
44 #include "ux_frotz.h"
45 #include "ux_blorb.h"
46 #include "ux_audio.h"
47
48 extern f_setup_t f_setup;
49 extern z_header_t z_header;
50
51 #ifndef NO_SOUND
52
53 ux_sem_t sound_done; /* 1 if the sound is done */
54
55 #include <ao/ao.h>
56 #include <sndfile.h>
57 #include <samplerate.h>
58 #include <libmodplug/modplug.h>
59
60 /* Exports
61 * void os_init_sound(void); startup system
62 * void os_beep(int); enqueue a beep sample
63 * void os_prepare_sample(int); put a sample into memory
64 * void os_start_sample(int, int, int, zword); queue up a sample
65 * void os_stop_sample(int); terminate sample
66 * void os_finish_with_sample(int); remove from memory
67 */
68
69 #define EVENT_START_STREAM 1
70 #define EVENT_STOP_STREAM 2
71
72 typedef struct {
73 SRC_STATE *src_state;
74 SRC_DATA src_data;
75 float *scratch;
76 float *input;
77 float *output;
78 } resampler_t;
79
80
81 typedef struct {
82 bool active; /* If a voice is actively outputting sound*/
83 int src; /* The source sound ID*/
84 int type; /* The voice type 0, 1, 2..N*/
85 int pos; /* The current position*/
86 int repid; /* The current number of repetitions*/
87 } sound_state_t;
88
89
90 typedef struct {
91 float *samples;
92 int nsamples;
93 } sound_buffer_t;
94
95 typedef enum {
96 FORM,
97 OGGV,
98 MOD
99 } sound_type_t;
100
101 typedef struct sound_stream {
102 /* returns 1 if process can continue */
103 int (*process)(struct sound_stream *self, float *outl, float *outr, unsigned samples);
104 void (*cleanup)(struct sound_stream *self);
105 sound_type_t sound_type;
106 int id;
107 } sound_stream_t;
108
109 typedef struct {
110 int (*process)(sound_stream_t *self, float *outl, float *outr, unsigned samples);
111 void (*cleanup)(sound_stream_t *self);
112 sound_type_t sound_type;
113 int id;
114 } sound_stream_dummy_t;
115
116 typedef struct {
117 uint8_t *data;
118 size_t len;
119 size_t pos;
120 } buf_t;
121
122 typedef struct {
123 FILE *file;
124 size_t base_offset;
125 size_t len;
126 size_t pos;
127 } file_reader_t;
128
129 typedef struct {
130 int (*process)(sound_stream_t *self, float *outl, float *outr, unsigned samples);
131 void (*cleanup)(sound_stream_t *self);
132 sound_type_t sound_type;
133 int id;
134
135 resampler_t *rsmp;
136 float volume;
137 float *floatbuffer;
138
139 SNDFILE *sndfile;
140 SF_INFO sf_info;
141 size_t length; /* Number of samples (= smpsl.length == smpsr.length) */
142 int repeats; /* Total times to play the sample 1..n */
143 int pos;
144
145 file_reader_t freader;
146 } sound_stream_aiff_t;
147
148 typedef struct {
149 int (*process)(sound_stream_t *self, float *outl, float *outr, unsigned samples);
150 void (*cleanup)(sound_stream_t *self);
151 sound_type_t sound_type;
152 int id;
153
154 char *filedata;
155 short *shortbuffer;
156 ModPlugFile *mod;
157 ModPlug_Settings settings;
158 } sound_stream_mod_t;
159
160 typedef struct {
161 uint8_t type;
162 union {
163 sound_stream_t *e;
164 int i;
165 };
166 } sound_event_t;
167
168 #define NUM_VOICES 8
169
170 typedef struct {
171 /* Audio driver parameters */
172 size_t buffer_size;
173 float sample_rate;
174
175 /* Output buffers */
176 float *outl;
177 float *outr;
178
179 /* Sound parameters */
180 sound_stream_t *streams[NUM_VOICES]; /* Active streams */
181 sound_state_t voices[NUM_VOICES]; /* Max concurrent sound effects/music */
182
183 /* Event (one is process per frame of audio) */
184 ux_sem_t ev_free; /* 1 if an event can be submitted */
185 ux_sem_t ev_pending; /* 1 if there's an event ready to be processed */
186 sound_event_t event;
187 } sound_engine_t;
188
189 static sound_engine_t frotz_audio;
190 /* FILE *audio_log; */
191
192 /**********************************************************************
193 * Utilities *
194 * *
195 * getfiledata - get all bytes of a file after *
196 * the start point *
197 * make_id - Create BLORB identifier *
198 * get_type - Get OGG/FORM/AIFF type *
199 * limit - x -> a <= x <= b *
200 * *
201 **********************************************************************/
202 static char *
getfiledata(FILE * fp,long * size)203 getfiledata(FILE *fp, long *size)
204 {
205 long offset = ftell(fp);
206 fseek(fp, 0L, SEEK_END);
207 (*size) = ftell(fp);
208 fseek(fp, offset, SEEK_SET);
209 char *data = (char*)malloc(*size);
210 fread(data, *size, sizeof(char), fp);
211 fseek(fp, offset, SEEK_SET);
212 return(data);
213 }
214
215
216 static
make_id(uint8_t a,uint8_t b,uint8_t c,uint8_t d)217 int32_t make_id(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
218 {
219 return (a << 24) | (b << 16) | (c << 8) | d;
220 }
221
222
223 static int
get_type(int magic)224 get_type(int magic)
225 {
226 /*fprintf(audio_log, "magic = %x\n", magic);*/
227 if (magic == make_id('F','O','R','M'))
228 return FORM;
229 if (magic == make_id('M','O','D',' '))
230 return MOD;
231 if (magic == make_id('O','G','G','V'))
232 return OGGV;
233 return -1;
234 }
235
236
237 static float
limit(float mn,float mx,float v)238 limit(float mn, float mx, float v)
239 {
240 if (v<mn) return mn;
241 if (v>mx) return mx;
242 return v;
243 }
244
245
246 /**********************************************************************
247 * Resampler *
248 * *
249 * Processes data input at one sampling rate and converts to another. *
250 * Used on ogg and aiff streams. *
251 * *
252 * NOTE: Additional code may be needed to smoothly handle repeat loop *
253 * conditions *
254 * *
255 * resampler_init - Create resampler *
256 * resampler_cleanup - Deallocate resampler resources
257 * resampler_step - Add data to resampler *
258 * resampler_consume - Remove data from resampler *
259 **********************************************************************/
260
261 static resampler_t*
resampler_init(int sample_rate_input)262 resampler_init(int sample_rate_input)
263 {
264 resampler_t *rsmp = (resampler_t*)calloc(sizeof(resampler_t), 1);
265 int error;
266 rsmp->src_state = src_new(SRC_SINC_FASTEST, 2, &error);
267 rsmp->input = (float*)calloc(frotz_audio.buffer_size, sizeof(float)*2);
268 rsmp->output = (float*)calloc(frotz_audio.buffer_size, sizeof(float)*2);
269 rsmp->scratch = (float*)calloc(frotz_audio.buffer_size, sizeof(float)*2);
270 rsmp->src_data.src_ratio = frotz_audio.sample_rate*1.0f/sample_rate_input;
271 rsmp->src_data.input_frames = 0;
272 rsmp->src_data.output_frames = frotz_audio.buffer_size;
273 rsmp->src_data.data_in = rsmp->input;
274 rsmp->src_data.data_out = rsmp->output;
275 rsmp->src_data.end_of_input = 0;
276
277 return rsmp;
278 }
279 static void
resampler_cleanup(resampler_t * rsmp)280 resampler_cleanup(resampler_t *rsmp)
281 {
282 src_delete(rsmp->src_state);
283 free(rsmp->input);
284 free(rsmp->output);
285 free(rsmp->scratch);
286 }
287
288
289 /*0 done running, 1 run again with more data*/
290 static int
resampler_step(resampler_t * rsmp,float * block)291 resampler_step(resampler_t *rsmp, float *block)
292 {
293 /*Always stereo*/
294 const int channels = 2;
295 const int smps = frotz_audio.buffer_size;
296 if (block) {
297 assert(rsmp->src_data.input_frames == 0);
298 memcpy(rsmp->input, block, channels*smps*sizeof(float));
299 rsmp->src_data.data_in = rsmp->input;
300 rsmp->src_data.input_frames = smps;
301 }
302 int err = src_process(rsmp->src_state, &rsmp->src_data);
303 assert(err == 0);
304
305 int u_in = rsmp->src_data.input_frames_used;
306 rsmp->src_data.data_in += 2*u_in;
307 rsmp->src_data.input_frames -= u_in;
308 /*
309 * If input buffer is empty, reset data_in pointer just in case
310 * the output buffer is also full.
311 */
312 if (rsmp->src_data.input_frames == 0)
313 rsmp->src_data.data_in = rsmp->input;
314 int g_out = rsmp->src_data.output_frames_gen;
315 rsmp->src_data.data_out += 2*g_out;
316 rsmp->src_data.output_frames -= g_out;
317
318 if (rsmp->src_data.output_frames == 0)
319 return 0;
320 return 1;
321 }
322
323
324 static void
resampler_consume(resampler_t * rsmp)325 resampler_consume(resampler_t *rsmp)
326 {
327 rsmp->src_data.data_out = rsmp->output;
328 rsmp->src_data.output_frames = frotz_audio.buffer_size;
329 }
330
331
332 /**********************************************************************
333 * MOD *
334 * *
335 * Processes MOD data via libmodplug *
336 * *
337 * process_mod - Generate MOD samples *
338 * cleanup_mod - Free MOD resources *
339 * load_mod - Create MOD stream *
340 **********************************************************************/
341
342 static int
process_mod(sound_stream_t * self_,float * outl,float * outr,unsigned samples)343 process_mod(sound_stream_t *self_, float *outl, float *outr, unsigned samples)
344 {
345 sound_stream_mod_t *self = (sound_stream_mod_t*)self_;
346
347 int n = ModPlug_Read(self->mod, self->shortbuffer, samples*4) / 4;
348 const float scale = (1.0f/32768.0f);/*volfactor;*/
349 int i;
350 for (i=0; i<n; ++i) {
351 outl[i] += scale*self->shortbuffer[i*2+0];
352 outr[i] += scale*self->shortbuffer[i*2+1];
353 }
354
355 if (n <= 0)
356 return 0;
357
358 return 1;
359 }
360
361 static void
cleanup_mod(sound_stream_t * s)362 cleanup_mod(sound_stream_t *s)
363 {
364 sound_stream_mod_t *self = (sound_stream_mod_t*)s;
365 ModPlug_Unload(self->mod);
366 free(self->shortbuffer);
367 free(self->filedata);
368 }
369
370
371 /*file, data start, id, volume*/
372 static sound_stream_t *
load_mod(FILE * fp,long startpos,int id,float volume)373 load_mod(FILE *fp, long startpos, int id, float volume)
374 {
375 sound_stream_mod_t *stream = (sound_stream_mod_t*)calloc(sizeof(sound_stream_mod_t), 1);
376 long size;
377 long filestart = ftell(fp);
378 fseek(fp, startpos, SEEK_SET);
379
380 stream->id = id;
381 stream->sound_type = MOD;
382 stream->process = process_mod;
383 stream->cleanup = cleanup_mod;
384
385 ModPlug_GetSettings(&stream->settings);
386
387 /* Note: All "Basic Settings" must be set before ModPlug_Load. */
388 stream->settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */
389 stream->settings.mChannels = 2;
390 stream->settings.mBits = 16;
391 stream->settings.mFrequency = frotz_audio.sample_rate;
392 stream->settings.mStereoSeparation = 128;
393 stream->settings.mMaxMixChannels = 256;
394
395 /* insert more setting changes here */
396 ModPlug_SetSettings(&stream->settings);
397
398 /* remember to free() filedata later */
399 stream->filedata = getfiledata(fp, &size);
400
401 stream->mod = ModPlug_Load(stream->filedata, size);
402 fseek(fp, filestart, SEEK_SET);
403 if (!stream->mod) {
404 fprintf(stderr, "Unable to load MOD chunk.\n\r");
405 return 0;
406 }
407
408 ModPlug_SetMasterVolume(stream->mod, volume * 256);/*powf(2.0f, 8.0f));*/
409
410 stream->shortbuffer = (int16_t*)calloc(frotz_audio.buffer_size, sizeof(short) * 2);
411
412 return (sound_stream_t*)stream;
413 }
414
415 /**********************************************************************
416 * AIFF/OGG *
417 * *
418 * Processes OGG/AIFF data via sndfile + resampler *
419 * *
420 * process_aiff - Create OGG/AIFF samples *
421 * cleanup_aiff - Free OGG/AIFF resources *
422 * mem_snd_read - In memory read *
423 * mem_snd_seek - In memory seek *
424 * mem_tell - In memory tell *
425 * mem_get_filelen - In memory filelen *
426 * load_aiff - Create OGG/AIFF stream *
427 * *
428 **********************************************************************/
429
430 static int
process_aiff(sound_stream_t * self_,float * outl,float * outr,unsigned samples)431 process_aiff(sound_stream_t *self_, float *outl, float *outr, unsigned samples)
432 {
433 sound_stream_aiff_t *self = (sound_stream_aiff_t*)self_;
434
435 int needs_data = resampler_step(self->rsmp, 0);
436 int i;
437 unsigned remaining_samples = samples;
438
439 while(needs_data) {
440 int inf = sf_readf_float(self->sndfile,
441 self->floatbuffer, remaining_samples);
442 if (self->sf_info.channels == 1) {
443 for (i=0; i<inf; ++i) {
444 self->rsmp->scratch[2*i+0+2*(samples-remaining_samples)] =
445 self->floatbuffer[i];
446 self->rsmp->scratch[2*i+1+2*(samples-remaining_samples)] =
447 self->floatbuffer[i];
448 }
449 } else if (self->sf_info.channels == 2) {
450 for (i=0; i<inf; ++i) {
451 self->rsmp->scratch[2*i+0+2*(samples-remaining_samples)] =
452 self->floatbuffer[2*i+0];
453 self->rsmp->scratch[2*i+1+2*(samples-remaining_samples)] =
454 self->floatbuffer[2*i+1];
455 }
456 }
457 /*
458 * If the read function didn't fill the scratch buffer, see if
459 * there are more repeats and if so, continue filling the scratch
460 * buffer, a repeat value of 255 means repeat forever
461 */
462 if (inf < (int)remaining_samples) {
463 if (self->repeats<255)
464 self->repeats--;
465 if (self->repeats > 0){
466 /*
467 * Repeating... Seek back to the beginning of the sound
468 * and allow the read to get enough samples to fill the
469 * scratch buffer, and continue with the while loop
470 */
471 sf_seek(self->sndfile,0, SEEK_SET);
472 if ( inf == (int)remaining_samples)
473 remaining_samples = samples;
474 else
475 remaining_samples = remaining_samples - inf;
476 continue;
477 } else if (inf <= 0) {
478 /*
479 * No repeats and no data left in the sound file,
480 * return 0 to tell the next level up that the sound
481 * is done
482 */
483 return 0;
484 } else {
485 /*
486 * No repeats but there was data read, set things back
487 * up so that the maximum buffer size can be read, but
488 * fall through to make sure the data is resampled
489 */
490 remaining_samples = samples;
491 }
492 }
493 needs_data = resampler_step(self->rsmp, self->rsmp->scratch);
494 }
495 resampler_consume(self->rsmp);
496
497 for (i=0; i<(int)samples; ++i) {
498 outl[i] += self->rsmp->output[2*i+0]*self->volume;
499 outr[i] += self->rsmp->output[2*i+1]*self->volume;
500 }
501
502 return 1;
503 }
504
505
506 static void
cleanup_aiff(sound_stream_t * s)507 cleanup_aiff(sound_stream_t *s)
508 {
509 sound_stream_aiff_t *self = (sound_stream_aiff_t*)s;
510
511 /* Cleanup frame */
512 resampler_cleanup(self->rsmp);
513 free(self->rsmp);
514 sf_close(self->sndfile);
515 free(self->floatbuffer);
516 }
517
518
519 static sf_count_t
mem_snd_read(void * ptr_,sf_count_t size,void * datasource)520 mem_snd_read(void *ptr_, sf_count_t size, void* datasource)
521 {
522 uint8_t *ptr = (uint8_t*)ptr_;
523 file_reader_t *fr = (file_reader_t *)datasource;
524 size_t to_read = size;
525 size_t read_total = 0;
526 ssize_t did_read = 0;
527 while (to_read > 0) {
528 did_read = pread(fileno(fr->file), ptr, size, fr->pos+fr->base_offset);
529 if (did_read < 0)
530 return did_read;
531 else if (did_read == 0)
532 return read_total;
533 read_total += did_read;
534 fr->pos += did_read;
535 ptr += did_read;
536 to_read -= did_read;
537 }
538 return read_total;
539 }
540
541
542 static sf_count_t
mem_snd_seek(sf_count_t offset,int whence,void * datasource)543 mem_snd_seek(sf_count_t offset, int whence, void *datasource) {
544 file_reader_t *fr = (file_reader_t *)datasource;
545 int64_t pos = 0;
546 if (whence == SEEK_SET)
547 pos = offset;
548 if (whence == SEEK_CUR)
549 pos += offset;
550 if (whence == SEEK_END)
551 pos = fr->len-offset;
552 if (pos >= (int64_t)fr->len)
553 pos = fr->len-1;
554 if (pos < 0)
555 pos = 0;
556 fr->pos = pos;
557
558 return 0;
559 }
560
561
562 static long
mem_tell(void * datasource)563 mem_tell(void *datasource) {
564 file_reader_t *fr = (file_reader_t*)datasource;
565 return fr->pos;
566 }
567
568
569 static sf_count_t
mem_get_filelen(void * datasource)570 mem_get_filelen(void *datasource)
571 {
572 file_reader_t *fr = (file_reader_t*)datasource;
573 return fr->len;
574 }
575
576
577 static sound_stream_t *
load_aiff(FILE * fp,long startpos,long length,int id,float volume,int repeats)578 load_aiff(FILE *fp, long startpos, long length, int id, float volume, int repeats)
579 {
580 sound_stream_aiff_t *aiff =
581 (sound_stream_aiff_t*)calloc(sizeof(sound_stream_aiff_t), 1);
582 aiff->sound_type = FORM;
583 aiff->id = id;
584 aiff->process = process_aiff;
585 aiff->cleanup = cleanup_aiff;
586 aiff->repeats = repeats;
587
588 aiff->volume = volume;
589 aiff->sf_info.format = 0;
590
591 fseek(fp, startpos, SEEK_SET);
592 aiff->freader.file = fp;
593 aiff->freader.pos = 0;
594 aiff->freader.len = length;
595 aiff->freader.base_offset = startpos;
596
597 SF_VIRTUAL_IO mem_cb = {
598 .seek = mem_snd_seek,
599 .read = mem_snd_read,
600 .tell = (sf_vio_tell)mem_tell,
601 .get_filelen = mem_get_filelen,
602 .write = NULL
603 };
604
605 aiff->sndfile = sf_open_virtual(&mem_cb, SFM_READ,
606 &aiff->sf_info, &aiff->freader);
607 aiff->rsmp = resampler_init(aiff->sf_info.samplerate);
608
609 aiff->floatbuffer = (float*)malloc(frotz_audio.buffer_size *
610 aiff->sf_info.channels * sizeof(float));
611
612 return (sound_stream_t*) aiff;
613 }
614
615
616
617
618 /**********************************************************************
619 * Sound Engine *
620 * *
621 * Processes OGG/AIFF data via sndfile + resampler *
622 * *
623 * process_engine - Create a frame of output *
624 * audio_loop - Stream audio to sound device *
625 * sound_halt_aiff - Stop all AIFF voices *
626 * sound_halt_mod - Stop all MOD voices *
627 * sound_halt_ogg - Stop all OGG voices *
628 * sound_stop_id - Proxy to stop an id *
629 * sound_stop_id_real - Stop a given stream id *
630 * sound_enqueue - Proxy to start a stream obj *
631 * sound_enqueue_real - Start a stream obj *
632 * volume_factor - Convert volume to scalar multiplier *
633 **********************************************************************/
634
635 static void
636 sound_enqueue_real(sound_engine_t *e, sound_stream_t *s);
637 static void
638 sound_stop_id_real(sound_engine_t *e, int id);
639
640 static void
process_engine(sound_engine_t * e)641 process_engine(sound_engine_t *e)
642 {
643 int i;
644 /* Handle event */
645 if (ux_sem_trywait(&e->ev_pending) == 0) {
646 if (e->event.type == EVENT_START_STREAM)
647 sound_enqueue_real(e,e->event.e);
648 else if (e->event.type == EVENT_STOP_STREAM)
649 sound_stop_id_real(e,e->event.i);
650 ux_sem_post(&e->ev_free);
651 }
652
653 /* Start out with an empty buffer */
654 memset(e->outl, 0, sizeof(float)*e->buffer_size);
655 memset(e->outr, 0, sizeof(float)*e->buffer_size);
656
657 for (i=0; i<8; ++i) {
658 sound_state_t *state = &e->voices[i];
659
660 /* Only process active voices */
661 if (!state->active)
662 continue;
663
664 sound_stream_t *sound = e->streams[i];
665
666 if (sound) {
667 int ret = sound->process(sound, e->outl, e->outr, e->buffer_size);
668 if (ret == 0) {
669 /* fprintf(audio_log, "stream #%d is complete\n", i); */
670 state->active = false;
671 sound->cleanup(sound);
672 free(sound);
673 e->streams[i] = NULL;
674 ux_sem_post(&sound_done);
675 }
676 }
677 }
678 }
679
680
681 static void*
audio_loop(void * v)682 audio_loop(void*v)
683 {
684 (void) v;
685 size_t outsize = frotz_audio.buffer_size*2*sizeof(int16_t);
686 int16_t *buf = (int16_t*)calloc(outsize,1);
687 int i;
688 ao_device *device;
689 ao_sample_format format;
690 ao_initialize();
691 int default_driver = ao_default_driver_id();
692
693 memset(&format, 0, sizeof(ao_sample_format));
694
695 format.byte_format = AO_FMT_NATIVE;
696 format.bits = 16;
697 format.channels = 2;
698 format.rate = 48000.0f;
699 device = ao_open_live(default_driver, &format, NULL);
700
701 while (1) {
702 process_engine(&frotz_audio);
703
704 const float mul = (32768.0f);
705 for (i=0; i<(int)frotz_audio.buffer_size; ++i) {
706 buf[2*i+0] = limit(-32764,32767,mul*0.8*frotz_audio.outl[i]);
707 buf[2*i+1] = limit(-32764,32767,mul*0.8*frotz_audio.outr[i]);
708 }
709 ao_play(device, (char*)buf, outsize);
710 }
711 return 0;
712 }
713
714
715 static void
sound_halt_aiff(void)716 sound_halt_aiff(void)
717 {
718 int i;
719 for (i=0; i<NUM_VOICES; ++i) {
720 if (frotz_audio.streams[i] && frotz_audio.streams[i]->sound_type == FORM) {
721 /* fprintf(audio_log, "killing aiff stream #%d\n", i); */
722 sound_stream_t *s = frotz_audio.streams[i];
723 frotz_audio.streams[i] = 0;
724 s->cleanup(s);
725 free(s);
726 }
727 }
728 }
729
730
731 static void
sound_halt_mod(void)732 sound_halt_mod(void)
733 {
734 int i;
735 for (i=0; i<NUM_VOICES; ++i) {
736 if (frotz_audio.streams[i] && frotz_audio.streams[i]->sound_type == MOD) {
737 /* fprintf(audio_log, "killing mod stream #%d\n", i); */
738 sound_stream_t *s = frotz_audio.streams[i];
739 frotz_audio.streams[i] = 0;
740 s->cleanup(s);
741 free(s);
742 }
743 }
744 }
745
746
747 static void
sound_halt_ogg(void)748 sound_halt_ogg(void)
749 {
750 int i;
751 for(i=0; i<NUM_VOICES; ++i) {
752 if(frotz_audio.streams[i] && frotz_audio.streams[i]->sound_type == OGGV) {
753 /* fprintf(audio_log, "killing ogg stream #%d\n", i); */
754 sound_stream_t *s = frotz_audio.streams[i];
755 frotz_audio.streams[i] = 0;
756 s->cleanup(s);
757 free(s);
758 }
759 }
760 }
761
762
763 static sound_stream_t *load_mod(FILE *fp, long startpos, int id, float volume);
764 static sound_stream_t *load_aiff(FILE *fp, long startpos, long length, int id, float volume, int repeats);
765
766
767 static void
sound_stop_id(int id)768 sound_stop_id(int id)
769 {
770 ux_sem_wait(&frotz_audio.ev_free);
771 frotz_audio.event.type = EVENT_STOP_STREAM;
772 frotz_audio.event.i = id;
773 ux_sem_post(&frotz_audio.ev_pending);
774 }
775
776
777 static void
sound_stop_id_real(sound_engine_t * e,int id)778 sound_stop_id_real(sound_engine_t *e, int id)
779 {
780 int i;
781 for (i=0; i<NUM_VOICES; ++i) {
782 sound_stream_t *s = e->streams[i];
783 if (s && s->id == id) {
784 /*fprintf(audio_log, "killing stream #%d\n", i);*/
785 e->streams[i] = 0;
786 s->cleanup(s);
787 free(s);
788 }
789 }
790 }
791
792
793 static void
sound_enqueue(sound_stream_t * s)794 sound_enqueue(sound_stream_t *s)
795 {
796 ux_sem_wait(&frotz_audio.ev_free);
797 frotz_audio.event.type = EVENT_START_STREAM;
798 frotz_audio.event.e = s;
799 ux_sem_post(&frotz_audio.ev_pending);
800 }
801
802
803 static void
sound_enqueue_real(sound_engine_t * e,sound_stream_t * s)804 sound_enqueue_real(sound_engine_t *e, sound_stream_t *s)
805 {
806 assert(e);
807 assert(s);
808 int i;
809
810 if (s->sound_type == FORM) {
811 sound_halt_aiff();
812 } else if (s->sound_type == MOD) {
813 sound_halt_mod();
814 sound_halt_ogg();
815 } else if (s->sound_type == OGGV) {
816 sound_halt_mod();
817 sound_halt_ogg();
818 }
819
820 for (i=0; i<NUM_VOICES; ++i) {
821 if (e->streams[i]) /*only use free voices*/
822 continue;
823 /* fprintf(audio_log, "Enqueue %p to %d\n", s, i);*/
824 e->streams[i] = s;
825 e->voices[i].active = true;
826 e->voices[i].src = 0;
827 e->voices[i].pos = 0;
828 e->voices[i].repid = 0;
829 break;
830 }
831 }
832
833
834 static float
volume_factor(int vol)835 volume_factor(int vol)
836 {
837 static float lut[8] = {0.0078125f, 0.015625f, 0.03125f, 0.0625f, 0.125f, 0.25f, 0.5f, 1.0f};
838
839 if (vol < 1) vol = 1;
840 if (vol > 8) vol = 8;
841 return lut[vol-1];
842 /* return powf(2, vol - 8); */
843 }
844
845
846 /**********************************************************************
847 * Public API *
848 * *
849 **********************************************************************/
850
851 void
os_init_sound(void)852 os_init_sound(void)
853 {
854 int i;
855 int err;
856 static pthread_attr_t attr;
857
858 if (!f_setup.sound) return;
859
860 /* Initialize sound engine */
861 /* audio_log = fopen("audio_log.txt", "w"); */
862 /* fprintf(audio_log, "os_init_sound...\n"); */
863 frotz_audio.buffer_size = 1024;
864 frotz_audio.sample_rate = 48000;
865 frotz_audio.outl = (float*)calloc(frotz_audio.buffer_size, sizeof(float));
866 frotz_audio.outr = (float*)calloc(frotz_audio.buffer_size, sizeof(float));
867
868 for(i=0; i<NUM_VOICES; ++i)
869 frotz_audio.voices[i].active = 0;
870
871 /* No events registered on startup */
872 ux_sem_init(&frotz_audio.ev_free, 0, 1);
873 ux_sem_init(&frotz_audio.ev_pending, 0, 0);
874 ux_sem_init(&sound_done, 0, 0);
875 frotz_audio.event.type = 0;
876
877 /* Start audio thread */
878 pthread_attr_init(&attr);
879 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
880
881 pthread_t unused_id;
882 err = pthread_create(&unused_id, &attr, &audio_loop, NULL);
883 if (err != 0) {
884 fprintf(stderr, "Can't create audio thread :[%s]", strerror(err));
885 os_quit(EXIT_FAILURE);
886 }
887 }
888
889
890 /*
891 * os_start_sample
892 *
893 * Play the given sample at the given volume (ranging from 1 to 8 and
894 * 255 meaning a default volume). The sound is played once or several
895 * times in the background (255 meaning forever). In Z-code 3 the
896 * repeats value is always 0 and the number of repeats is taken from
897 * the sound file itself. The end_of_sound function is called as soon
898 * as the sound finishes.
899 *
900 * The end_of_sound function is called in os_tick();
901 *
902 */
903 void
os_start_sample(int number,int volume,int repeats,zword eos)904 os_start_sample(int number, int volume, int repeats, zword eos)
905 {
906 (void) eos;
907 /* fprintf(audio_log, "os_start_sample(%d,%d,%d,%d)...\n",number,volume,repeats, eos); */
908 /* fflush(audio_log); */
909 extern bb_map_t *blorb_map;
910 extern FILE *blorb_fp;
911
912 bb_result_t resource;
913 int type;
914 const float vol = volume_factor(volume);
915 sound_stream_t *s = 0;
916
917 if (!f_setup.sound) return;
918
919 /* Load resource from BLORB data */
920 if (blorb_map == NULL) return;
921
922 if (bb_err_None != bb_load_resource(blorb_map, bb_method_FilePos, &resource, bb_ID_Snd, number))
923 return;
924
925 type = get_type(blorb_map->chunks[resource.chunknum].type);
926
927 if (type == FORM) {
928 s = load_aiff(blorb_fp,
929 resource.data.startpos,
930 resource.length,
931 number,
932 vol,
933 repeats);
934 } else if (type == MOD) {
935 s = load_mod(blorb_fp, resource.data.startpos, number, vol);
936 } else if (type == OGGV) {
937 s = load_aiff(blorb_fp,
938 resource.data.startpos,
939 resource.length,
940 number,
941 vol,
942 repeats);
943 s->sound_type = OGGV;
944 }
945
946 if (s)
947 sound_enqueue(s);
948 }
949
950
os_beep(int bv)951 void os_beep(int bv)
952 {
953 (void) bv;
954 /* Currently implemented as a simple terminal bell */
955 /* To implement generate a high frequency beep for bv=1, */
956 /* low frequency for bv=2 */
957 /* fprintf(audio_log, "os_beep(%d)...\n", bv); */
958 beep();
959 }
960
961
os_prepare_sample(int id)962 void os_prepare_sample(int id)
963 {
964 (void) id;
965 /* Currently not implemented */
966 /* fprintf(audio_log, "os_prepare_sample(%d)...\n", id); */
967 }
968
969
os_stop_sample(int id)970 void os_stop_sample(int id)
971 {
972 /* fprintf(audio_log, "os_stop_sample(%d)...\n", id); */
973 if (!f_setup.sound) return;
974 sound_stop_id(id);
975 }
976
os_finish_with_sample(int id)977 void os_finish_with_sample(int id)
978 {
979 /* fprintf(audio_log, "os_finish_with_sample(%d)...\n", id); */
980 os_stop_sample(id);
981 }
982
983 #endif /* NO_SOUND */
984