adding event meachinsm for game items that change and sound support.
This commit is contained in:
parent
9ce106c179
commit
f49d07fdc5
15 changed files with 1468 additions and 43 deletions
968
game/sound/sound.cpp
Normal file
968
game/sound/sound.cpp
Normal file
|
|
@ -0,0 +1,968 @@
|
|||
#include "sound.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <portaudio.h>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
// for dumping sound
|
||||
#include <sndfile.h>
|
||||
|
||||
namespace sound {
|
||||
|
||||
typedef struct {
|
||||
PaStream *stream;
|
||||
|
||||
unsigned int framesPerBuffer;
|
||||
unsigned int sampleRate;
|
||||
|
||||
unsigned int sound_uid_counter;
|
||||
|
||||
// constant
|
||||
unsigned int maxNumSounds;
|
||||
|
||||
// num active sonuds
|
||||
unsigned int highestSoundIndex;
|
||||
|
||||
// pointer to array of sound handles that has this number of active sounds
|
||||
SoundHandle **sounds;
|
||||
|
||||
bool dumping;
|
||||
const char *dump_filename;
|
||||
SNDFILE *outfile;
|
||||
SF_INFO sfinfo;
|
||||
|
||||
bool init;
|
||||
} SoundContext;
|
||||
|
||||
// singleton so no arguments must be supplied for play() functions etc.
|
||||
static SoundContext context;
|
||||
|
||||
static double startup_time = -1.0;
|
||||
static double global_time = -1.0;;
|
||||
|
||||
static double timestamp_double(void)
|
||||
{
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
//printf("%d %d\n", (int) now.tv_sec, (int) now.tv_usec);
|
||||
|
||||
double t = now.tv_sec;
|
||||
t += now.tv_usec / 1000000.0f;
|
||||
//printf("%f\n", t);
|
||||
return t;
|
||||
}
|
||||
|
||||
const char *stringFromEnvelope(enum EnvelopeState envelope)
|
||||
{
|
||||
switch(envelope) {
|
||||
case New: return "New";
|
||||
case Wait: return "Wait";
|
||||
case Rise: return "Rise";
|
||||
case BeginOvershoot: return "BeginOvershoot";
|
||||
case EndOvershoot: return "EndOvershoot";
|
||||
case Decay: return "Decay";
|
||||
case Hold: return "Hold";
|
||||
case Done: return "Done";
|
||||
default: assert(false); return "<error>";
|
||||
}
|
||||
}
|
||||
|
||||
static void checkEnvelopeState(SoundHandle *handle, enum EnvelopeState next)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle->envelope != next) {
|
||||
#if 1
|
||||
printf("[sound] global time %4.3f #%d (%s) t=%f %5s -> %5s\n",
|
||||
global_time,
|
||||
handle->uid,
|
||||
handle->name,
|
||||
handle->time,
|
||||
stringFromEnvelope(handle->envelope),
|
||||
stringFromEnvelope(next));
|
||||
#endif
|
||||
|
||||
handle->envelope = next;
|
||||
}
|
||||
}
|
||||
|
||||
void stopDumpingWav(void)
|
||||
{
|
||||
if (!context.init || !context.dumping) {
|
||||
return;
|
||||
}
|
||||
|
||||
int err = sf_close(context.outfile);
|
||||
context.outfile = NULL;
|
||||
context.dumping = false;
|
||||
|
||||
if (err < 0) {
|
||||
printf("warning: stop dumping wav returned error: %d\n", err);
|
||||
} else {
|
||||
printf("sucessfully stopped dumping to file %s\n", context.dump_filename);
|
||||
}
|
||||
}
|
||||
|
||||
static bool dumpSamples(float *samples, size_t numSamples)
|
||||
{
|
||||
// TODO: dumping in mono?
|
||||
|
||||
if (!context.init || !context.dumping) {
|
||||
printf("warning: can't dump samples: context not initialized or not currently dumping!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
sf_writef_float(context.outfile, samples, numSamples);
|
||||
sf_write_sync(context.outfile); // force writing
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool startDumpingWav(const char *filename)
|
||||
{
|
||||
if (!context.init) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.dumping) {
|
||||
printf("warning: already dumping to %s!\n", context.dump_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
context.sfinfo.channels = 2;
|
||||
context.sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
|
||||
context.sfinfo.samplerate = context.sampleRate;
|
||||
|
||||
context.outfile = sf_open(filename, SFM_WRITE, &context.sfinfo);
|
||||
|
||||
if (context.outfile == NULL) {
|
||||
printf("Unable to dump to output file %s.\n", filename);
|
||||
sf_perror(NULL);
|
||||
return false;
|
||||
};
|
||||
|
||||
//printf("start dumping wav to file %s\n", filename);
|
||||
context.dump_filename = filename;
|
||||
context.dumping = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static float amplitudeForTime(SoundHandle *handle)
|
||||
{
|
||||
//float amplitude = handle->amplitude;
|
||||
float amplitude = 1.0;
|
||||
|
||||
float t = handle->time;
|
||||
if (t < handle->_riseTime) {
|
||||
// linear increase
|
||||
float a = t / handle->_riseTime;
|
||||
|
||||
if (handle->_relativeOvershootTime == 0.0) {
|
||||
checkEnvelopeState(handle, Rise);
|
||||
|
||||
} else {
|
||||
//
|
||||
// |--overshoot-up-| |-overshoot-down-|
|
||||
// |-------------overshoot------------|
|
||||
// |-----------------rise-time -----------------|
|
||||
|
||||
// make it raise to full level earlier
|
||||
const float overshoot = 2.0*handle->_relativeOvershootTime;
|
||||
|
||||
a *= 1.0+overshoot;
|
||||
|
||||
// compensate the overshoot during last r/2 time to normal level
|
||||
// (1.0) by inversing slope
|
||||
if (a < 1.0) {
|
||||
checkEnvelopeState(handle, Rise);
|
||||
|
||||
} else if (a < 1.0 + (overshoot/2.0)) {
|
||||
checkEnvelopeState(handle, BeginOvershoot);
|
||||
|
||||
} else {
|
||||
a = 2.0 + overshoot - a;
|
||||
checkEnvelopeState(handle, EndOvershoot);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
amplitude *= a;
|
||||
//printf("amplitude rising: %f\n", a);
|
||||
|
||||
} else {
|
||||
t -= handle->_riseTime;
|
||||
if (t < handle->_holdTime) {
|
||||
// stays constant during this time
|
||||
checkEnvelopeState(handle, Hold);
|
||||
|
||||
} else {
|
||||
t -= handle->_holdTime;
|
||||
if (t < handle->_decayTime) {
|
||||
// linear decrease
|
||||
float a = 1.0 - (t / handle->_decayTime);
|
||||
//printf("amplitude decaying : %f\n", a);
|
||||
amplitude *= a;
|
||||
checkEnvelopeState(handle, Decay);
|
||||
|
||||
} else {
|
||||
bool switchToDone = false;
|
||||
t -= handle->_decayTime;
|
||||
if (t < handle->_waitTime) {
|
||||
// still waiting
|
||||
checkEnvelopeState(handle, Wait);
|
||||
|
||||
} else if (handle->looping && (handle->_loopCount == -1 || handle->_loopCount > 0)) {
|
||||
// waited long enough to restart
|
||||
// TODO: dont waste time! does stuff stay synchronized
|
||||
// over a long period of time?
|
||||
//handle->time = 0.0;
|
||||
handle->time -= handle->_riseTime + handle->_waitTime + handle->_holdTime + handle->_decayTime;
|
||||
|
||||
if (handle->_loopCount != -1) {
|
||||
handle->_loopCount--;
|
||||
//printf("decrement loopCount to %d\n", handle->_loopCount);
|
||||
if (handle->_loopCount == 0) {
|
||||
switchToDone = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switchToDone = true;
|
||||
}
|
||||
|
||||
if (switchToDone) {
|
||||
// zero
|
||||
checkEnvelopeState(handle, Done);
|
||||
handle->_done = true;
|
||||
return 0.0;
|
||||
}
|
||||
amplitude = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//int phase = handle->envelope;
|
||||
//printf("%f, %d, %f\n", handle->time, phase, amplitude);
|
||||
|
||||
if (handle->minAmplitude != 0.0) {
|
||||
amplitude = handle->minAmplitude + amplitude/(1.0-handle->minAmplitude);
|
||||
}
|
||||
|
||||
amplitude *= handle->amplitude;
|
||||
|
||||
return amplitude;
|
||||
}
|
||||
|
||||
// TODO: support stereo fully
|
||||
|
||||
// args:
|
||||
// n: n-th last history value
|
||||
static float historyValue(SoundHandle *handle, size_t n)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return 0.0;
|
||||
}
|
||||
if (handle->history == NULL) {
|
||||
return 0.0;
|
||||
}
|
||||
if (n >= handle->numHistorySamples) {
|
||||
// TODO clear history when looping?
|
||||
// or add zero samples?
|
||||
printf("warning: try to access more history values than saved.\n");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return handle->history[2 * ((handle->lastHistorySample+n) % handle->numHistorySamples)];
|
||||
}
|
||||
|
||||
void saveHistoryValue(SoundHandle *handle, float v)
|
||||
{
|
||||
if (handle == NULL || handle->history == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
handle->history[2*handle->lastHistorySample] = v;
|
||||
handle->lastHistorySample = (handle->lastHistorySample+1) % handle->numHistorySamples;
|
||||
}
|
||||
|
||||
float sumHistory(SoundHandle *handle, float initialWeight, float v)
|
||||
{
|
||||
float result = initialWeight * v;
|
||||
float totalWeight = initialWeight;
|
||||
|
||||
size_t i;
|
||||
for (i=0; i<handle->numHistorySamples; i++) {
|
||||
float weight = 1.0f / (float) (1+i);
|
||||
//float weight = 1.0f;
|
||||
result += weight * historyValue(handle, i);
|
||||
totalWeight += weight;
|
||||
}
|
||||
|
||||
result /= totalWeight;
|
||||
return result;
|
||||
}
|
||||
|
||||
// return false if playing done.
|
||||
static bool advanceSound(SoundHandle *handle, float *out, unsigned int framesPerBuffer)
|
||||
{
|
||||
assert(handle != NULL);
|
||||
assert(framesPerBuffer > 0);
|
||||
assert(out != NULL);
|
||||
|
||||
if (handle == NULL || framesPerBuffer == 0 || out == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: add length to sounds
|
||||
|
||||
// TODO: find out the period, write just one period and then copy the
|
||||
// values.
|
||||
//float dt = handle->freq / context.sampleRate;
|
||||
|
||||
// TODO: divide into three parts for envelops
|
||||
// because they are linear in the phases so interpolating between that
|
||||
// should be made easier
|
||||
|
||||
// advance parameters for testing
|
||||
switch(handle->_type) {
|
||||
//float div = 10.0;
|
||||
//float t = (((int)(100*handle->time/div)) % 101) / 100.0;
|
||||
//handle->leakage = 0.5+0.5*sin(2.0*M_PI*t);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const float dt = 1.0 / context.sampleRate;
|
||||
const float ds = 2.0 * M_PI * handle->freq * dt;
|
||||
|
||||
// XXX too long range when becomes done
|
||||
unsigned int i;
|
||||
for (i=0; i<framesPerBuffer; i++) {
|
||||
handle->time += dt;
|
||||
|
||||
// TODO: save this state too?
|
||||
if (handle->time < 0.0) {
|
||||
checkEnvelopeState(handle, Wait);
|
||||
// this sound can not yet be heard
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: add phase as argument.
|
||||
|
||||
float v;
|
||||
switch (handle->_type) {
|
||||
case SoundFrequency:
|
||||
handle->state += ds;
|
||||
v = sin(handle->state);
|
||||
break;
|
||||
|
||||
case SoundWhiteNoise:
|
||||
v = util::randf_m1_1();
|
||||
break;
|
||||
|
||||
case SoundPinkNoise:
|
||||
//v = (historyValue(handle, 0) + Randf_m1_1()) / 2.0f;
|
||||
//saveHistoryValue(handle, v);
|
||||
v = (handle->state + util::randf_m1_1()) / 2.0f;
|
||||
handle->state = v;
|
||||
break;
|
||||
|
||||
case SoundBrownianNoise:
|
||||
// this is created by integrating white noise.
|
||||
|
||||
{
|
||||
float white = util::randf_m1_1();
|
||||
float p = handle->leakage * handle->state;
|
||||
float s = handle->scaling * white;
|
||||
float z = p + s;
|
||||
if (handle->skipDirectionIfClamping && fabsf(z) >= 1.0f) {
|
||||
z = p - s;
|
||||
}
|
||||
handle->state = z;
|
||||
v = z;
|
||||
}
|
||||
//v = sumHistory(handle, handle->freq, randf_m1_1());
|
||||
//v = sumHistory(handle, 0.1, randf_m1_1());
|
||||
//v = 0.2*historyValue(handle, 1) + 0.3*historyValue(handle, 0) + 0.5*randf_m1_1();
|
||||
//v = (historyValue(handle, 1) + historyValue(handle, 0) + randf_m1_1()) / 3.0;
|
||||
//saveHistoryValue(handle, v);
|
||||
|
||||
#if 0
|
||||
{
|
||||
float a = 0.8;
|
||||
v = a*handle->state + (1.0-a)*randf_m1_1();
|
||||
handle->state = v;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("warning: unimplemented sound type for #%d: %d\n", handle->uid, handle->_type);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (handle->_useEnvelope) {
|
||||
v *= amplitudeForTime(handle);
|
||||
if (handle->_done) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
v *= handle->amplitude;
|
||||
}
|
||||
|
||||
// add values from this sound te to current sound level
|
||||
*out++ += v;
|
||||
*out++ += v;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* This routine will be called by the PortAudio engine when audio is needed.
|
||||
It may called at interrupt level on some machines so don't do anything
|
||||
that could mess up the system like calling malloc() or free().
|
||||
*/
|
||||
static int sound_callback(const void *inputBuffer,
|
||||
void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void *userData)
|
||||
{
|
||||
(void) inputBuffer;
|
||||
(void) timeInfo;
|
||||
(void) statusFlags;
|
||||
(void) userData;
|
||||
|
||||
// update time
|
||||
double now = timestamp_double();
|
||||
|
||||
// should not happen. just in case.
|
||||
global_time = now - startup_time;
|
||||
|
||||
//printf("global time raw: %f\n", global_time);
|
||||
if (global_time < 0.0) {
|
||||
global_time = 0.0;
|
||||
}
|
||||
|
||||
// we have 32 bit float format so cast here
|
||||
float *out = (float *) outputBuffer;
|
||||
|
||||
// set all to zero
|
||||
unsigned int i;
|
||||
for (i=0; i<framesPerBuffer; i++) {
|
||||
*out++ = 0.0f;
|
||||
*out++ = 0.0f;
|
||||
}
|
||||
|
||||
// reset pointer
|
||||
out = (float *) outputBuffer;
|
||||
|
||||
// advance all sounds
|
||||
for (i=0; i<context.highestSoundIndex; i++) {
|
||||
SoundHandle *handle = context.sounds[i];
|
||||
// XXX
|
||||
if (handle == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!handle->_done) {
|
||||
#if 0
|
||||
printf("play sound 0x%x with loop=%d, sample=%d, freq=%f amplitude=%f\n",
|
||||
(void*) handle,
|
||||
handle->looping,
|
||||
(unsigned int) handle->_currentSample, handle->freq, handle->amplitude);
|
||||
#endif
|
||||
|
||||
if (!advanceSound(handle, out, framesPerBuffer)) {
|
||||
handle->_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: when we should not free in the loop, where else?
|
||||
if (handle->_done) {
|
||||
//printf("[sound] %f freeing sound #%d handle 0x%p\n", global_time, handle->uid, handle);
|
||||
context.sounds[i] = NULL;
|
||||
if (!handle->keep_when_done) {
|
||||
deleteSound(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure all -1.0 >= x <= 1
|
||||
#if 0
|
||||
out = outputBuffer;
|
||||
for (i=0; i<2*framesPerBuffer; i++) {
|
||||
if (*out < -1.0) { *out = -1.0; }
|
||||
else if (*out > 1.0) { *out = 1.0; }
|
||||
}
|
||||
#endif
|
||||
|
||||
if (context.dumping) {
|
||||
dumpSamples((float *) outputBuffer, framesPerBuffer);
|
||||
}
|
||||
|
||||
// TODO: check!
|
||||
#if 0
|
||||
// apply post-processing
|
||||
out = outputBuffer;
|
||||
// advance all sounds
|
||||
for (i=0; i<context.highestSoundIndex; i++) {
|
||||
float v = *out;
|
||||
v = pow(v, 2.0);
|
||||
*out++ = v;
|
||||
|
||||
v = *out;
|
||||
v = pow(v, 2.0);
|
||||
*out++ = v;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool initSound(void)
|
||||
{
|
||||
startup_time = timestamp_double();
|
||||
global_time = 0.0;
|
||||
|
||||
// TODO: make all customizable too
|
||||
context.init = false; // for now, set on succes to true
|
||||
context.dumping = false;
|
||||
context.dump_filename = NULL;
|
||||
context.sampleRate = 44800;
|
||||
context.framesPerBuffer = 256;
|
||||
//context.framesPerBuffer = 1024;
|
||||
//context.framesPerBuffer = 100;
|
||||
//context.framesPerBuffer = 44100;
|
||||
|
||||
// TODO: add max number of simultanous playing sounds, that's more important.
|
||||
|
||||
context.sound_uid_counter = 0;
|
||||
context.maxNumSounds = 50;
|
||||
context.highestSoundIndex = 0;
|
||||
context.sounds = (SoundHandle **) calloc(context.maxNumSounds, 1000); // XXX HACK
|
||||
assert(context.sounds != NULL);
|
||||
|
||||
PaError err = Pa_Initialize();
|
||||
if (err != paNoError) {
|
||||
printf("error: sound failed to initialize. error=%s\n", Pa_GetErrorText(err));
|
||||
return false;
|
||||
|
||||
} else {
|
||||
//printf("sound initialized :)");
|
||||
}
|
||||
|
||||
int numDevices;
|
||||
numDevices = Pa_GetDeviceCount();
|
||||
if (numDevices < 0 ) {
|
||||
printf("ERROR: Pa_CountDevices returned %i\n", numDevices);
|
||||
return false;
|
||||
|
||||
} else {
|
||||
#if 0
|
||||
printf("sound: num devices=%d\n", numDevices);
|
||||
const PaDeviceInfo *deviceInfo;
|
||||
int i;
|
||||
for( i=0; i<numDevices; i++ ) {
|
||||
deviceInfo = Pa_GetDeviceInfo( i );
|
||||
printf("dev [%i]: %s\n", i, deviceInfo->name);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Open an audio I/O stream. */
|
||||
err = Pa_OpenDefaultStream(
|
||||
&context.stream,
|
||||
0, /* no input channels */
|
||||
2, /* stereo output */
|
||||
paFloat32, /* 32 bit floating point output */
|
||||
context.sampleRate, /* sample rate */
|
||||
context.framesPerBuffer, /* frames per buffer */
|
||||
sound_callback, /* this is your callback function */
|
||||
NULL); /* callback function pointer */
|
||||
|
||||
if (err != paNoError) {
|
||||
printf("error: opening default stream: %s\n", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = Pa_StartStream(context.stream);
|
||||
if (err != paNoError) {
|
||||
printf("error: starting default stream failed: %s\n", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
// clear that
|
||||
//sound_context.num_active_handles = 0;
|
||||
//sound_context.num_cached = 0;
|
||||
|
||||
context.init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void allocateHistory(SoundHandle *handle, size_t numHistorySamples)
|
||||
{
|
||||
handle->lastHistorySample = 0;
|
||||
|
||||
if (numHistorySamples == 0) {
|
||||
handle->numHistorySamples = 0;
|
||||
handle->history = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
handle->numHistorySamples = numHistorySamples;
|
||||
handle->history = (float *) calloc(sizeof(float)*2, handle->numHistorySamples);
|
||||
|
||||
// clear all
|
||||
size_t i;
|
||||
for (i=0; i<2*handle->numHistorySamples; i++) {
|
||||
handle->history[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
SoundHandle *playFrequency(float freq, float amplitude)
|
||||
{
|
||||
SoundHandle *handle = playSound(SoundFrequency, amplitude);
|
||||
if (handle != NULL) {
|
||||
handle->freq = freq;
|
||||
//handle->looping = true;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
SoundHandle *playSound(enum Sound type, float amplitude)
|
||||
{
|
||||
if (!context.init) {
|
||||
//printf("warning: called playSound() with no initialized sound system!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t nextFreeIndex = 0;
|
||||
while(nextFreeIndex < context.highestSoundIndex) {
|
||||
if (context.sounds[nextFreeIndex] == NULL) {
|
||||
// got an empyt sound to use
|
||||
break;
|
||||
}
|
||||
nextFreeIndex++;
|
||||
}
|
||||
|
||||
if (nextFreeIndex >= context.maxNumSounds) {
|
||||
printf("error: can't create more sounds, got maximum of: %d\n", context.maxNumSounds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SoundHandle *handle = (SoundHandle *) calloc(1, sizeof(SoundHandle));
|
||||
handle->envelope = New;
|
||||
handle->amplitude = amplitude;
|
||||
handle->minAmplitude = 0.0;
|
||||
handle->freq = 1000;
|
||||
handle->looping = false;
|
||||
handle->_loopCount = 0;
|
||||
handle->_type = type;
|
||||
handle->time = 0.0;
|
||||
handle->keep_when_done = false;
|
||||
handle->state = 0.0;
|
||||
handle->leakage = 0.0;
|
||||
handle->scaling = 0.0;
|
||||
handle->_done = false;
|
||||
handle->skipDirectionIfClamping = false;
|
||||
|
||||
handle->_useEnvelope = false;
|
||||
handle->_riseTime = 0.0;
|
||||
handle->_relativeOvershootTime = 0.0;
|
||||
handle->_decayTime = 0.0;
|
||||
handle->_holdTime = 0.0;
|
||||
handle->_waitTime = 0.0;
|
||||
handle->decayType = SoundDecayLinear;
|
||||
handle->riseType = SoundDecayLinear;
|
||||
handle->name = "<unnamed>";
|
||||
|
||||
// filters may to access values from their history
|
||||
int numHistoryValues = 0;
|
||||
|
||||
switch(type) {
|
||||
// pink/white/brown noise need to save only one sample, state is enough
|
||||
// for that.
|
||||
case SoundBrownianNoise:
|
||||
// something like lowpass filter constant.
|
||||
// lower value == lower freq. noise
|
||||
// values that audacity uses
|
||||
//handle->leakage = 0.997;
|
||||
//handle->scaling = 0.05;
|
||||
|
||||
handle->leakage = (context.sampleRate - 144.0) / context.sampleRate;
|
||||
handle->scaling = 9.0 / sqrt(context.sampleRate);
|
||||
//printf("computed leakage and scaling: %f %F\n", handle->leakage, handle->scaling);
|
||||
|
||||
//handle->leakage = 0.7;
|
||||
//handle->scaling = 0.2;
|
||||
handle->skipDirectionIfClamping = true;
|
||||
break;
|
||||
|
||||
case SoundPinkNoise:
|
||||
break;
|
||||
|
||||
case SoundWhiteNoise:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
allocateHistory(handle, numHistoryValues);
|
||||
|
||||
handle->uid = context.sound_uid_counter++;
|
||||
|
||||
context.sounds[nextFreeIndex] = handle;
|
||||
|
||||
if (nextFreeIndex == context.highestSoundIndex) {
|
||||
context.highestSoundIndex++;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool stopSound(SoundHandle *handle)
|
||||
{
|
||||
if (!context.init) {
|
||||
printf("warning: called stopSound() with no initialized sound system!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handle == NULL) {
|
||||
printf("warning: stopSound() handle is NULL\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handle->_done) {
|
||||
printf("warning: stopSound() sound is already stopped\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: kill with variable
|
||||
handle->_done = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopAllSounds(void)
|
||||
{
|
||||
if (!context.init) {
|
||||
printf("warning: called stopAllSounds() with no initialized sound system!\n");
|
||||
}
|
||||
|
||||
unsigned int i;
|
||||
for (i=0; i<context.highestSoundIndex; i++) {
|
||||
if (context.sounds[i] != NULL && !context.sounds[i]->_done) {
|
||||
stopSound(context.sounds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void computeEnvelopeUse(SoundHandle *handle)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// wait time has no influence (yet)
|
||||
handle->_useEnvelope = (handle->_riseTime != 0.0 || handle->_holdTime != 0.0 || handle->_decayTime != 0.0);
|
||||
}
|
||||
|
||||
bool configureEnvelope(SoundHandle *handle, float rise, float hold, float decay)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: check inf/nan too
|
||||
if (rise < 0.0) {
|
||||
return false;
|
||||
}
|
||||
if (hold < 0.0) {
|
||||
return false;
|
||||
}
|
||||
if (decay < 0.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->_riseTime = rise;
|
||||
handle->_holdTime = hold;
|
||||
handle->_decayTime = decay;
|
||||
|
||||
// set depending on flags
|
||||
computeEnvelopeUse(handle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool configureEnvelopeLooping(SoundHandle *handle, float rise, float hold, float decay, float wait)
|
||||
{
|
||||
if (wait < 0.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!configureEnvelope(handle, rise, hold, decay)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->looping = true;
|
||||
handle->_loopCount = -1;
|
||||
handle->_waitTime = wait;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float getRiseDuration(SoundHandle *handle)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
return handle->_riseTime;
|
||||
}
|
||||
|
||||
float getHoldDuration(SoundHandle *handle)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
return handle->_holdTime;
|
||||
}
|
||||
|
||||
float getDecayDuration(SoundHandle *handle)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
return handle->_decayTime;
|
||||
}
|
||||
|
||||
float getwaitTime(SoundHandle *handle)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
return handle->_waitTime;
|
||||
}
|
||||
|
||||
void teardownSound(void)
|
||||
{
|
||||
if (!context.init) {
|
||||
printf("[sonud] can't teardown sound when not initialized!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
PaError err = Pa_CloseStream(context.stream);
|
||||
if (err != paNoError) {
|
||||
printf("[sound] portaudio failed to close stream: %d\n", err);
|
||||
}
|
||||
|
||||
err = Pa_Terminate();
|
||||
if (err != paNoError) {
|
||||
printf("[sound] portaudio failed to terminate: %d\n", err);
|
||||
}
|
||||
|
||||
if (context.dumping) {
|
||||
stopDumpingWav();
|
||||
}
|
||||
|
||||
context.init = false;
|
||||
}
|
||||
|
||||
bool configureOvershoot(SoundHandle *handle, float relativeOvershootTime)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (relativeOvershootTime < 0.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->_relativeOvershootTime = relativeOvershootTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void setLoopCount(SoundHandle *handle, int numRepetitions)
|
||||
{
|
||||
if (numRepetitions < -1) {
|
||||
numRepetitions = -1;
|
||||
}
|
||||
|
||||
handle->_loopCount = numRepetitions;
|
||||
|
||||
if (numRepetitions == 0) {
|
||||
handle->looping = false;
|
||||
} else if (numRepetitions == -1) {
|
||||
handle->looping = true;
|
||||
} else {
|
||||
handle->looping = true;
|
||||
}
|
||||
}
|
||||
|
||||
int numActiveSounds(void)
|
||||
{
|
||||
int num = 0;
|
||||
size_t i;
|
||||
for (i=0; i<context.highestSoundIndex; i++) {
|
||||
if (context.sounds[i] != NULL) {
|
||||
num++;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
void deleteSound(SoundHandle *handle)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// just for sure
|
||||
handle->_done = true;
|
||||
|
||||
// clear from that
|
||||
size_t i;
|
||||
for (i=0; i<context.highestSoundIndex; i++) {
|
||||
if (context.sounds[i] == handle) {
|
||||
context.sounds[i] = NULL;
|
||||
if (i+1 == context.highestSoundIndex) {
|
||||
context.highestSoundIndex--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (handle->history != NULL) {
|
||||
free(handle->history);
|
||||
}
|
||||
|
||||
free(handle);
|
||||
}
|
||||
|
||||
bool configureEnvelopeWait(SoundHandle *handle, float rise, float hold, float decay, float wait)
|
||||
{
|
||||
if (configureEnvelopeLooping(handle, rise, hold, decay, wait)) {
|
||||
handle->looping = false;
|
||||
handle->_loopCount = -1;
|
||||
return true;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
185
game/sound/sound.hpp
Normal file
185
game/sound/sound.hpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#ifndef _SOUND_HPP_
|
||||
#define _SOUND_HPP_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
namespace sound {
|
||||
|
||||
// type of sound.
|
||||
// the envelope settings modify each sound.
|
||||
enum Sound {
|
||||
SoundFrequency=0, // one frequency
|
||||
SoundWhiteNoise=1, // all frequencies are equal
|
||||
SoundBrownianNoise=2, // 1/f^2 noise
|
||||
SoundPinkNoise=3, // 1/f noise
|
||||
};
|
||||
|
||||
enum EnvelopeState {
|
||||
New=0, // newly spawned
|
||||
Wait, // ready but not yet started (waiting before starting or before repeating)
|
||||
Rise, // increasing
|
||||
BeginOvershoot,
|
||||
EndOvershoot,
|
||||
Hold, // staying at normal level
|
||||
Decay, // deacying
|
||||
Done, // done
|
||||
};
|
||||
|
||||
enum SoundDecayType {
|
||||
SoundDecayLinear = 0,
|
||||
SoundDecayExp = 1
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
// just for debugging/logging. don't rely on use
|
||||
const char *name;
|
||||
|
||||
// unique ID for accessing them via lookups etc. if sound is dead, this will be never reused
|
||||
int uid;
|
||||
|
||||
// if true, don't deallocate once this is done.
|
||||
bool keep_when_done;
|
||||
|
||||
// these are public and can be changed by the user.
|
||||
enum EnvelopeState envelope;
|
||||
|
||||
/****************************************************************/
|
||||
/* Special Parameters for specific types */
|
||||
/****************************************************************/
|
||||
|
||||
/******************** SoundFrequency ****************************/
|
||||
|
||||
float freq; // base frequency for type: SoundFrequency
|
||||
|
||||
/******************** White / Brownian / Pink Noise ****************/
|
||||
float leakage; // influence of previous state on current value (0..1)
|
||||
float scaling; // influence of white noise on next value
|
||||
|
||||
// If true and the value goes outside (-1..1), do the step in the other
|
||||
// direction to stay inside the valid range.
|
||||
bool skipDirectionIfClamping;
|
||||
|
||||
/******************** SoundFrequency: ***************************/
|
||||
|
||||
/****************************************************************/
|
||||
/* Common sound state / parameters */
|
||||
/****************************************************************/
|
||||
// progress of the sound which is used for the envelope but can be used for
|
||||
// other things too
|
||||
float time;
|
||||
|
||||
// scalar state that can be used arbitrarily to derive the waveform from it
|
||||
float state;
|
||||
|
||||
// time when the sound is done
|
||||
// if != 0.0, the sound changes with that
|
||||
// TODO: make attack gain decay stuff as envelope
|
||||
// TODO: add amplitude noise
|
||||
float _riseTime;
|
||||
float _relativeOvershootTime; // percentage of end of rise time that is used for overshoot (max amplitude at t_overshoot/2)
|
||||
float _holdTime;
|
||||
float _decayTime;
|
||||
float _waitTime;
|
||||
|
||||
// waveform of type of rise. default: linear
|
||||
enum SoundDecayType riseType;
|
||||
|
||||
// waveform of type of decay. default: linear
|
||||
enum SoundDecayType decayType;
|
||||
|
||||
// if true, play infinitely
|
||||
bool looping;
|
||||
|
||||
// base amplitude of the sound
|
||||
float amplitude;
|
||||
|
||||
// amplitude when signal is at minimum (before rise, after release and in wait time)
|
||||
float minAmplitude;
|
||||
|
||||
// these are al private!
|
||||
// never acces them
|
||||
enum Sound _type; // which type of sound
|
||||
|
||||
int _loopCount;
|
||||
|
||||
// if true, will get removed
|
||||
bool _done;
|
||||
bool _useEnvelope;
|
||||
|
||||
float *history; // if != 0, history is a pointer to 2*numHistorySamples samples.
|
||||
size_t numHistorySamples; // if != 0, save history for this sound.
|
||||
size_t lastHistorySample; // index/2 of last history sample. used
|
||||
} SoundHandle;
|
||||
|
||||
// initialize sound subsystem. return true if working, false on errors.
|
||||
bool initSound(void);
|
||||
|
||||
// start playing a sound.
|
||||
// args:
|
||||
// type - type of sound.
|
||||
// amplitude : 0..1
|
||||
// handle: if != NULL will point to the internal sound handle to change the
|
||||
// manipulate playing of the sound.
|
||||
// TODO: add sound from file to play too
|
||||
SoundHandle *playSound(enum Sound type, float amplitude);
|
||||
|
||||
// start playing a looping frequency.
|
||||
// args:
|
||||
// freq: frequency in Hz
|
||||
SoundHandle *playFrequency(float freq, float amplitude);
|
||||
|
||||
// set them.
|
||||
// args:
|
||||
// time of phases
|
||||
bool configureEnvelope(SoundHandle *handle, float rise, float hold, float decay);
|
||||
|
||||
bool configureOvershoot(SoundHandle *handle, float relativeOvershootTime);
|
||||
|
||||
// same as above but set to looping and set wait time between intervals
|
||||
bool configureEnvelopeWait(SoundHandle *handle, float rise, float hold, float decay, float wait);
|
||||
|
||||
// without looping but with wait
|
||||
bool configureEnvelopeLooping(SoundHandle *handle, float rise, float hold, float decay, float wait);
|
||||
|
||||
// getter for attributes. if 0.0, unused.
|
||||
float getRiseDuration(SoundHandle *handle);
|
||||
float getHoldDuration(SoundHandle *handle);
|
||||
float getDecayDuration(SoundHandle *handle);
|
||||
|
||||
float getWaitTime(SoundHandle *handle);
|
||||
void configureWaitTime(SoundHandle *handle, float waitTime);
|
||||
|
||||
// Set looping behaviour of the sound.
|
||||
// args:
|
||||
// / > 0, sound is looping for this number of repetitions
|
||||
// numRepetitions { == 0, sound is stopped when it is over
|
||||
// \ == -1, loop the sound forever
|
||||
//
|
||||
void setLoopCount(SoundHandle *handle, int numRepetitions);
|
||||
|
||||
// stop playing a sound. the handle is invalid now.
|
||||
bool stopSound(SoundHandle *handle);
|
||||
|
||||
// stop and remove all playing sounds. all handle become invalid after calling
|
||||
// this.
|
||||
void stopAllSounds(void);
|
||||
|
||||
void teardownSound(void);
|
||||
|
||||
// for debugging
|
||||
bool startDumpingWav(const char *filename);
|
||||
|
||||
// is deletede when 'keep' not set after stopping
|
||||
void stopDumpingWav(void);
|
||||
|
||||
int numActiveSounds(void);
|
||||
|
||||
void deleteSound(SoundHandle *handle);
|
||||
|
||||
//SoundHandle *playFreqEnvelope(float freq, float rise, float hold, float decay);
|
||||
|
||||
// TODO: add fade out/fade in overlay stuff for stopping sounds smoothly
|
||||
}
|
||||
|
||||
#endif
|
||||
77
game/sound/sound_effects.cpp
Normal file
77
game/sound/sound_effects.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#include "sound_effects.hpp"
|
||||
|
||||
#include "sound.hpp"
|
||||
|
||||
namespace sound {
|
||||
#if 0
|
||||
class MissileFlySoundEffect : public SoundEffect {
|
||||
MissileFlySoundEffect(const game::Missile *missile)
|
||||
: m_missileId(missile->id)
|
||||
, m_handle(sound::playSound(sound::playFrequency(440, 0.3))
|
||||
{
|
||||
}
|
||||
|
||||
~MissileFlySoundEffect()
|
||||
{
|
||||
delete(m_handle);
|
||||
}
|
||||
|
||||
bool advance(float dt)
|
||||
{
|
||||
for (const game::StateUpdateEvent *event : updates) {
|
||||
handleUpdateEvent(event);
|
||||
}
|
||||
|
||||
// TODO: add event if sth. comes into life or dies in a state
|
||||
// cycle.. delete objects that died last cycle in the current cycle
|
||||
// so pointers don't get invalid.
|
||||
|
||||
bool keep = false;
|
||||
for (const game::Missile *missile : state->missiles) {
|
||||
if (missile->id == m_missileId) {
|
||||
keep = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keep) {
|
||||
sound::stopSound(m_handle);
|
||||
sound::deleteSound(m_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
SoundHandle *m_handle;
|
||||
size_t m_missileId;
|
||||
};
|
||||
|
||||
void SoundEffects::advance(float dt, const std::list<game::StateUpdateEvent*> &updates)
|
||||
{
|
||||
//StateUpdateEvent::LifeCycle::Create,
|
||||
//StateUpdateEvent::Type::Explosion
|
||||
|
||||
std::vector<SoundEffect*> rm;
|
||||
|
||||
for (SoundEffect *effect : m_effects) {
|
||||
bool keep = effect->advance(dt, m_gameState);
|
||||
if (!keep) {
|
||||
rm.push_back(effect);
|
||||
}
|
||||
}
|
||||
|
||||
for (SoundEffect *effect : rm) {
|
||||
m_effects.remove(rm);
|
||||
delete(rm);
|
||||
}
|
||||
|
||||
// spawn new sounds
|
||||
//for (const game::Missile *missile : state->missiles) {
|
||||
// for (SoundEffect
|
||||
// if (missile->
|
||||
//}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
36
game/sound/sound_effects.hpp
Normal file
36
game/sound/sound_effects.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "state/state.hpp"
|
||||
|
||||
namespace sound {
|
||||
#if 0
|
||||
namespace game {
|
||||
class StateUpdateEvent;
|
||||
}
|
||||
|
||||
class SoundEffect {
|
||||
public:
|
||||
virtual ~SoundEffects()
|
||||
{
|
||||
}
|
||||
|
||||
// return false if it can be deleted
|
||||
virtual bool advance(float dt) = 0;
|
||||
};
|
||||
|
||||
class SoundEffects {
|
||||
public:
|
||||
SoundEffects()
|
||||
{
|
||||
}
|
||||
|
||||
void handleUpdateEvent(game::StateUpdateEvent *event);
|
||||
void advance(float dt, const std::list<game::StateUpdateEvent*> &updates);
|
||||
|
||||
private:
|
||||
std::list<SoundEffect*> m_effects;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue