From: John Tsiombikas Date: Mon, 30 Oct 2017 19:59:55 +0000 (+0200) Subject: added audio X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=laserbrain_demo;a=commitdiff_plain;h=fb11663a3654acd0132e71e5652e35b0ea72d544 added audio --- diff --git a/Makefile b/Makefile index d3220fc..4002a6d 100644 --- 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 diff --git a/src/app.cc b/src/app.cc index 2f2cd50..7f6f077 100644 --- a/src/app.cc +++ b/src/app.cc @@ -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 index 0000000..b1543b4 --- /dev/null +++ b/src/audio/audio.cc @@ -0,0 +1,62 @@ +#include +#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 index 0000000..0f00520 --- /dev/null +++ b/src/audio/audio.h @@ -0,0 +1,11 @@ +#ifndef AUDIO_H_ +#define AUDIO_H_ + +#include + +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 index 0000000..c3fc6ea --- /dev/null +++ b/src/audio/openal.h @@ -0,0 +1,12 @@ +#ifndef OPENAL_H_ +#define OPENAL_H_ + +#ifndef __APPLE__ +#include +#include +#else +#include +#include +#endif + +#endif /* OPENAL_H_ */ diff --git a/src/audio/ovstream.cc b/src/audio/ovstream.cc new file mode 100644 index 0000000..56f3033 --- /dev/null +++ b/src/audio/ovstream.cc @@ -0,0 +1,86 @@ +#include +#include +#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 : ""); + 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 index 0000000..772fa2a --- /dev/null +++ b/src/audio/ovstream.h @@ -0,0 +1,26 @@ +#ifndef OVSTREAM_H_ +#define OVSTREAM_H_ + +#include +#include +#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 index 0000000..39a147c --- /dev/null +++ b/src/audio/stream.cc @@ -0,0 +1,266 @@ +#include +#include +#include +#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; inum_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; inum_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 index 0000000..9a2387b --- /dev/null +++ b/src/audio/stream.h @@ -0,0 +1,61 @@ +#ifndef STREAM_H_ +#define STREAM_H_ + +#include +#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_ diff --git a/src/metascene.cc b/src/metascene.cc index 2328f33..66be3ef 100644 --- a/src/metascene.cc +++ b/src/metascene.cc @@ -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... diff --git a/src/metascene.h b/src/metascene.h index 9ac9092..c363901 100644 --- a/src/metascene.h +++ b/src/metascene.h @@ -4,6 +4,7 @@ #include #include "scene.h" #include "mesh.h" +#include "audio/ovstream.h" #include "datamap.h" class MetaScene { @@ -18,6 +19,7 @@ public: std::map scndata; + AudioStream *music; MetaScene(); ~MetaScene(); diff --git a/src/timer.cc b/src/timer.cc new file mode 100644 index 0000000..95f6c0c --- /dev/null +++ b/src/timer.cc @@ -0,0 +1,118 @@ +#include "timer.h" + +#if defined(__APPLE__) && !defined(__unix__) +#define __unix__ +#endif + +#ifdef __unix__ +#include +#include +#include + +#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 +#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 index 0000000..8f1e9b1 --- /dev/null +++ b/src/timer.h @@ -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_