KlassischeKeplerKriege/game/sound/sound.cpp
2016-10-03 18:45:24 +02:00

1071 lines
31 KiB
C++

#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>
#include <pthread.h>
namespace sound {
//struct Slot {
// bool used;
// bool usable;
// bool deleteOnCleanup;
// SoundHandle *handle;
//};
struct SoundContext {
PaStream *stream;
// this locks access to the context
pthread_mutex_t mutex;
unsigned int framesPerBuffer;
unsigned int sampleRate;
size_t sound_uid_counter;
// constant
size_t maxNumSounds;
// index of highest sound or -1, if there are no sounds.
// so 0 means no sounds, > 0 could mean different things depending on
// empty sounds.
volatile ssize_t highestSoundIndex;
// pointer to array of sound handles that has this number of active sounds
//Slot *soundSlots;
SoundHandle **sounds;
bool dumping;
const char *dump_filename;
SNDFILE *outfile;
SF_INFO sfinfo;
bool init;
};
// 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 #%zd (%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) {
fprintf(stderr, "[sound] warning: stop dumping wav returned error: %d\n", err);
} else {
printf("[sound] 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) {
fprintf(stderr, "[sound] 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("[sound] 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) {
fprintf(stderr, "[sound] Unable to dump to output file %s.\n", filename);
sf_perror(NULL);
return false;
};
printf("[sound] 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("[sound] 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 on no success
bool tryLock()
{
int ret = pthread_mutex_trylock(&context.mutex);
return ret == 0;
}
// wait if does not lock
void lock()
{
pthread_mutex_lock(&context.mutex);
}
void unlock()
{
pthread_mutex_unlock(&context.mutex);
}
// 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("[sound] warning: unimplemented sound type for #%zd: %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
ssize_t i;
for (i=0; i<context.highestSoundIndex; i++) {
SoundHandle *handle = context.sounds[i];
if (handle == NULL) {
continue;
}
if (handle->deleteFlag) {
continue;
}
if (handle->isDeleted) {
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 && !handle->deleteFlag) {
//printf("[sound] %f freeing sound #%d handle 0x%p\n", global_time, handle->uid, handle);
printf("[sound] %f mark sound #%zd handle 0x%p for deletion\n", global_time, handle->uid, handle);
//context.sounds[i] = NULL;
handle->deleteFlag = true;
//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 = 10;
context.highestSoundIndex = -1;
//context.soundSlots = (Slot*) calloc(context.maxNumSounds, 1000); // XXX HACK
context.sounds = (SoundHandle **) calloc(context.maxNumSounds, 1000); // XXX HACK
//assert(context.soundSlots != 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;
}
ssize_t nextFreeIndex = 0;
while(nextFreeIndex <= context.highestSoundIndex) {
if (context.sounds[nextFreeIndex] == NULL || context.sounds[nextFreeIndex]->isDeleted) {
// got an empty sound to use
break;
}
nextFreeIndex++;
}
if (context.sounds[nextFreeIndex] != NULL) {
printf("[sound] playSound() finally free'ing old sound: %zd\n",
context.sounds[nextFreeIndex]->uid);
free(context.sounds[nextFreeIndex]);
context.sounds[nextFreeIndex] = NULL;
}
if (context.sounds[nextFreeIndex] != NULL) {
fprintf(stderr, "[sound] warning: playSound() nextFreeIndex is "
"%zd but that sound is not NULL!\n", nextFreeIndex);
return NULL;
}
if (nextFreeIndex+1 >= (ssize_t) context.maxNumSounds) {
printf("[sound] warning: can't create more sounds, got maximum of: %zd\n", context.maxNumSounds);
return NULL;
}
SoundHandle *handle = (SoundHandle *) calloc(1, sizeof(SoundHandle));
handle->deleteFlag = false;
handle->isDeleted = false;
handle->_done = false;
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->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+1) {
context.highestSoundIndex++;
}
//if (nextFreeIndex < context.highestSoundIndex) {
// fprintf(stderr, "[sound] warning: nextFreeIndex > context.highestSoundIndex, "
// "this should not happen: %zd %zd\n", nextFreeIndex, context.highestSoundIndex);
//}
printf("[sound] playSound() created sound #%zd name %s\n", handle->uid, handle->name);
printf("[sound] highestSoundIndex now %zd\n", 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;
ssize_t i;
for (i=0; i<=context.highestSoundIndex; i++) {
// TODO: active is when not done or not deleted!
if (context.sounds[i] != NULL) {
num++;
}
}
return num;
}
void deleteSound(SoundHandle *handle)
{
if (handle == NULL) {
return;
}
// just to be sure
handle->_done = true;
// clear from that
ssize_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;
}
}
void deleteOldSounds(void)
{
ssize_t i;
for (i=0; i<=context.highestSoundIndex; i++) {
SoundHandle *handle = context.sounds[i];
if (handle != NULL && handle->deleteFlag) {
//context.sounds[i] = NULL;
printf("[sound] %f finally deleting sound #%zd handle 0x%p\n", global_time, handle->uid, handle);
if (handle->history != NULL) {
free(handle->history);
}
handle->isDeleted = true;
handle->deleteFlag = false;
//free(handle);
}
}
ssize_t nextHighestSoundIndex = -1;
for (i=0; i<=context.highestSoundIndex; i++) {
SoundHandle *handle = context.sounds[i];
if (handle != NULL) {
nextHighestSoundIndex = i;
}
}
if (context.highestSoundIndex != nextHighestSoundIndex) {
printf("[sound] deleteOldSounds() setting hightest sound index from %zd to %zd\n",
context.highestSoundIndex, nextHighestSoundIndex);
context.highestSoundIndex = nextHighestSoundIndex;
}
}
}