added audio
authorJohn Tsiombikas <nuclear@member.fsf.org>
Mon, 30 Oct 2017 19:59:55 +0000 (21:59 +0200)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Mon, 30 Oct 2017 19:59:55 +0000 (21:59 +0200)
13 files changed:
Makefile
src/app.cc
src/audio/audio.cc [new file with mode: 0644]
src/audio/audio.h [new file with mode: 0644]
src/audio/openal.h [new file with mode: 0644]
src/audio/ovstream.cc [new file with mode: 0644]
src/audio/ovstream.h [new file with mode: 0644]
src/audio/stream.cc [new file with mode: 0644]
src/audio/stream.h [new file with mode: 0644]
src/metascene.cc
src/metascene.h
src/timer.cc [new file with mode: 0644]
src/timer.h [new file with mode: 0644]

index d3220fc..4002a6d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,10 @@
-src = $(wildcard src/*.cc) $(wildcard src/machine/*.cc) $(wildcard src/blobs/*.cc)
-csrc = $(wildcard src/*.c) $(wildcard src/machine/*.c) $(wildcard src/blobs/*.c)
+src = $(wildcard src/*.cc) \
+         $(wildcard src/audio/*.cc) \
+         $(wildcard src/machine/*.cc) \
+         $(wildcard src/blobs/*.cc)
+csrc = $(wildcard src/*.c) \
+          $(wildcard src/machine/*.c) \
+          $(wildcard src/blobs/*.c)
 obj = $(src:.cc=.o) $(csrc:.c=.o)
 dep = $(obj:.o=.d)
 bin = demo
@@ -14,9 +19,9 @@ warn = -pedantic -Wall
 
 CFLAGS = $(warn) $(opt) $(dbg) $(incpath)
 CXXFLAGS = -std=c++11 $(warn) $(opt) $(dbg) $(incpath)
-LDFLAGS = $(libpath) -ldrawtext $(libgl_$(sys)) -lm -lgmath -lvmath -limago \
-                 -lresman -lpthread -lassimp -ltreestore -lgoatvr \
-                 `pkg-config --libs sdl2 freetype2` -lpng -ljpeg -lz
+LDFLAGS = $(libpath) -ldrawtext $(libgl_$(sys)) $(libal_$(sys)) -lm -lgmath -lvmath \
+                 -limago -lresman -lpthread -lassimp -ltreestore -lgoatvr \
+                 `pkg-config --libs sdl2 freetype2` -lpng -ljpeg -lz -lvorbisfile
 
 sys = $(shell uname -s | sed 's/MINGW.*/mingw/')
 libgl_Linux = -lGL -lGLU -lGLEW
@@ -24,6 +29,10 @@ libgl_Darwin = -framework OpenGL -lGLEW
 libgl_mingw = -lopengl32 -lglu32 -lglew32
 #libgl_mingw = -lglu32 -Wl,-Bstatic -lglew32 -Wl,-Bdynamic -lopengl32
 
+libal_Linux = -lopenal
+libal_Darwin = -framework OpenAL
+libal_mingw = #?
+
 ifeq ($(sys), mingw)
        bin = demo.exe
        LDFLAGS += -lmingw32 -lSDL2main -lSDL2 -lwinmm -mwindows
index 2f2cd50..7f6f077 100644 (file)
@@ -114,6 +114,8 @@ bool app_init(int argc, char **argv)
 
        glClearColor(1, 1, 1, 1);
 
+       init_audio();
+
        if(!init_vrhands()) {
                return false;
        }
@@ -166,11 +168,20 @@ bool app_init(int argc, char **argv)
        if(opt.vr || opt.fullscreen) {
                app_grab_mouse(true);
        }
+
+       if(mscn->music) {
+               mscn->music->play(AUDIO_PLAYMODE_LOOP);
+       }
        return true;
 }
 
 void app_cleanup()
 {
+       if(mscn->music) {
+               mscn->music->stop();
+       }
+       destroy_audio();
+
        app_grab_mouse(false);
        if(opt.vr) {
                goatvr_shutdown();
diff --git a/src/audio/audio.cc b/src/audio/audio.cc
new file mode 100644 (file)
index 0000000..b1543b4
--- /dev/null
@@ -0,0 +1,62 @@
+#include <stdio.h>
+#include "audio.h"
+
+#include "openal.h"
+
+static ALCdevice *dev;
+static ALCcontext *ctx;
+
+bool init_audio()
+{
+       if(!(dev = alcOpenDevice(0))) {
+               fprintf(stderr, "failed to open OpenAL device\n");
+               return false;
+       }
+
+       if(!(ctx = alcCreateContext(dev, 0))) {
+               fprintf(stderr, "failed to create context\n");
+               alcCloseDevice(dev);
+               return false;
+       }
+
+       alcMakeContextCurrent(ctx);
+
+       alGetError();
+       return true;
+}
+
+void destroy_audio()
+{
+       printf("shutting down audio system\n");
+       alcMakeContextCurrent(0);
+
+       if(ctx) {
+               alcDestroyContext(ctx);
+               ctx = 0;
+       }
+
+       if(dev) {
+               alcCloseDevice(dev);
+               dev = 0;
+       }
+}
+
+void set_audio_listener(const Mat4 &xform)
+{
+       float pos[3], orient[6];
+
+       pos[0] = xform[3][0];
+       pos[1] = xform[3][1];
+       pos[2] = xform[3][2];
+
+       orient[0] = xform[2][0];
+       orient[1] = xform[2][1];
+       orient[2] = -xform[2][2];
+
+       orient[3] = xform[1][0];
+       orient[4] = xform[1][1];
+       orient[5] = xform[1][2];
+
+       alListenerfv(AL_POSITION, pos);
+       alListenerfv(AL_ORIENTATION, orient);
+}
diff --git a/src/audio/audio.h b/src/audio/audio.h
new file mode 100644 (file)
index 0000000..0f00520
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef AUDIO_H_
+#define AUDIO_H_
+
+#include <gmath/gmath.h>
+
+bool init_audio();
+void destroy_audio();
+
+void set_audio_listener(const Mat4 &xform);
+
+#endif  // AUDIO_H_
diff --git a/src/audio/openal.h b/src/audio/openal.h
new file mode 100644 (file)
index 0000000..c3fc6ea
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef OPENAL_H_
+#define OPENAL_H_
+
+#ifndef __APPLE__
+#include <AL/al.h>
+#include <AL/alc.h>
+#else
+#include <OpenAL/al.h>
+#include <OpenAL/alc.h>
+#endif
+
+#endif  /* OPENAL_H_ */
diff --git a/src/audio/ovstream.cc b/src/audio/ovstream.cc
new file mode 100644 (file)
index 0000000..56f3033
--- /dev/null
@@ -0,0 +1,86 @@
+#include <stdio.h>
+#include <assert.h>
+#include "logger.h"
+#include "ovstream.h"
+
+OggVorbisStream::OggVorbisStream()
+{
+       vfopen = false;
+
+       pthread_mutex_init(&vflock, 0);
+}
+
+OggVorbisStream::~OggVorbisStream()
+{
+       close();
+}
+
+bool OggVorbisStream::open(const char *fname)
+{
+       close();
+
+       pthread_mutex_lock(&vflock);
+
+       if(ov_fopen(fname, &vf) != 0) {
+               error_log("failed to open ogg/vorbis stream: %s\n", fname ? fname : "<not found>");
+               pthread_mutex_unlock(&vflock);
+               return false;
+       }
+
+       vfopen = true;
+       pthread_mutex_unlock(&vflock);
+       return true;
+}
+
+void OggVorbisStream::close()
+{
+       pthread_mutex_lock(&vflock);
+       if(vfopen) {
+               ov_clear(&vf);
+               vfopen = false;
+       }
+       pthread_mutex_unlock(&vflock);
+}
+
+void OggVorbisStream::rewind()
+{
+       pthread_mutex_lock(&vflock);
+       if(vfopen) {
+               ov_raw_seek(&vf, 0);
+       }
+       pthread_mutex_unlock(&vflock);
+}
+
+bool OggVorbisStream::more_samples(AudioStreamBuffer *buf)
+{
+       pthread_mutex_lock(&vflock);
+
+       vorbis_info *vinfo = ov_info(&vf, -1);
+       buf->channels = vinfo->channels;
+       buf->sample_rate = vinfo->rate;
+       assert(buf->channels == 2);
+       assert(buf->sample_rate == 44100);
+
+       long bufsz = AUDIO_BUFFER_BYTES;
+       long total_read = 0;
+       while(total_read < bufsz) {
+               int bitstream;
+               long rd = ov_read(&vf, buf->samples + total_read, bufsz - total_read, 0, 2, 1, &bitstream);
+
+               if(!rd) {
+                       bufsz = total_read;
+               } else {
+                       total_read += rd;
+               }
+       }
+
+       if(!total_read) {
+               buf->num_samples = 0;
+               pthread_mutex_unlock(&vflock);
+               return false;
+       }
+
+       buf->num_samples = bufsz / vinfo->channels / 2;
+       pthread_mutex_unlock(&vflock);
+       return true;
+}
diff --git a/src/audio/ovstream.h b/src/audio/ovstream.h
new file mode 100644 (file)
index 0000000..772fa2a
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef OVSTREAM_H_
+#define OVSTREAM_H_
+
+#include <pthread.h>
+#include <vorbis/vorbisfile.h>
+#include "stream.h"
+
+class OggVorbisStream : public AudioStream {
+private:
+       OggVorbis_File vf;
+       bool vfopen;
+       pthread_mutex_t vflock;
+
+       virtual bool more_samples(AudioStreamBuffer *buf);
+
+public:
+       OggVorbisStream();
+       virtual ~OggVorbisStream();
+
+       bool open(const char *fname);
+       void close();
+
+       virtual void rewind();
+};
+
+#endif  // OVSTREAM_H_
diff --git a/src/audio/stream.cc b/src/audio/stream.cc
new file mode 100644 (file)
index 0000000..39a147c
--- /dev/null
@@ -0,0 +1,266 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <assert.h>
+#include "stream.h"
+#include "logger.h"
+#include "timer.h"
+
+#include "openal.h"
+
+static ALenum alformat(AudioStreamBuffer *buf)
+{
+       return buf->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
+}
+
+AudioStream::AudioStream()
+{
+       alsrc = 0;
+       poll_interval = 25;
+       done = true;
+       loop = false;
+       volume = 1.0;
+       pitch = 1.0;
+
+       pthread_mutex_init(&mutex, 0);
+}
+
+AudioStream::~AudioStream()
+{
+       stop();
+}
+
+bool AudioStream::open(const char *fname)
+{
+       return false;
+}
+
+void AudioStream::close()
+{
+}
+
+void AudioStream::set_volume(float vol)
+{
+       if(vol < 0.0) vol = 0.0;
+       if(vol > 1.0) vol = 1.0;
+
+       volume = vol;
+
+       pthread_mutex_lock(&mutex);
+       if(alsrc) {
+               alSourcef(alsrc, AL_GAIN, vol);
+       }
+       pthread_mutex_unlock(&mutex);
+}
+
+float AudioStream::get_volume() const
+{
+       return volume;
+}
+void AudioStream::set_pitch(float pitch)
+{
+       if(pitch < 0.0) pitch = 0.0;
+       if(pitch > 1.0) pitch = 1.0;
+
+       this->pitch = pitch;
+
+       pthread_mutex_lock(&mutex);
+       if(alsrc) {
+               alSourcef(alsrc, AL_PITCH, pitch);
+       }
+       pthread_mutex_unlock(&mutex);
+}
+
+float AudioStream::get_pitch() const
+{
+       return pitch;
+}
+
+
+static void *thread_func(void *arg)
+{
+       AudioStream *astr = (AudioStream*)arg;
+       astr->poll_loop();
+       return 0;
+}
+
+void AudioStream::play(AUDIO_PLAYMODE mode)
+{
+       loop = (mode == AUDIO_PLAYMODE_LOOP);
+       done = false;
+
+       if(pthread_create(&play_thread, 0, thread_func, this) != 0) {
+               error_log("failed to create music playback thread\n");
+       }
+}
+
+void AudioStream::stop()
+{
+       pthread_mutex_lock(&mutex);
+
+       if(alsrc) {
+               done = true;
+               //alSourceStop(alsrc);
+               printf("waiting for the music thread to stop\n");
+               pthread_mutex_unlock(&mutex);
+               pthread_join(play_thread, 0);
+       } else {
+               pthread_mutex_unlock(&mutex);
+       }
+}
+
+// gets an array of buffers and returns the index of the one matching id
+static inline int find_buffer(unsigned int id, unsigned int *barr, int num)
+{
+       for(int i=0; i<num; i++) {
+               if(barr[i] == id) {
+                       return i;
+               }
+       }
+       return -1;
+}
+
+
+static int queued_idx_list[AUDIO_NUM_BUFFERS];
+static int queued_idx_head = 0;
+static int queued_idx_tail = 0;
+
+#define BUFQ_UNQUEUE() \
+       do { \
+               queued_idx_tail = (queued_idx_tail + 1) % AUDIO_NUM_BUFFERS; \
+       } while(0)
+
+
+#define BUFQ_QUEUE(idx)        \
+       do { \
+               queued_idx_head = (queued_idx_head + 1) % AUDIO_NUM_BUFFERS; \
+               queued_idx_list[queued_idx_head] = idx; \
+       } while(0)
+
+// thread function
+void AudioStream::poll_loop()
+{
+       long prev_msec = -1000;
+       unsigned int albuf[AUDIO_NUM_BUFFERS];
+
+       pthread_mutex_lock(&mutex);
+       alGenSources(1, &alsrc);
+       alSourcei(alsrc, AL_LOOPING, AL_FALSE);
+       alSourcef(alsrc, AL_GAIN, volume);
+       alSourcef(alsrc, AL_PITCH, pitch);
+       alGenBuffers(AUDIO_NUM_BUFFERS, albuf);
+       AudioStreamBuffer *buf = new AudioStreamBuffer;
+
+       assert(alGetError() == 0);
+       for(int i=0; i<AUDIO_NUM_BUFFERS; i++) {
+               if(more_samples(buf)) {
+                       int bufsz = buf->num_samples * buf->channels * 2;       // 2 is for 16bit samples
+                       alBufferData(albuf[i], alformat(buf), buf->samples, bufsz, buf->sample_rate);
+
+                       if(alGetError()) {
+                               fprintf(stderr, "failed to load sample data into OpenAL buffer\n");
+                       }
+
+                       alSourceQueueBuffers(alsrc, 1, albuf + i);
+                       BUFQ_QUEUE(i);
+
+                       if(alGetError()) {
+                               fprintf(stderr, "failed to start streaming audio buffers\n");
+                       }
+               } else {
+                       break;
+               }
+       }
+
+       // start playback
+       alSourcePlay(alsrc);
+       while(!done) {
+               /* find out how many (if any) of the queued buffers are
+               * done, and free to be reused.
+               */
+               int num_buf_done;
+               alGetSourcei(alsrc, AL_BUFFERS_PROCESSED, &num_buf_done);
+               for(int i=0; i<num_buf_done; i++) {
+                       int err;
+                       // unqueue a buffer...
+                       unsigned int buf_id;
+                       alSourceUnqueueBuffers(alsrc, 1, &buf_id);
+
+                       if((err = alGetError())) {
+                               fprintf(stderr, "failed to unqueue used buffer (error: %x)\n", err);
+                               num_buf_done = i;
+                               break;
+                       }
+                       BUFQ_UNQUEUE();
+
+                       // find out which one of our al buffers we just unqueued
+                       int bidx = find_buffer(buf_id, albuf, AUDIO_NUM_BUFFERS);
+                       assert(bidx != -1);
+
+                       int looping;
+
+                       alGetSourcei(alsrc, AL_LOOPING, &looping);
+                       assert(looping == AL_FALSE);
+                       /*if((unsigned int)cur_buf == buf_id) {
+                               continue;
+                       }*/
+
+                       // if there are more data, fill it up and requeue it
+                       if(more_samples(buf)) {
+                               int bufsz = buf->num_samples * buf->channels * 2;       // 2 is for 16bit samples
+                               alBufferData(buf_id, alformat(buf), buf->samples, bufsz, buf->sample_rate);
+                               if((err = alGetError())) {
+                                       fprintf(stderr, "failed to load sample data into OpenAL buffer (error: %x)\n", err);
+                               }
+
+                               alSourceQueueBuffers(alsrc, 1, &buf_id);
+                               if(alGetError()) {
+                                       fprintf(stderr, "failed to start streaming audio buffers\n");
+                               }
+                               BUFQ_QUEUE(bidx);
+                       } else {
+                               // no more data...
+                               if(loop) {
+                                       debug_log("audio stream looping...\n");
+                                       rewind();
+                               } else {
+                                       done = true;
+                               }
+                       }
+               }
+
+               if(num_buf_done) {
+                       // make sure playback didn't stop
+                       int state;
+                       alGetSourcei(alsrc, AL_SOURCE_STATE, &state);
+                       if(state != AL_PLAYING) {
+                               alSourcePlay(alsrc);
+                       }
+               }
+
+               pthread_mutex_unlock(&mutex);
+               long msec = get_time_msec();
+               long dt = msec - prev_msec;
+               prev_msec = msec;
+
+               if(dt < (long)poll_interval) {
+                       sleep_msec(poll_interval - dt);
+               } else {
+                       sched_yield();
+               }
+               pthread_mutex_lock(&mutex);
+       }
+
+
+       // done with the data, wait for the source to stop playing before cleanup
+       int state;
+       while(alGetSourcei(alsrc, AL_SOURCE_STATE, &state), state == AL_PLAYING) {
+               sched_yield();
+       }
+
+       alDeleteBuffers(AUDIO_NUM_BUFFERS, albuf);
+       alDeleteSources(1, &alsrc);
+       alsrc = 0;
+       pthread_mutex_unlock(&mutex);
+
+       delete buf;
+}
diff --git a/src/audio/stream.h b/src/audio/stream.h
new file mode 100644 (file)
index 0000000..9a2387b
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef STREAM_H_
+#define STREAM_H_
+
+#include <pthread.h>
+#include "audio.h"
+
+#define AUDIO_NUM_BUFFERS              8
+#define AUDIO_BUFFER_MSEC              32
+// TODO should the sampling rate be hardcoded?
+#define AUDIO_BUFFER_SAMPLES   (AUDIO_BUFFER_MSEC * 44100 / 1000)
+// TODO unhardcode the channels number
+#define AUDIO_BUFFER_BYTES             (AUDIO_BUFFER_SAMPLES * 2 * 2)
+
+enum AUDIO_PLAYMODE
+{
+       AUDIO_PLAYMODE_ONCE,
+       AUDIO_PLAYMODE_LOOP
+};
+
+struct AudioStreamBuffer {
+       char samples[AUDIO_BUFFER_BYTES];
+
+       int num_samples;
+       int channels;
+       int sample_rate;
+};
+
+class AudioStream {
+private:
+       pthread_t play_thread;
+       pthread_mutex_t mutex;
+
+       float volume, pitch;
+       bool done, loop;
+       unsigned int poll_interval;
+       unsigned int alsrc;
+
+       virtual bool more_samples(AudioStreamBuffer *buf) = 0;
+
+public:
+       void poll_loop();
+
+       AudioStream();
+       virtual ~AudioStream();
+
+       virtual bool open(const char *fname);
+       virtual void close();
+
+       virtual void set_volume(float vol);
+       virtual float get_volume() const;
+
+       virtual void set_pitch(float p);
+       virtual float get_pitch() const;
+
+       virtual void play(AUDIO_PLAYMODE mode);
+       virtual void stop();
+
+       virtual void rewind() = 0;
+};
+
+#endif  // AUDIO_STREAM_H_
index 2328f33..66be3ef 100644 (file)
@@ -22,6 +22,7 @@ struct MaterialEdit {
 static bool proc_node(MetaScene *mscn, struct ts_node *node);
 static bool proc_scenefile(MetaScene *mscn, struct ts_node *node);
 static bool proc_mtledit(MetaScene *mscn, MaterialEdit *med, struct ts_node *node);
+static bool proc_music(MetaScene *mscn, struct ts_node *node);
 static void apply_mtledit(Scene *scn, const MaterialEdit &med);
 static void apply_mtledit(Material *mtl, const MaterialEdit &med);
 static struct ts_attr *attr_inscope(struct ts_node *node, const char *name);
@@ -32,11 +33,13 @@ static void print_scene_graph(SceneNode *n, int level);
 MetaScene::MetaScene()
 {
        walk_mesh = 0;
+       music = 0;
 }
 
 MetaScene::~MetaScene()
 {
        delete walk_mesh;
+       delete music;
 }
 
 
@@ -146,6 +149,9 @@ static bool proc_node(MetaScene *mscn, struct ts_node *node)
                if(match && replace) {
                        mscn->datamap.map(match, replace);
                }
+
+       } else if(strcmp(node->name, "music") == 0) {
+               return proc_music(mscn, node);
        }
 
        return true;
@@ -366,6 +372,37 @@ static void apply_mtledit(Scene *scn, const MaterialEdit &med)
        }
 }
 
