-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
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
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
glClearColor(1, 1, 1, 1);
+ init_audio();
+
if(!init_vrhands()) {
return false;
}
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();
--- /dev/null
+#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);
+}
--- /dev/null
+#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_
--- /dev/null
+#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_ */
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
--- /dev/null
+#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;
+}
--- /dev/null
+#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_
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);
MetaScene::MetaScene()
{
walk_mesh = 0;
+ music = 0;
}
MetaScene::~MetaScene()
{
delete walk_mesh;
+ delete music;
}
if(match && replace) {
mscn->datamap.map(match, replace);
}
+
+ } else if(strcmp(node->name, "music") == 0) {
+ return proc_music(mscn, node);
}
return true;
}
}
+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...
#include <map>
#include "scene.h"
#include "mesh.h"
+#include "audio/ovstream.h"
#include "datamap.h"
class MetaScene {
std::map<Scene*, void*> scndata;
+ AudioStream *music;
MetaScene();
~MetaScene();
--- /dev/null
+#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;
+}
--- /dev/null
+#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_