adding event meachinsm for game items that change and sound support.

This commit is contained in:
Andreas Ortmann 2016-10-02 23:49:40 +02:00
parent 9ce106c179
commit f49d07fdc5
15 changed files with 1468 additions and 43 deletions

View file

@ -22,6 +22,10 @@ set(GAME_SRC
util.cpp
game.cpp
# TODO: make optional!
sound/sound.cpp
sound/sound_effects.cpp
state/object.cpp
state/explosion.cpp
state/trace.cpp
@ -43,4 +47,4 @@ include_directories(${assimp_INCLUDE_DIRS})
add_executable(game ${GAME_SRC})
setup_target(game)
target_link_libraries(game X11 epoxy pthread ${assimp_LIBRARIES} assimp)
target_link_libraries(game X11 epoxy pthread ${assimp_LIBRARIES} assimp -lportaudio -lsndfile)

View file

@ -10,6 +10,9 @@
#include "network/server.hpp"
#include "options.hpp"
#include "sound/sound_effects.hpp"
#include "state/state_update_event.hpp"
#include <sys/time.h>
uint64_t optionsFlags;
@ -19,6 +22,7 @@ using asio::ip::tcp;
int main(int argc, char *argv[])
{
bool devMode = false;
bool soundEnabled = false;
char port[]="3490";
static struct option long_options[] =
@ -33,6 +37,7 @@ int main(int argc, char *argv[])
// {"delete", required_argument, 0, 'd'},
{"autorun", required_argument, 0, 'a'},
{"port", required_argument, 0, 'p'},
{"sound", no_argument, 0, 's'},
{"fps", no_argument, 0, 'f'},
{"dev", no_argument, 0, 'd'},
{0, 0, 0, 0}
@ -41,7 +46,7 @@ int main(int argc, char *argv[])
int option_index = 0;
while(1){
char c = getopt_long (argc, argv, "p:fad",
char c = getopt_long (argc, argv, "p:fads",
long_options, &option_index);
if (c == -1)
break;
@ -51,6 +56,7 @@ int main(int argc, char *argv[])
case 'f':
SET_FLAG(SHOW_FPS,true);
break;
case 'd':
std::cout<<"enabling developer mode" << std::endl;
devMode = true;
@ -60,6 +66,10 @@ int main(int argc, char *argv[])
strcpy(port,optarg);
break;
case 's':
soundEnabled = true;
break;
case 'a':
SET_FLAG(TEST_AUTORUN, true);
break;
@ -75,6 +85,13 @@ int main(int argc, char *argv[])
srand(time(NULL));
//sound::SoundEffects *sounds = nullptr;
if (soundEnabled) {
//if (sound::initSound()) {
//sounds = new sound::SoundEffects();
//}
}
Game game;
game.state()->setDeveloperMode(devMode);
@ -89,6 +106,10 @@ int main(int argc, char *argv[])
while(window.running()){
window.poll();
io_service.poll();
//if (sounds != nullptr) {
// sounds.advance(game->state());
//}
}
return 0;

View file

@ -33,5 +33,5 @@ private:
char m_rcv_data[max_length];
game::State* m_state;
bool m_started = false;
int m_pid;
size_t m_pid;
};

968
game/sound/sound.cpp Normal file
View 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
View 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

View 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
}

View 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
}

View file

@ -14,18 +14,16 @@ namespace game {
// angles are supplied in degrees and are CCW
Missile *missile = new Missile(
state->generateMissileId(),
player,
player->ship->position,
-util::deg2rad(m_angle),
0.005*player->speed);
Trace *trace = new Trace(missile);
missile->trace = trace;
player->energy -= player->speed;
player->missiles.push_back(missile);
state->addTrace(trace);
state->addMissile(missile);
}
bool ShootCommand::ready(const Player *player, const State *state) const

View file

@ -0,0 +1,21 @@
#pragma once
#include "state/explosion.hpp"
#include "state/state_update_event.hpp"
namespace game {
class ExplosionEvent : public StateUpdateEvent {
public:
ExplosionEvent(StateUpdateEvent::LifeCycle lifeCycle, Explosion *explosion)
: StateUpdateEvent(lifeCycle, StateUpdateEvent::EventType::Explosion)
, explosion(explosion)
{
std::string typeStr = StateUpdateEvent::lifeCycleToString(lifeCycle);
std::cout<<"created explosion event for id " << explosion->id << " of type " << typeStr << std::endl;
}
public:
Explosion *explosion;
};
}

View file

@ -11,8 +11,9 @@
#include <glm/gtx/norm.hpp>
namespace game {
Missile::Missile(Player *player, const glm::vec2 &pos, float angle, float speed)
: player(player)
Missile::Missile(size_t id, Player *player, const glm::vec2 &pos, float angle, float speed)
: id(id)
, player(player)
, position(pos)
{
velocity = speed * glm::vec2(std::sin(angle), std::cos(angle));

View file

@ -57,13 +57,14 @@ namespace game {
int planetId;
};
Missile(Player *player, const glm::vec2 &pos, float angle, float speed);
Missile(size_t id, Player *player, const glm::vec2 &pos, float angle, float speed);
~Missile();
// try to advance. if something will be hit, return the first hit in
// time.
Missile::Event advance(const game::State *state, float dt);
const size_t id;
Player *player; // owner won't be hit by own missiles
glm::vec2 position;
glm::vec2 velocity;

View file

@ -14,7 +14,7 @@ namespace game {
class Player {
public:
int id;
size_t id;
bool alive;
float speed;
float energy;
@ -23,7 +23,7 @@ namespace game {
std::string name;
std::list<Missile*> missiles;
Player(int id) : id(id), alive(true), speed(1.0), energy(0.0), ship(nullptr), name("<unnamed>")
Player(size_t id) : id(id), alive(true), speed(1.0), energy(0.0), ship(nullptr), name("<unnamed>")
{
}

View file

@ -15,6 +15,9 @@
#include "trace.hpp"
#include "explosion.hpp"
#include "state_update_event.hpp"
#include "events/explosion_event.hpp"
#include "util.hpp"
namespace game {
@ -28,7 +31,8 @@ namespace game {
//for (Player *player : players) {
//}
m_nextId = 0;
m_nextPlayerId = 0;
m_nextMissileId = 0;
m_time = 0.0;
m_shipRadius = 0.02;
m_maxMissileDistance = 2.0;
@ -146,15 +150,15 @@ namespace game {
return true;
}
int State::addPlayer()
size_t State::addPlayer()
{
int playerId = m_nextId++;
size_t playerId = m_nextPlayerId++;
Player *player = new Player(playerId);
players.push_back(player);
return playerId;
}
void State::quitPlayer(int playerId)
void State::quitPlayer(size_t playerId)
{
std::cout << playerId << " quit" << std::endl;
@ -168,12 +172,12 @@ namespace game {
}
}
void State::clear(int playerId)
void State::clear(size_t playerId)
{
std::cout << playerId << " clear" << std::endl;
}
void State::setName(int playerId, std::string name)
void State::setName(size_t playerId, std::string name)
{
// discard if not unique
for (const Player *other : players) {
@ -186,7 +190,7 @@ namespace game {
playerForId(playerId)->name = name;
}
void State::setSpeed(int playerId, double speed)
void State::setSpeed(size_t playerId, double speed)
{
playerForId(playerId)->speed = speed;
}
@ -338,12 +342,15 @@ namespace game {
for (Explosion *explosion : rm) {
explosions.remove(explosion);
delete(explosion);
m_nextEvents.push_back(new ExplosionEvent(StateUpdateEvent::LifeCycle::Destroy, explosion));
//delete(explosion);
}
}
void State::advance(float dt)
{
//std::cout<<"[state] (init) cycle: update events length is " << m_nextEvents.size() << std::endl;
m_time += dt;
advancePlayerShipSpawns(dt);
@ -355,9 +362,19 @@ namespace game {
advancePlayerCommands(dt);
advancePlayerMissiles(dt);
//std::cout<<"[state] (before move) cycle: update events length is " << m_nextEvents.size() << std::endl;
// put collected events into that list.
m_allEvents.push_back(std::move(m_nextEvents));
//std::cout<<"[state] (after move) cycle: update events length is " << m_nextEvents.size() << std::endl;
// finally remove things.
//m_nextEvents.clear();
}
Player *State::playerForId(int playerId)
Player *State::playerForId(size_t playerId)
{
for (Player *p : players) {
if (p->id == playerId) {
@ -402,7 +419,7 @@ namespace game {
}
}
void State::commandForPlayer(int playerId, game::Command *cmd)
void State::commandForPlayer(size_t playerId, game::Command *cmd)
{
Player *player = playerForId(playerId);
if (player != nullptr) {
@ -425,6 +442,14 @@ namespace game {
traces.push_back(trace);
}
void State::addMissile(Missile *missile)
{
Trace *trace = new Trace(missile);
missile->trace = trace;
addTrace(trace);
}
void State::deleteTrace(Trace *trace)
{
if (trace->missile != nullptr) {
@ -442,7 +467,10 @@ namespace game {
return;
}
explosions.push_back(new Explosion(m_nextExplosionId++, evt->position, evt->missileVelocity, evt->hit));
Explosion *explosion = new Explosion(m_nextExplosionId++, evt->position, evt->missileVelocity, evt->hit);
explosions.push_back(explosion);
m_nextEvents.push_back(new ExplosionEvent(StateUpdateEvent::LifeCycle::Create, explosion));
}
void State::advanceTraceAges(float dt)
@ -472,4 +500,23 @@ namespace game {
{
m_playingFieldSize = glm::vec2(width, height);
}
size_t State::generateMissileId()
{
return m_nextMissileId++;
}
void State::applyAndClearAllOldStateUpdates()
{
// TODO: delete the items for events that are to be removed in proper
// way
for (std::list<StateUpdateEvent*> list : m_allEvents) {
for (StateUpdateEvent *evt : list) {
delete(evt);
}
list.clear();
}
m_allEvents.clear();
}
}

View file

@ -28,6 +28,7 @@ namespace game {
// forward declarations
class Command;
class Player;
class StateUpdateEvent;
class Ship;
class Trace;
@ -53,15 +54,15 @@ namespace game {
// The upper layer (network/renderer) calling these three functions
// should keep id's unique and give one (network) input an id.
int addPlayer();
void clear(int playerId);
void setName(int playerId, std::string name);
void setSpeed(int playerId, double speed);
void quitPlayer(int playerId);
void commandForPlayer(int playerId, Command *cmd);
size_t addPlayer();
void clear(size_t playerId);
void setName(size_t playerId, std::string name);
void setSpeed(size_t playerId, double speed);
void quitPlayer(size_t playerId);
void commandForPlayer(size_t playerId, Command *cmd);
// lookup. return nullptr on invalid playerId
Player *playerForId(int playerId);
Player *playerForId(size_t playerId);
/*************************************************************************/
/* Mixed stuff */
@ -75,6 +76,9 @@ namespace game {
// add a trace to the list of traces.
void addTrace(Trace *trace);
// add a missile
void addMissile(Missile *missile);
// delete traces with this command
void deleteTrace(Trace *trace); // using a pointer
@ -97,10 +101,17 @@ namespace game {
void setPlayingFieldSize(int width, int height);
void setPlayingFieldCenter(int width, int height);
size_t generateMissileId();
void applyAndClearAllOldStateUpdates();
/*************************************************************************/
/* Rendering */
/*************************************************************************/
// TODO: hide and replace by events
// Game items which should be rendered are here:
// (access missiles by iterating over player's missiles attribute)
std::vector<Planet*> planets;
@ -139,13 +150,17 @@ namespace game {
float m_playerRespawnTime;
float m_shipRadius;
float m_defaultEnergy;
int m_nextId;
int m_maxNumTraces;
size_t m_nextPlayerId;
size_t m_nextExplosionId;
size_t m_nextMissileId;
float m_time;
bool m_developerMode;
glm::vec2 m_playingFieldCenter;
glm::vec2 m_playingFieldSize;
std::list<StateUpdateEvent*> m_nextEvents;
std::vector<std::list<StateUpdateEvent*>> m_allEvents;
};
};
}

View file

@ -0,0 +1,51 @@
#pragma once
#include <string>
// TODO: make life cycle object class.
// objects from that class can be created and destroyed only through factory
// methods which create updates too.
namespace game {
class StateUpdateEvent {
public:
enum class LifeCycle {
Create, // something was created
Modify, // something was modified (look at attributes)
Destroy // something was destroyed
};
// add all possible classes here
enum class EventType {
Explosion
};
StateUpdateEvent(LifeCycle cycle, EventType event)
: m_lifeCycle(cycle), m_eventType(event)
{
}
LifeCycle lifeCycle() const { return m_lifeCycle; }
EventType eventType() const { return m_eventType; }
std::string description() const
{
// TODO
return "<event>";
}
static std::string lifeCycleToString(LifeCycle lifeCycle)
{
switch(lifeCycle) {
case StateUpdateEvent::LifeCycle::Create: return "create";
case StateUpdateEvent::LifeCycle::Modify: return "modify";
case StateUpdateEvent::LifeCycle::Destroy: return "destroy";
default: return "<invalid>";
}
}
private:
const LifeCycle m_lifeCycle;
const EventType m_eventType;
};
}