2020-05-10 06:40:37 +00:00
|
|
|
//
|
|
|
|
// speaker.c
|
|
|
|
// A2Mac
|
|
|
|
//
|
|
|
|
// Created by Tamas Rudnai on 5/9/20.
|
|
|
|
// Copyright © 2020 GameAlloy. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <OpenAL/al.h>
|
|
|
|
#include <OpenAL/alc.h>
|
|
|
|
|
|
|
|
#include "speaker.h"
|
2020-05-17 14:49:05 +00:00
|
|
|
#include "6502.h"
|
2020-05-28 00:27:11 +00:00
|
|
|
#include "disk.h" // to be able to disable disk acceleration
|
2020-05-17 14:49:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
#define min(x,y) (x) < (y) ? (x) : (y)
|
|
|
|
#define max(x,y) (x) > (y) ? (x) : (y)
|
|
|
|
#define clamp(min,num,max) (num) < (min) ? (min) : (num) > (max) ? (max) : (num)
|
2020-05-10 06:40:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
#define CASE_RETURN(err) case (err): return #err
|
|
|
|
const char* al_err_str(ALenum err) {
|
|
|
|
switch(err) {
|
|
|
|
CASE_RETURN(AL_NO_ERROR);
|
|
|
|
CASE_RETURN(AL_INVALID_NAME);
|
|
|
|
CASE_RETURN(AL_INVALID_ENUM);
|
|
|
|
CASE_RETURN(AL_INVALID_VALUE);
|
|
|
|
CASE_RETURN(AL_INVALID_OPERATION);
|
|
|
|
CASE_RETURN(AL_OUT_OF_MEMORY);
|
|
|
|
}
|
2020-05-17 14:49:05 +00:00
|
|
|
printf("alError: 0x%04X\n", err);
|
2020-05-10 06:40:37 +00:00
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
#undef CASE_RETURN
|
|
|
|
|
|
|
|
#define __al_check_error(file,line) \
|
|
|
|
do { \
|
|
|
|
ALenum err = alGetError(); \
|
|
|
|
for(; err != AL_NO_ERROR; err = alGetError()) { \
|
|
|
|
printf( "AL Error %s at %s:%d\n", al_err_str(err), file, line ); \
|
|
|
|
} \
|
|
|
|
} while(0)
|
|
|
|
|
|
|
|
#define al_check_error() \
|
|
|
|
__al_check_error(__FILE__, __LINE__)
|
|
|
|
|
|
|
|
|
|
|
|
ALCdevice *dev = NULL;
|
|
|
|
ALCcontext *ctx = NULL;
|
|
|
|
ALuint spkr_buf = 0;
|
|
|
|
ALuint spkr_src = 0;
|
|
|
|
|
2020-05-18 02:32:45 +00:00
|
|
|
|
|
|
|
int spkr_level = SPKR_LEVEL_ZERO;
|
|
|
|
|
|
|
|
|
|
|
|
#define BUFFER_COUNT 10
|
|
|
|
#define SOURCES_COUNT 1
|
|
|
|
|
|
|
|
ALuint spkr_buffers[BUFFER_COUNT];
|
2020-05-10 17:54:34 +00:00
|
|
|
|
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
const int spkr_fps = fps;
|
2020-05-10 06:40:37 +00:00
|
|
|
const int spkr_seconds = 1;
|
|
|
|
const unsigned spkr_sample_rate = 44100;
|
2020-06-09 04:12:40 +00:00
|
|
|
unsigned spkr_extra_buf = 800 / fps;
|
2020-05-18 05:08:23 +00:00
|
|
|
const unsigned spkr_buf_size = spkr_seconds * spkr_sample_rate * 2 / spkr_fps;
|
2020-05-24 19:14:09 +00:00
|
|
|
int16_t spkr_samples [ spkr_buf_size * spkr_fps * BUFFER_COUNT * 2]; // stereo
|
2020-05-10 22:30:29 +00:00
|
|
|
unsigned spkr_sample_idx = 0;
|
2020-05-10 06:40:37 +00:00
|
|
|
|
2020-06-06 06:17:12 +00:00
|
|
|
const unsigned spkr_play_timeout = 8; // increase to 32 for 240 fps
|
2020-05-17 23:01:15 +00:00
|
|
|
unsigned spkr_play_time = 0;
|
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
|
2020-06-09 07:54:36 +00:00
|
|
|
static uint8_t* load_sfx( const char * bundlePath, const char * filename ) {
|
|
|
|
char fullPath[256];
|
|
|
|
|
|
|
|
strcpy( fullPath, bundlePath );
|
|
|
|
strcat( fullPath, "/");
|
|
|
|
strcat( fullPath, filename );
|
|
|
|
|
|
|
|
FILE * f = fopen(fullPath, "rb");
|
|
|
|
if (f == NULL) {
|
|
|
|
perror("Failed to read SFX: ");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fseek(f, 0L, SEEK_END);
|
|
|
|
uint16_t flen = ftell(f);
|
|
|
|
fseek(f, 0L, SEEK_SET);
|
|
|
|
|
|
|
|
if (flen <= 0) {
|
|
|
|
printf("Failed to read SFX or 0 size\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t * buffer = malloc(flen);
|
|
|
|
|
|
|
|
if (buffer == NULL) {
|
|
|
|
printf("Not enough memory for SFX\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fread( buffer, 1, flen, f);
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
if ( flen == 0 ) {
|
|
|
|
printf("Error loading SFX file\n");
|
|
|
|
free(buffer);
|
|
|
|
return NULL; // there was an error
|
|
|
|
}
|
|
|
|
|
|
|
|
// everything seems to be ok
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void spkr_load_sfx( const char * bundlePath ) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-10 22:30:29 +00:00
|
|
|
// initialize OpenAL
|
2020-05-10 06:40:37 +00:00
|
|
|
void spkr_init() {
|
|
|
|
const char *defname = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
|
|
|
|
printf( "Default device: %s\n", defname );
|
2020-05-18 02:32:45 +00:00
|
|
|
|
|
|
|
// restart OpenAL when restarting the virtual machine
|
|
|
|
spkr_exit();
|
2020-05-10 06:40:37 +00:00
|
|
|
|
|
|
|
dev = alcOpenDevice(defname);
|
|
|
|
ctx = alcCreateContext(dev, NULL);
|
|
|
|
alcMakeContextCurrent(ctx);
|
|
|
|
|
|
|
|
// Fill buffer with zeros
|
2020-05-24 19:14:09 +00:00
|
|
|
memset( spkr_samples, spkr_level, spkr_buf_size * sizeof(spkr_samples[0]) );
|
2020-05-10 06:40:37 +00:00
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
// Create buffer to store samples
|
|
|
|
alGenBuffers(BUFFER_COUNT, spkr_buffers);
|
|
|
|
al_check_error();
|
|
|
|
|
|
|
|
// Set-up sound source and play buffer
|
|
|
|
alGenSources(1, &spkr_src);
|
|
|
|
al_check_error();
|
|
|
|
alSourcei(spkr_src, AL_LOOPING, AL_FALSE);
|
|
|
|
al_check_error();
|
2020-05-17 20:48:35 +00:00
|
|
|
alSourcef(spkr_src, AL_ROLLOFF_FACTOR, 0);
|
|
|
|
al_check_error();
|
|
|
|
alSource3f(spkr_src, AL_POSITION, 0.0, 8.0, 0.0);
|
|
|
|
al_check_error();
|
2020-05-17 14:49:05 +00:00
|
|
|
alListener3f(AL_POSITION, 0.0, 0.0, 0.0);
|
|
|
|
al_check_error();
|
2020-05-17 20:48:35 +00:00
|
|
|
alListener3f(AL_ORIENTATION, 0.0, -16.0, 0.0);
|
|
|
|
al_check_error();
|
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
// start from the beginning
|
|
|
|
spkr_sample_idx = 0;
|
|
|
|
|
2020-05-18 01:31:41 +00:00
|
|
|
// make sure we have free buffers initialized here
|
|
|
|
freeBuffers = BUFFER_COUNT;
|
2020-05-10 06:40:37 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 22:30:29 +00:00
|
|
|
// Dealloc OpenAL
|
2020-05-10 06:40:37 +00:00
|
|
|
void spkr_exit() {
|
2020-05-18 02:32:45 +00:00
|
|
|
if ( spkr_src ) {
|
|
|
|
ALCdevice *dev = NULL;
|
|
|
|
ALCcontext *ctx = NULL;
|
|
|
|
ctx = alcGetCurrentContext();
|
|
|
|
dev = alcGetContextsDevice(ctx);
|
|
|
|
|
|
|
|
alcMakeContextCurrent(NULL);
|
|
|
|
alcDestroyContext(ctx);
|
|
|
|
alcCloseDevice(dev);
|
|
|
|
|
|
|
|
al_check_error();
|
|
|
|
|
|
|
|
spkr_src = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-24 20:07:34 +00:00
|
|
|
char spkr_state = 0;
|
|
|
|
|
2020-05-18 02:32:45 +00:00
|
|
|
void spkr_toggle() {
|
|
|
|
// TODO: This is very slow!
|
2020-05-10 06:40:37 +00:00
|
|
|
|
2020-05-28 00:16:49 +00:00
|
|
|
spkr_play_time = 0;
|
2020-05-10 06:40:37 +00:00
|
|
|
|
2020-05-28 00:27:11 +00:00
|
|
|
if ( diskAccelerator_count ) {
|
|
|
|
// turn off disk acceleration immediately
|
|
|
|
diskAccelerator_count = 0;
|
|
|
|
clk_6502_per_frm = clk_6502_per_frm_max = clk_6502_per_frm_set;
|
|
|
|
}
|
|
|
|
|
2020-05-28 00:16:49 +00:00
|
|
|
if ( clk_6502_per_frm_set < clk_6502_per_frm_max_sound ) {
|
|
|
|
|
|
|
|
spkr_play_time = spkr_play_timeout;
|
2020-05-24 20:07:34 +00:00
|
|
|
|
2020-05-28 00:16:49 +00:00
|
|
|
// push a click into the speaker buffer
|
|
|
|
// (we will play the entire buffer at the end of the frame)
|
|
|
|
spkr_sample_idx = (clkfrm / (default_MHz_6502 / spkr_sample_rate)) * 2;
|
2020-05-24 20:07:34 +00:00
|
|
|
|
2020-05-28 00:16:49 +00:00
|
|
|
if ( spkr_state ) {
|
|
|
|
// down edge
|
|
|
|
spkr_state = 0;
|
2020-05-24 20:07:34 +00:00
|
|
|
|
2020-05-28 00:16:49 +00:00
|
|
|
float fadeLevel = spkr_level - SPKR_LEVEL_MIN;
|
|
|
|
|
|
|
|
while ( fadeLevel > +1 ) {
|
|
|
|
spkr_samples[ spkr_sample_idx++ ] = SPKR_LEVEL_MIN + fadeLevel;
|
|
|
|
spkr_samples[ spkr_sample_idx++ ] = SPKR_LEVEL_MIN + fadeLevel;
|
|
|
|
|
|
|
|
// how smooth we want the speeker to decay, so we will hear no pops and crackles
|
|
|
|
// 0.9 gives you a kind of saw wave at 1KHz (beep)
|
|
|
|
// 0.7 is better, but Xonix gives you a bit distorted speech and Donkey Kong does not sound the same
|
|
|
|
fadeLevel *= 0.16;
|
|
|
|
}
|
|
|
|
spkr_level = SPKR_LEVEL_MIN;
|
2020-05-18 02:32:45 +00:00
|
|
|
}
|
2020-05-28 00:16:49 +00:00
|
|
|
else {
|
|
|
|
// up edge
|
|
|
|
spkr_state = 1;
|
|
|
|
|
|
|
|
float fadeLevel = spkr_level - SPKR_LEVEL_MAX;
|
2020-05-24 20:07:34 +00:00
|
|
|
|
2020-05-28 00:16:49 +00:00
|
|
|
while ( fadeLevel < -1 ) {
|
|
|
|
spkr_samples[ spkr_sample_idx++ ] = SPKR_LEVEL_MAX + fadeLevel;
|
|
|
|
spkr_samples[ spkr_sample_idx++ ] = SPKR_LEVEL_MAX + fadeLevel;
|
|
|
|
|
|
|
|
// how smooth we want the speeker to decay, so we will hear no pops and crackles
|
|
|
|
// 0.9 gives you a kind of saw wave at 1KHz (beep)
|
|
|
|
// 0.7 is better, but Xonix gives you a bit distorted speech and Donkey Kong does not sound the same
|
|
|
|
fadeLevel *= 0.32;
|
|
|
|
}
|
|
|
|
spkr_level = SPKR_LEVEL_MAX;
|
2020-05-18 02:32:45 +00:00
|
|
|
}
|
2020-05-24 20:07:34 +00:00
|
|
|
|
2020-05-28 00:16:49 +00:00
|
|
|
|
|
|
|
//spkr_samples[sample_idx] = spkr_level;
|
|
|
|
for ( int i = spkr_sample_idx; i < spkr_buf_size + spkr_extra_buf; i++ ) {
|
|
|
|
spkr_samples[i] = spkr_level;
|
|
|
|
}
|
|
|
|
// memset(spkr_samples + spkr_sample_idx, spkr_level, spkr_buf_size * sizeof(spkr_samples[0]));
|
|
|
|
|
2020-05-24 19:14:09 +00:00
|
|
|
}
|
2020-05-10 06:40:37 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
ALint freeBuffers = BUFFER_COUNT;
|
|
|
|
//ALuint alBuffers[BUFFER_COUNT];
|
|
|
|
|
|
|
|
void spkr_update() {
|
2020-05-17 23:01:15 +00:00
|
|
|
if ( spkr_play_time ) {
|
2020-05-10 22:30:29 +00:00
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
ALint processed = 0;
|
2020-05-18 01:31:41 +00:00
|
|
|
|
|
|
|
// printf("freeBuffers:%d queued:%d processed:%d\n", freeBuffers, queued,processed);
|
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
do {
|
|
|
|
alGetSourcei (spkr_src, AL_BUFFERS_PROCESSED, &processed);
|
|
|
|
// al_check_error();
|
2020-05-18 01:31:41 +00:00
|
|
|
|
|
|
|
if ( processed ) {
|
|
|
|
alSourceUnqueueBuffers( spkr_src, processed, &spkr_buffers[freeBuffers]);
|
|
|
|
// al_check_error();
|
|
|
|
freeBuffers += processed;
|
|
|
|
}
|
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
} while( freeBuffers <= 0 );
|
2020-05-10 22:30:29 +00:00
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
freeBuffers = clamp( 1, freeBuffers, BUFFER_COUNT );
|
|
|
|
|
|
|
|
// printf("freeBuffers2: %d processed: %d\n", freeBuffers, processed);
|
|
|
|
|
|
|
|
ALenum state;
|
|
|
|
alGetSourcei( spkr_src, AL_SOURCE_STATE, &state );
|
|
|
|
// al_check_error();
|
|
|
|
|
2020-05-24 19:14:09 +00:00
|
|
|
////////// check if there is no sound generated for long time, and fade out speaker level to avoid pops and crackles
|
2020-05-10 22:30:29 +00:00
|
|
|
|
2020-05-17 23:01:15 +00:00
|
|
|
if ( --spkr_play_time == 0 ) {
|
|
|
|
// we need to soft mute the speaker to eliminate clicking noise
|
|
|
|
// simple linear mute
|
2020-05-24 19:14:09 +00:00
|
|
|
const int steepness = 64;
|
|
|
|
|
|
|
|
float fadeLevel = spkr_level - SPKR_LEVEL_ZERO;
|
|
|
|
|
2020-05-24 20:07:34 +00:00
|
|
|
if ( spkr_level != SPKR_LEVEL_ZERO ) {
|
2020-05-24 19:14:09 +00:00
|
|
|
spkr_sample_idx = 0;
|
|
|
|
|
|
|
|
while ( ( fadeLevel < -1 ) || ( fadeLevel > 1 ) ) {
|
|
|
|
spkr_samples[ spkr_sample_idx++ ] = SPKR_LEVEL_ZERO + fadeLevel;
|
|
|
|
spkr_samples[ spkr_sample_idx++ ] = SPKR_LEVEL_ZERO + fadeLevel;
|
|
|
|
|
|
|
|
// how smooth we want the speeker to decay, so we will hear no pops and crackles
|
|
|
|
fadeLevel *= 0.999;
|
2020-05-17 23:01:15 +00:00
|
|
|
}
|
2020-05-24 20:07:34 +00:00
|
|
|
spkr_level = SPKR_LEVEL_ZERO;
|
2020-05-24 19:14:09 +00:00
|
|
|
|
2020-05-17 23:01:15 +00:00
|
|
|
//spkr_samples[sample_idx] = spkr_level;
|
2020-05-24 19:14:09 +00:00
|
|
|
memset(spkr_samples + spkr_sample_idx, SPKR_LEVEL_ZERO, spkr_extra_buf * sizeof(spkr_samples[0]));
|
2020-05-17 23:01:15 +00:00
|
|
|
|
|
|
|
freeBuffers--;
|
2020-05-24 19:14:09 +00:00
|
|
|
alBufferData(spkr_buffers[freeBuffers], AL_FORMAT_STEREO16, spkr_samples, spkr_sample_idx * sizeof(spkr_samples[0]), spkr_sample_rate);
|
2020-05-17 23:01:15 +00:00
|
|
|
al_check_error();
|
|
|
|
alSourceQueueBuffers(spkr_src, 1, &spkr_buffers[freeBuffers]);
|
|
|
|
al_check_error();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
freeBuffers--;
|
2020-05-24 19:14:09 +00:00
|
|
|
alBufferData(spkr_buffers[freeBuffers], AL_FORMAT_STEREO16, spkr_samples, (spkr_buf_size + spkr_extra_buf) * sizeof(spkr_samples[0]), spkr_sample_rate);
|
2020-05-17 23:01:15 +00:00
|
|
|
al_check_error();
|
|
|
|
alSourceQueueBuffers(spkr_src, 1, &spkr_buffers[freeBuffers]);
|
|
|
|
al_check_error();
|
|
|
|
}
|
2020-05-10 22:30:29 +00:00
|
|
|
|
2020-05-17 14:49:05 +00:00
|
|
|
switch (state) {
|
|
|
|
case AL_PAUSED:
|
|
|
|
alSourcePlay(spkr_src);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AL_PLAYING:
|
|
|
|
// already playing
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
alSourcePlay(spkr_src);
|
|
|
|
alSourcePause(spkr_src);
|
|
|
|
break;
|
|
|
|
}
|
2020-05-10 22:30:29 +00:00
|
|
|
|
|
|
|
// clear the slack buffer , so we can fill it up by new data
|
2020-05-24 19:14:09 +00:00
|
|
|
for ( int i = 0; i < spkr_buf_size + spkr_extra_buf; i++ ) {
|
|
|
|
spkr_samples[i] = spkr_level;
|
|
|
|
}
|
|
|
|
// memset(spkr_samples, spkr_level, (spkr_buf_size + spkr_extra_buf) * sizeof(spkr_samples[0]));
|
2020-05-10 22:30:29 +00:00
|
|
|
|
|
|
|
// start from the beginning
|
|
|
|
spkr_sample_idx = 0;
|
2020-05-17 14:49:05 +00:00
|
|
|
|
2020-05-10 06:40:37 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-14 03:40:54 +00:00
|
|
|
|
|
|
|
|