+static bool proc_music(MetaScene *mscn, struct ts_node *node)
+{
+       const char *fname = ts_get_attr_str(node, "file");
+       if(fname) {
+               SceneData *sdat = new SceneData;
+               sdat->meta = mscn;
+
+               // datapath
+               struct ts_attr *adpath = attr_inscope(node, "datapath");
+               if(adpath && adpath->val.type == TS_STRING) {
+                       mscn->datamap.set_path(adpath->val.str);
+               }
+
+               int namesz = mscn->datamap.lookup(fname, 0, 0);
+               char *namebuf = (char*)alloca(namesz + 1);
+               if(mscn->datamap.lookup(fname, namebuf, namesz + 1)) {
+                       fname = namebuf;
+               }
+
+               OggVorbisStream *ovstream = new OggVorbisStream;
+               if(!ovstream->open(fname)) {
+                       delete ovstream;
+                       return false;
+               }
+
+               delete mscn->music;
+               mscn->music = ovstream;
+       }
+       return true;
+}
+
 static void apply_mtledit(Material *mtl, const MaterialEdit &med)
 {
        // TODO more edit modes...
index 9ac9092..c363901 100644 (file)
@@ -4,6 +4,7 @@
 #include <map>
 #include "scene.h"
 #include "mesh.h"
+#include "audio/ovstream.h"
 #include "datamap.h"
 
 class MetaScene {
@@ -18,6 +19,7 @@ public:
 
        std::map<Scene*, void*> scndata;
 
+       AudioStream *music;
 
        MetaScene();
        ~MetaScene();
diff --git a/src/timer.cc b/src/timer.cc
new file mode 100644 (file)
index 0000000..95f6c0c
--- /dev/null
@@ -0,0 +1,118 @@
+#include "timer.h"
+
+#if defined(__APPLE__) && !defined(__unix__)
+#define __unix__
+#endif
+
+#ifdef __unix__
+#include <time.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#ifdef CLOCK_MONOTONIC
+unsigned long get_time_msec(void)
+{
+       struct timespec ts;
+       static struct timespec ts0;
+
+       clock_gettime(CLOCK_MONOTONIC, &ts);
+       if(ts0.tv_sec == 0 && ts0.tv_nsec == 0) {
+               ts0 = ts;
+               return 0;
+       }
+       return (ts.tv_sec - ts0.tv_sec) * 1000 + (ts.tv_nsec - ts0.tv_nsec) / 1000000;
+}
+#else  /* no fancy POSIX clocks, fallback to good'ol gettimeofday */
+unsigned long get_time_msec(void)
+{
+       struct timeval tv;
+       static struct timeval tv0;
+
+       gettimeofday(&tv, 0);
+       if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
+               tv0 = tv;
+               return 0;
+       }
+       return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
+}
+#endif /* !posix clock */
+
+void sleep_msec(unsigned long msec)
+{
+       usleep(msec * 1000);
+}
+#endif
+
+#ifdef WIN32
+#include <windows.h>
+#pragma comment(lib, "winmm.lib")
+
+unsigned long get_time_msec(void)
+{
+       return timeGetTime();
+}
+
+void sleep_msec(unsigned long msec)
+{
+       Sleep(msec);
+}
+#endif
+
+double get_time_sec(void)
+{
+       return get_time_msec() / 1000.0f;
+}
+
+void sleep_sec(double sec)
+{
+       if(sec > 0.0f) {
+               sleep_msec(sec * 1000.0f);
+       }
+}
+
+
+Timer::Timer()
+{
+       reset();
+}
+
+void Timer::reset()
+{
+       pause_time = 0;
+       start_time = get_time_msec();
+}
+
+void Timer::start()
+{
+       if(!is_running()) {
+               // resuming
+               start_time += get_time_msec() - pause_time;
+               pause_time = 0;
+       }
+}
+
+void Timer::stop()
+{
+       if(is_running()) {
+               pause_time = get_time_msec();
+       }
+}
+
+bool Timer::is_running() const
+{
+       return pause_time == 0;
+}
+
+unsigned long Timer::get_msec() const
+{
+       if(!is_running()) {
+               // in paused state...
+               return pause_time - start_time;
+       }
+       return get_time_msec() - start_time;
+}
+
+double Timer::get_sec() const
+{
+       return (double)get_msec() / 1000.0;
+}
diff --git a/src/timer.h b/src/timer.h
new file mode 100644 (file)
index 0000000..8f1e9b1
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef TIMER_H_
+#define TIMER_H_
+
+unsigned long get_time_msec(void);
+void sleep_msec(unsigned long msec);
+
+double get_time_sec(void);
+void sleep_sec(double sec);
+
+
+class Timer {
+private:
+       unsigned long start_time, pause_time;
+
+public:
+       Timer();
+
+       void reset();
+
+       void start();
+       void stop();
+
+       bool is_running() const;
+
+       unsigned long get_msec() const;
+       double get_sec() const;
+};
+
+#endif // TIMER_H_