mirror of https://github.com/bjornbytes/lovr.git
commit
7cce1e30a7
|
@ -7,3 +7,4 @@ src/obj
|
|||
src/Tupfile
|
||||
*.lua
|
||||
*.glsl
|
||||
.DS_Store
|
||||
|
|
|
@ -79,6 +79,17 @@ else()
|
|||
set(LOVR_ASSIMP ${ASSIMP_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# OpenAL
|
||||
if (WIN32)
|
||||
add_subdirectory(deps/openal-soft openal)
|
||||
include_directories(deps/openal-soft/include)
|
||||
set(LOVR_OPENAL OpenAL32)
|
||||
else()
|
||||
pkg_search_module(OPENAL REQUIRED openal)
|
||||
include_directories(${OPENAL_INCLUDE_DIRS})
|
||||
set(LOVR_OPENAL ${OPENAL_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# openvr
|
||||
if(WIN32)
|
||||
include_directories(deps/openvr/headers)
|
||||
|
@ -109,6 +120,7 @@ set(LOVR_LIB
|
|||
${LOVR_GLFW}
|
||||
${LOVR_PHYSFS}
|
||||
${LOVR_ASSIMP}
|
||||
${LOVR_OPENAL}
|
||||
${LOVR_OPENVR}
|
||||
)
|
||||
|
||||
|
@ -120,4 +132,5 @@ if(WIN32)
|
|||
move_dll(${LOVR_GLFW})
|
||||
move_dll(${LOVR_PHYSFS})
|
||||
move_dll(${LOVR_ASSIMP})
|
||||
move_dll(${LOVR_OPENAL})
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
#include "audio/audio.h"
|
||||
#include "loaders/source.h"
|
||||
#include "util.h"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
static AudioState state;
|
||||
|
||||
static LPALCRESETDEVICESOFT alcResetDeviceSOFT;
|
||||
|
||||
static void cross(float ux, float uy, float uz, float vx, float vy, float vz, float* x, float* y, float* z) {
|
||||
*x = uy * vz - uz * vy;
|
||||
*y = ux * vz - uz * vx;
|
||||
*z = ux * vy - uy * vx;
|
||||
}
|
||||
|
||||
void lovrAudioInit() {
|
||||
ALCdevice* device = alcOpenDevice(NULL);
|
||||
if (!device) {
|
||||
error("Unable to open default audio device");
|
||||
}
|
||||
|
||||
ALCcontext* context = alcCreateContext(device, NULL);
|
||||
if (!context || !alcMakeContextCurrent(context) || alcGetError(device) != ALC_NO_ERROR) {
|
||||
error("Unable to create OpenAL context");
|
||||
}
|
||||
|
||||
alcResetDeviceSOFT = (LPALCRESETDEVICESOFT) alcGetProcAddress(device, "alcResetDeviceSOFT");
|
||||
|
||||
if (alcIsExtensionPresent(device, "ALC_SOFT_HRTF")) {
|
||||
ALCint attrs[3] = { ALC_HRTF_SOFT, ALC_TRUE, 0 };
|
||||
alcResetDeviceSOFT(device, attrs);
|
||||
}
|
||||
|
||||
state.device = device;
|
||||
state.context = context;
|
||||
vec_init(&state.sources);
|
||||
}
|
||||
|
||||
void lovrAudioDestroy() {
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(state.context);
|
||||
alcCloseDevice(state.device);
|
||||
vec_deinit(&state.sources);
|
||||
}
|
||||
|
||||
void lovrAudioUpdate() {
|
||||
int i; Source* source;
|
||||
vec_foreach_rev(&state.sources, source, i) {
|
||||
int isStopped = lovrSourceIsStopped(source);
|
||||
ALint processed;
|
||||
alGetSourcei(source->id, AL_BUFFERS_PROCESSED, &processed);
|
||||
|
||||
if (processed) {
|
||||
ALuint buffers[SOURCE_BUFFERS];
|
||||
alSourceUnqueueBuffers(source->id, processed, buffers);
|
||||
lovrSourceStream(source, buffers, processed);
|
||||
if (isStopped) {
|
||||
alSourcePlay(source->id);
|
||||
}
|
||||
} else if (isStopped) {
|
||||
vec_splice(&state.sources, i, 1);
|
||||
lovrRelease(&source->ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void lovrAudioAdd(Source* source) {
|
||||
if (!lovrAudioHas(source)) {
|
||||
lovrRetain(&source->ref);
|
||||
vec_push(&state.sources, source);
|
||||
}
|
||||
}
|
||||
|
||||
void lovrAudioGetOrientation(float* angle, float* ax, float* ay, float* az) {
|
||||
float v[6];
|
||||
alGetListenerfv(AL_ORIENTATION, v);
|
||||
float cx, cy, cz;
|
||||
cross(v[0], v[1], v[2], v[3], v[4], v[5], &cx, &cy, &cz);
|
||||
float w = 1 + v[0] * v[3] + v[1] * v[4] + v[2] * v[5];
|
||||
float len = sqrt(cx * cx + cy * cy + cz * cz + w * w);
|
||||
if (len != 1) {
|
||||
cx /= len;
|
||||
cy /= len;
|
||||
cz /= len;
|
||||
w /= len;
|
||||
}
|
||||
*angle = 2 * acos(w);
|
||||
float s = sqrt(1 - w * w);
|
||||
if (s < .001) {
|
||||
*ax = cx;
|
||||
*ay = cy;
|
||||
*az = cz;
|
||||
} else {
|
||||
*ax = cx / s;
|
||||
*ay = cy / s;
|
||||
*az = cz / s;
|
||||
}
|
||||
}
|
||||
|
||||
void lovrAudioGetPosition(float* x, float* y, float* z) {
|
||||
alGetListener3f(AL_POSITION, x, y, z);
|
||||
}
|
||||
|
||||
float lovrAudioGetVolume() {
|
||||
float volume;
|
||||
alGetListenerf(AL_GAIN, &volume);
|
||||
return volume;
|
||||
}
|
||||
|
||||
int lovrAudioHas(Source* source) {
|
||||
int index;
|
||||
vec_find(&state.sources, source, index);
|
||||
return index >= 0;
|
||||
}
|
||||
|
||||
void lovrAudioPause() {
|
||||
int i; Source* source;
|
||||
vec_foreach(&state.sources, source, i) {
|
||||
lovrSourcePause(source);
|
||||
}
|
||||
}
|
||||
|
||||
void lovrAudioResume() {
|
||||
int i; Source* source;
|
||||
vec_foreach(&state.sources, source, i) {
|
||||
lovrSourceResume(source);
|
||||
}
|
||||
}
|
||||
|
||||
void lovrAudioRewind() {
|
||||
int i; Source* source;
|
||||
vec_foreach(&state.sources, source, i) {
|
||||
lovrSourceRewind(source);
|
||||
}
|
||||
}
|
||||
|
||||
// Help
|
||||
void lovrAudioSetOrientation(float angle, float ax, float ay, float az) {
|
||||
|
||||
// Quaternion
|
||||
float cos2 = cos(angle / 2.f);
|
||||
float sin2 = sin(angle / 2.f);
|
||||
float qx = sin2 * ax;
|
||||
float qy = sin2 * ay;
|
||||
float qz = sin2 * az;
|
||||
float s = cos2;
|
||||
|
||||
float vx, vy, vz, qdotv, qdotq, a, b, c, cx, cy, cz;
|
||||
|
||||
// Forward
|
||||
vx = 0;
|
||||
vy = 0;
|
||||
vz = -1;
|
||||
qdotv = qx * vx + qy * vy + qz * vz;
|
||||
qdotq = qx * qx + qy * qy + qz * qz;
|
||||
a = 2 * qdotv;
|
||||
b = s * s - qdotq;
|
||||
c = 2 * s;
|
||||
cross(qx, qy, qz, vx, vy, vz, &cx, &cy, &cz);
|
||||
float fx = a * qx + b * vx + c * cx;
|
||||
float fy = a * qy + b * vy + c * cy;
|
||||
float fz = a * qz + b * vz + c * cz;
|
||||
|
||||
// Up
|
||||
vx = 0;
|
||||
vy = 1;
|
||||
vz = 0;
|
||||
qdotv = qx * vx + qy * vy + qz * vz;
|
||||
qdotq = qx * qx + qy * qy + qz * qz;
|
||||
a = 2 * qdotv;
|
||||
b = s * s - qdotq;
|
||||
c = 2 * s;
|
||||
cross(qx, qy, qz, vx, vy, vz, &cx, &cy, &cz);
|
||||
float ux = a * qx + b * vx + c * cx;
|
||||
float uy = a * qy + b * vy + c * cy;
|
||||
float uz = a * qz + b * vz + c * cz;
|
||||
|
||||
ALfloat orientation[6] = { fx, fy, fz, ux, uy, uz };
|
||||
alListenerfv(AL_ORIENTATION, orientation);
|
||||
}
|
||||
|
||||
void lovrAudioSetPosition(float x, float y, float z) {
|
||||
alListener3f(AL_POSITION, x, y, z);
|
||||
}
|
||||
|
||||
void lovrAudioSetVolume(float volume) {
|
||||
alListenerf(AL_GAIN, volume);
|
||||
}
|
||||
|
||||
void lovrAudioStop() {
|
||||
int i; Source* source;
|
||||
vec_foreach(&state.sources, source, i) {
|
||||
lovrSourceStop(source);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#include "audio/source.h"
|
||||
#include "vendor/vec/vec.h"
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <AL/alext.h>
|
||||
|
||||
#ifndef LOVR_AUDIO_TYPES
|
||||
#define LOVR_AUDIO_TYPES
|
||||
|
||||
typedef struct {
|
||||
ALCdevice* device;
|
||||
ALCcontext* context;
|
||||
vec_void_t sources;
|
||||
} AudioState;
|
||||
|
||||
#endif
|
||||
|
||||
void lovrAudioInit();
|
||||
void lovrAudioDestroy();
|
||||
void lovrAudioUpdate();
|
||||
void lovrAudioAdd(Source* source);
|
||||
void lovrAudioGetOrientation(float* angle, float* ax, float* ay, float* az);
|
||||
void lovrAudioGetPosition(float* x, float* y, float* z);
|
||||
float lovrAudioGetVolume();
|
||||
int lovrAudioHas(Source* source);
|
||||
void lovrAudioPause();
|
||||
void lovrAudioResume();
|
||||
void lovrAudioRewind();
|
||||
void lovrAudioSetOrientation(float angle, float ax, float ay, float az);
|
||||
void lovrAudioSetPosition(float x, float y, float z);
|
||||
void lovrAudioSetVolume(float volume);
|
||||
void lovrAudioStop();
|
|
@ -0,0 +1,222 @@
|
|||
#include "audio/source.h"
|
||||
#include "loaders/source.h"
|
||||
|
||||
static ALenum lovrSourceGetState(Source* source) {
|
||||
ALenum state;
|
||||
alGetSourcei(source->id, AL_SOURCE_STATE, &state);
|
||||
return state;
|
||||
}
|
||||
|
||||
Source* lovrSourceCreate(SourceData* sourceData) {
|
||||
Source* source = lovrAlloc(sizeof(Source), lovrSourceDestroy);
|
||||
if (!source) return NULL;
|
||||
|
||||
source->sourceData = sourceData;
|
||||
source->isLooping = 0;
|
||||
alGenSources(1, &source->id);
|
||||
alGenBuffers(SOURCE_BUFFERS, source->buffers);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
void lovrSourceDestroy(const Ref* ref) {
|
||||
Source* source = containerof(ref, Source);
|
||||
alDeleteSources(1, &source->id);
|
||||
alDeleteBuffers(SOURCE_BUFFERS, source->buffers);
|
||||
lovrSourceDataDestroy(source->sourceData);
|
||||
free(source);
|
||||
}
|
||||
|
||||
int lovrSourceGetBitDepth(Source* source) {
|
||||
return source->sourceData->bitDepth;
|
||||
}
|
||||
|
||||
int lovrSourceGetChannels(Source* source) {
|
||||
return source->sourceData->channels;
|
||||
}
|
||||
|
||||
int lovrSourceGetDuration(Source* source) {
|
||||
return source->sourceData->samples;
|
||||
}
|
||||
|
||||
// Get the OpenAL sound format for the sound
|
||||
ALenum lovrSourceGetFormat(Source* source) {
|
||||
int channels = source->sourceData->channels;
|
||||
int bitDepth = source->sourceData->bitDepth;
|
||||
|
||||
if (bitDepth == 8 && channels == 1) {
|
||||
return AL_FORMAT_MONO8;
|
||||
} else if (bitDepth == 8 && channels == 2) {
|
||||
return AL_FORMAT_STEREO8;
|
||||
} else if (bitDepth == 16 && channels == 1) {
|
||||
return AL_FORMAT_MONO16;
|
||||
} else if (bitDepth == 16 && channels == 2) {
|
||||
return AL_FORMAT_STEREO16;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void lovrSourceGetOrientation(Source* source, float* x, float* y, float* z) {
|
||||
alGetSource3f(source->id, AL_DIRECTION, x, y, z);
|
||||
}
|
||||
|
||||
float lovrSourceGetPitch(Source* source) {
|
||||
float pitch;
|
||||
alGetSourcef(source->id, AL_PITCH, &pitch);
|
||||
return pitch;
|
||||
}
|
||||
|
||||
void lovrSourceGetPosition(Source* source, float* x, float* y, float* z) {
|
||||
alGetSource3f(source->id, AL_POSITION, x, y, z);
|
||||
}
|
||||
|
||||
int lovrSourceGetSampleRate(Source* source) {
|
||||
return source->sourceData->sampleRate;
|
||||
}
|
||||
|
||||
float lovrSourceGetVolume(Source* source) {
|
||||
float volume;
|
||||
alGetSourcef(source->id, AL_GAIN, &volume);
|
||||
return volume;
|
||||
}
|
||||
|
||||
int lovrSourceIsLooping(Source* source) {
|
||||
return source->isLooping;
|
||||
}
|
||||
|
||||
int lovrSourceIsPaused(Source* source) {
|
||||
return lovrSourceGetState(source) == AL_PAUSED;
|
||||
}
|
||||
|
||||
int lovrSourceIsPlaying(Source* source) {
|
||||
return lovrSourceGetState(source) == AL_PLAYING;
|
||||
}
|
||||
|
||||
int lovrSourceIsStopped(Source* source) {
|
||||
return lovrSourceGetState(source) == AL_STOPPED;
|
||||
}
|
||||
|
||||
void lovrSourcePause(Source* source) {
|
||||
alSourcePause(source->id);
|
||||
}
|
||||
|
||||
void lovrSourcePlay(Source* source) {
|
||||
if (lovrSourceIsPlaying(source)) {
|
||||
return;
|
||||
} else if (lovrSourceIsPaused(source)) {
|
||||
lovrSourceResume(source);
|
||||
return;
|
||||
}
|
||||
|
||||
lovrSourceStream(source, source->buffers, SOURCE_BUFFERS);
|
||||
alSourcePlay(source->id);
|
||||
}
|
||||
|
||||
void lovrSourceResume(Source* source) {
|
||||
if (!lovrSourceIsPaused(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
alSourcePlay(source->id);
|
||||
}
|
||||
|
||||
void lovrSourceRewind(Source* source) {
|
||||
if (lovrSourceIsStopped(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int wasPaused = lovrSourceIsPaused(source);
|
||||
alSourceRewind(source->id);
|
||||
lovrSourceStop(source);
|
||||
lovrSourcePlay(source);
|
||||
if (wasPaused) {
|
||||
lovrSourcePause(source);
|
||||
}
|
||||
}
|
||||
|
||||
void lovrSourceSeek(Source* source, int sample) {
|
||||
int wasPaused = lovrSourceIsPaused(source);
|
||||
lovrSourceStop(source);
|
||||
lovrSourceDataSeek(source->sourceData, sample);
|
||||
lovrSourcePlay(source);
|
||||
if (wasPaused) {
|
||||
lovrSourcePause(source);
|
||||
}
|
||||
}
|
||||
|
||||
void lovrSourceSetLooping(Source* source, int isLooping) {
|
||||
source->isLooping = isLooping;
|
||||
}
|
||||
|
||||
void lovrSourceSetOrientation(Source* source, float dx, float dy, float dz) {
|
||||
alSource3f(source->id, AL_DIRECTION, dx, dy, dz);
|
||||
}
|
||||
|
||||
void lovrSourceSetPitch(Source* source, float pitch) {
|
||||
alSourcef(source->id, AL_PITCH, pitch);
|
||||
}
|
||||
|
||||
void lovrSourceSetPosition(Source* source, float x, float y, float z) {
|
||||
alSource3f(source->id, AL_POSITION, x, y, z);
|
||||
}
|
||||
|
||||
void lovrSourceSetVolume(Source* source, float volume) {
|
||||
alSourcef(source->id, AL_GAIN, volume);
|
||||
}
|
||||
|
||||
void lovrSourceStop(Source* source) {
|
||||
if (lovrSourceIsStopped(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Empty the buffers
|
||||
int count = 0;
|
||||
alGetSourcei(source->id, AL_BUFFERS_QUEUED, &count);
|
||||
alSourceUnqueueBuffers(source->id, count, NULL);
|
||||
|
||||
// Stop the source
|
||||
alSourceStop(source->id);
|
||||
alSourcei(source->id, AL_BUFFER, AL_NONE);
|
||||
|
||||
// Rewind the decoder
|
||||
lovrSourceDataRewind(source->sourceData);
|
||||
}
|
||||
|
||||
// Fills buffers with data and queues them, called once initially and over time to stream more data
|
||||
void lovrSourceStream(Source* source, ALuint* buffers, int count) {
|
||||
SourceData* sourceData = source->sourceData;
|
||||
ALenum format = lovrSourceGetFormat(source);
|
||||
int frequency = sourceData->sampleRate;
|
||||
int samples = 0;
|
||||
int n = 0;
|
||||
|
||||
// Keep decoding until there is nothing left to decode or all the buffers are filled
|
||||
while (n < count && (samples = lovrSourceDataDecode(sourceData)) != 0) {
|
||||
alBufferData(buffers[n++], format, sourceData->buffer, samples * sizeof(ALshort), frequency);
|
||||
}
|
||||
|
||||
alSourceQueueBuffers(source->id, n, buffers);
|
||||
|
||||
if (samples == 0 && source->isLooping && n < count) {
|
||||
lovrSourceDataRewind(sourceData);
|
||||
lovrSourceStream(source, buffers + n, count - n);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int lovrSourceTell(Source* source) {
|
||||
int decoderOffset = lovrSourceDataTell(source->sourceData);
|
||||
int samplesPerBuffer = source->sourceData->bufferSize / source->sourceData->channels / sizeof(ALshort);
|
||||
int queuedBuffers, sampleOffset;
|
||||
alGetSourcei(source->id, AL_BUFFERS_QUEUED, &queuedBuffers);
|
||||
alGetSourcei(source->id, AL_SAMPLE_OFFSET, &sampleOffset);
|
||||
|
||||
int offset = decoderOffset - queuedBuffers * samplesPerBuffer + sampleOffset;
|
||||
|
||||
if (offset < 0) {
|
||||
return offset + source->sourceData->samples;
|
||||
} else {
|
||||
return offset;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
#include "util.h"
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
|
||||
#ifndef LOVR_SOURCE_TYPES
|
||||
#define LOVR_SOURCE_TYPES
|
||||
|
||||
#define SOURCE_BUFFERS 4
|
||||
|
||||
typedef enum {
|
||||
UNIT_SECONDS,
|
||||
UNIT_SAMPLES
|
||||
} TimeUnit;
|
||||
|
||||
typedef struct {
|
||||
int bitDepth;
|
||||
int channels;
|
||||
int sampleRate;
|
||||
int samples;
|
||||
int bufferSize;
|
||||
void* buffer;
|
||||
void* decoder;
|
||||
void* data;
|
||||
} SourceData;
|
||||
|
||||
typedef struct {
|
||||
Ref ref;
|
||||
SourceData* sourceData;
|
||||
ALuint id;
|
||||
ALuint buffers[SOURCE_BUFFERS];
|
||||
int isLooping;
|
||||
} Source;
|
||||
|
||||
#endif
|
||||
|
||||
Source* lovrSourceCreate(SourceData* sourceData);
|
||||
void lovrSourceDestroy(const Ref* ref);
|
||||
int lovrSourceGetBitDepth(Source* source);
|
||||
int lovrSourceGetChannels(Source* source);
|
||||
int lovrSourceGetDuration(Source* source);
|
||||
ALenum lovrSourceGetFormat(Source* source);
|
||||
void lovrSourceGetOrientation(Source* source, float* x, float* y, float* z);
|
||||
float lovrSourceGetPitch(Source* source);
|
||||
void lovrSourceGetPosition(Source* source, float* x, float* y, float* z);
|
||||
int lovrSourceGetSampleRate(Source* source);
|
||||
float lovrSourceGetVolume(Source* source);
|
||||
int lovrSourceIsLooping(Source* source);
|
||||
int lovrSourceIsPaused(Source* source);
|
||||
int lovrSourceIsPlaying(Source* source);
|
||||
int lovrSourceIsStopped(Source* source);
|
||||
void lovrSourcePause(Source* source);
|
||||
void lovrSourcePlay(Source* source);
|
||||
void lovrSourceResume(Source* source);
|
||||
void lovrSourceRewind(Source* source);
|
||||
void lovrSourceSeek(Source* source, int sample);
|
||||
void lovrSourceSetLooping(Source* source, int isLooping);
|
||||
void lovrSourceSetPitch(Source* source, float pitch);
|
||||
void lovrSourceSetOrientation(Source* source, float dx, float dy, float dz);
|
||||
void lovrSourceSetPosition(Source* source, float x, float y, float z);
|
||||
void lovrSourceSetVolume(Source* source, float volume);
|
||||
void lovrSourceStop(Source* source);
|
||||
void lovrSourceStream(Source* source, ALuint* buffers, int count);
|
||||
int lovrSourceTell(Source* source);
|
|
@ -0,0 +1,66 @@
|
|||
#include "loaders/source.h"
|
||||
#include "vendor/stb/stb_vorbis.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
SourceData* lovrSourceDataFromFile(void* data, int size) {
|
||||
SourceData* sourceData = malloc(sizeof(SourceData));
|
||||
if (!sourceData) return NULL;
|
||||
|
||||
stb_vorbis* decoder = stb_vorbis_open_memory(data, size, NULL, NULL);
|
||||
|
||||
if (!decoder) {
|
||||
free(sourceData);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stb_vorbis_info info = stb_vorbis_get_info(decoder);
|
||||
|
||||
sourceData->bitDepth = 16;
|
||||
sourceData->channels = info.channels;
|
||||
sourceData->sampleRate = info.sample_rate;
|
||||
sourceData->samples = stb_vorbis_stream_length_in_samples(decoder);
|
||||
sourceData->decoder = decoder;
|
||||
sourceData->bufferSize = sourceData->channels * 4096 * sizeof(short);
|
||||
sourceData->buffer = malloc(sourceData->bufferSize);
|
||||
sourceData->data = data;
|
||||
|
||||
return sourceData;
|
||||
}
|
||||
|
||||
void lovrSourceDataDestroy(SourceData* sourceData) {
|
||||
stb_vorbis_close(sourceData->decoder);
|
||||
free(sourceData->data);
|
||||
free(sourceData->buffer);
|
||||
free(sourceData);
|
||||
}
|
||||
|
||||
int lovrSourceDataDecode(SourceData* sourceData) {
|
||||
stb_vorbis* decoder = (stb_vorbis*) sourceData->decoder;
|
||||
short* buffer = (short*) sourceData->buffer;
|
||||
int channels = sourceData->channels;
|
||||
int capacity = sourceData->bufferSize / sizeof(short);
|
||||
int samples = 0;
|
||||
|
||||
while (samples < capacity) {
|
||||
int count = stb_vorbis_get_samples_short_interleaved(decoder, channels, buffer + samples, capacity - samples);
|
||||
if (count == 0) break;
|
||||
samples += count * channels;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
void lovrSourceDataRewind(SourceData* sourceData) {
|
||||
stb_vorbis* decoder = (stb_vorbis*) sourceData->decoder;
|
||||
stb_vorbis_seek_start(decoder);
|
||||
}
|
||||
|
||||
void lovrSourceDataSeek(SourceData* sourceData, int sample) {
|
||||
stb_vorbis* decoder = (stb_vorbis*) sourceData->decoder;
|
||||
stb_vorbis_seek(decoder, sample);
|
||||
}
|
||||
|
||||
int lovrSourceDataTell(SourceData* sourceData) {
|
||||
stb_vorbis* decoder = (stb_vorbis*) sourceData->decoder;
|
||||
return stb_vorbis_get_sample_offset(decoder);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#include "audio/source.h"
|
||||
|
||||
SourceData* lovrSourceDataFromFile(void* data, int size);
|
||||
void lovrSourceDataDestroy(SourceData* sourceData);
|
||||
int lovrSourceDataDecode(SourceData* sourceData);
|
||||
void lovrSourceDataRewind(SourceData* sourceData);
|
||||
void lovrSourceDataSeek(SourceData* sourceData, int sample);
|
||||
int lovrSourceDataTell(SourceData* sourceData);
|
14
src/lovr.c
14
src/lovr.c
|
@ -1,4 +1,5 @@
|
|||
#include "lovr.h"
|
||||
#include "lovr/audio.h"
|
||||
#include "lovr/event.h"
|
||||
#include "lovr/filesystem.h"
|
||||
#include "lovr/graphics.h"
|
||||
|
@ -56,6 +57,7 @@ void lovrInit(lua_State* L, int argc, char** argv) {
|
|||
lua_setglobal(L, "lovr");
|
||||
|
||||
// Preload modules
|
||||
luax_preloadmodule(L, "lovr.audio", l_lovrAudioInit);
|
||||
luax_preloadmodule(L, "lovr.event", l_lovrEventInit);
|
||||
luax_preloadmodule(L, "lovr.filesystem", l_lovrFilesystemInit);
|
||||
luax_preloadmodule(L, "lovr.graphics", l_lovrGraphicsInit);
|
||||
|
@ -63,10 +65,11 @@ void lovrInit(lua_State* L, int argc, char** argv) {
|
|||
luax_preloadmodule(L, "lovr.timer", l_lovrTimerInit);
|
||||
|
||||
// Bootstrap
|
||||
char buffer[2048];
|
||||
char buffer[4096];
|
||||
snprintf(buffer, sizeof(buffer), "%s",
|
||||
"local conf = { "
|
||||
" modules = { "
|
||||
" audio = true, "
|
||||
" event = true, "
|
||||
" graphics = true, "
|
||||
" headset = true, "
|
||||
|
@ -85,7 +88,7 @@ void lovrInit(lua_State* L, int argc, char** argv) {
|
|||
" success, err = pcall(lovr.conf, conf) "
|
||||
"end "
|
||||
|
||||
"local modules = { 'event', 'graphics', 'headset', 'timer' } "
|
||||
"local modules = { 'audio', 'event', 'graphics', 'headset', 'timer' } "
|
||||
"for _, module in ipairs(modules) do "
|
||||
" if conf.modules[module] then "
|
||||
" lovr[module] = require('lovr.' .. module) "
|
||||
|
@ -119,6 +122,13 @@ void lovrInit(lua_State* L, int argc, char** argv) {
|
|||
" lovr.handlers[name](a, b, c, d) "
|
||||
" end "
|
||||
" local dt = lovr.timer.step() "
|
||||
" if lovr.audio then "
|
||||
" lovr.audio.update() "
|
||||
" if lovr.headset and lovr.headset.isPresent() then "
|
||||
" lovr.audio.setPosition(lovr.headset.getPosition()) "
|
||||
" lovr.audio.setOrientation(lovr.headset.getOrientation()) "
|
||||
" end "
|
||||
" end "
|
||||
" if lovr.update then lovr.update(dt) end "
|
||||
" lovr.graphics.clear() "
|
||||
" lovr.graphics.origin() "
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
#include "lovr/audio.h"
|
||||
#include "lovr/types/source.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/source.h"
|
||||
#include "loaders/source.h"
|
||||
#include "filesystem/filesystem.h"
|
||||
|
||||
const luaL_Reg lovrAudio[] = {
|
||||
{ "update", l_lovrAudioUpdate },
|
||||
{ "getOrientation", l_lovrAudioGetOrientation },
|
||||
{ "getPosition", l_lovrAudioGetPosition },
|
||||
{ "getVolume", l_lovrAudioGetVolume },
|
||||
{ "newSource", l_lovrAudioNewSource },
|
||||
{ "pause", l_lovrAudioPause },
|
||||
{ "resume", l_lovrAudioResume },
|
||||
{ "rewind", l_lovrAudioRewind },
|
||||
{ "setOrientation", l_lovrAudioSetOrientation },
|
||||
{ "setPosition", l_lovrAudioSetPosition },
|
||||
{ "setVolume", l_lovrAudioSetVolume },
|
||||
{ "stop", l_lovrAudioStop },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int l_lovrAudioInit(lua_State* L) {
|
||||
lua_newtable(L);
|
||||
luaL_register(L, NULL, lovrAudio);
|
||||
luax_registertype(L, "Source", lovrSource);
|
||||
|
||||
map_init(&TimeUnits);
|
||||
map_set(&TimeUnits, "seconds", UNIT_SECONDS);
|
||||
map_set(&TimeUnits, "samples", UNIT_SAMPLES);
|
||||
|
||||
lovrAudioInit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrAudioUpdate(lua_State* L) {
|
||||
lovrAudioUpdate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrAudioGetOrientation(lua_State* L) {
|
||||
float angle, ax, ay, az;
|
||||
lovrAudioGetOrientation(&angle, &ax, &ay, &az);
|
||||
lua_pushnumber(L, angle);
|
||||
lua_pushnumber(L, ax);
|
||||
lua_pushnumber(L, ay);
|
||||
lua_pushnumber(L, az);
|
||||
return 4;
|
||||
}
|
||||
|
||||
int l_lovrAudioGetPosition(lua_State* L) {
|
||||
float x, y, z;
|
||||
lovrAudioGetPosition(&x, &y, &z);
|
||||
lua_pushnumber(L, x);
|
||||
lua_pushnumber(L, y);
|
||||
lua_pushnumber(L, z);
|
||||
return 3;
|
||||
}
|
||||
|
||||
int l_lovrAudioGetVolume(lua_State* L) {
|
||||
lua_pushnumber(L, lovrAudioGetVolume());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrAudioNewSource(lua_State* L) {
|
||||
const char* filename = luaL_checkstring(L, 1);
|
||||
if (!strstr(filename, ".ogg")) {
|
||||
return luaL_error(L, "Only .ogg files are supported");
|
||||
}
|
||||
|
||||
int size;
|
||||
void* data = lovrFilesystemRead(filename, &size);
|
||||
if (!data) {
|
||||
return luaL_error(L, "Could not load source from file '%s'", filename);
|
||||
}
|
||||
|
||||
SourceData* sourceData = lovrSourceDataFromFile(data, size);
|
||||
luax_pushtype(L, Source, lovrSourceCreate(sourceData));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrAudioPause(lua_State* L) {
|
||||
lovrAudioPause();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrAudioResume(lua_State* L) {
|
||||
lovrAudioResume();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrAudioRewind(lua_State* L) {
|
||||
lovrAudioRewind();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrAudioSetOrientation(lua_State* L) {
|
||||
float angle = luaL_checknumber(L, 1);
|
||||
float ax = luaL_checknumber(L, 2);
|
||||
float ay = luaL_checknumber(L, 3);
|
||||
float az = luaL_checknumber(L, 4);
|
||||
lovrAudioSetOrientation(angle, ax, ay, az);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrAudioSetPosition(lua_State* L) {
|
||||
float x = luaL_checknumber(L, 1);
|
||||
float y = luaL_checknumber(L, 2);
|
||||
float z = luaL_checknumber(L, 3);
|
||||
lovrAudioSetPosition(x, y, z);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrAudioSetVolume(lua_State* L) {
|
||||
float volume = luaL_checknumber(L, 1);
|
||||
lovrAudioSetVolume(volume);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrAudioStop(lua_State* L) {
|
||||
lovrAudioStop();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#include "vendor/map/map.h"
|
||||
|
||||
map_int_t TimeUnits;
|
||||
|
||||
extern const luaL_Reg lovrAudio[];
|
||||
int l_lovrAudioInit(lua_State* L);
|
||||
int l_lovrAudioUpdate(lua_State* L);
|
||||
int l_lovrAudioGetOrientation(lua_State* L);
|
||||
int l_lovrAudioGetPosition(lua_State* L);
|
||||
int l_lovrAudioGetVolume(lua_State* L);
|
||||
int l_lovrAudioNewSource(lua_State* L);
|
||||
int l_lovrAudioPause(lua_State* L);
|
||||
int l_lovrAudioResume(lua_State* L);
|
||||
int l_lovrAudioRewind(lua_State* L);
|
||||
int l_lovrAudioSetOrientation(lua_State* L);
|
||||
int l_lovrAudioSetPosition(lua_State* L);
|
||||
int l_lovrAudioSetVolume(lua_State* L);
|
||||
int l_lovrAudioStop(lua_State* L);
|
|
@ -0,0 +1,202 @@
|
|||
#include "lovr/types/source.h"
|
||||
#include "lovr/audio.h"
|
||||
#include "audio/audio.h"
|
||||
|
||||
const luaL_Reg lovrSource[] = {
|
||||
{ "getBitDepth", l_lovrSourceGetBitDepth },
|
||||
{ "getChannels", l_lovrSourceGetChannels },
|
||||
{ "getDuration", l_lovrSourceGetDuration },
|
||||
{ "getOrientation", l_lovrSourceGetOrientation },
|
||||
{ "getPitch", l_lovrSourceGetPitch },
|
||||
{ "getPosition", l_lovrSourceGetPosition },
|
||||
{ "getSampleRate", l_lovrSourceGetSampleRate },
|
||||
{ "getVolume", l_lovrSourceGetVolume },
|
||||
{ "isLooping", l_lovrSourceIsLooping },
|
||||
{ "isPaused", l_lovrSourceIsPaused },
|
||||
{ "isPlaying", l_lovrSourceIsPlaying },
|
||||
{ "isStopped", l_lovrSourceIsStopped },
|
||||
{ "pause", l_lovrSourcePause },
|
||||
{ "play", l_lovrSourcePlay },
|
||||
{ "resume", l_lovrSourceResume },
|
||||
{ "rewind", l_lovrSourceRewind },
|
||||
{ "seek", l_lovrSourceSeek },
|
||||
{ "setLooping", l_lovrSourceSetLooping },
|
||||
{ "setOrientation", l_lovrSourceSetOrientation },
|
||||
{ "setPitch", l_lovrSourceSetPitch },
|
||||
{ "setPosition", l_lovrSourceSetPosition },
|
||||
{ "setVolume", l_lovrSourceSetVolume },
|
||||
{ "stop", l_lovrSourceStop },
|
||||
{ "tell", l_lovrSourceTell },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int l_lovrSourceGetBitDepth(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
lua_pushinteger(L, lovrSourceGetBitDepth(source));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceGetChannels(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
lua_pushinteger(L, lovrSourceGetChannels(source));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceGetDuration(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
TimeUnit* unit = luax_optenum(L, 2, "seconds", &TimeUnits, "unit");
|
||||
int duration = lovrSourceGetDuration(source);
|
||||
|
||||
if (*unit == UNIT_SECONDS) {
|
||||
lua_pushnumber(L, (float) duration / lovrSourceGetSampleRate(source));
|
||||
} else {
|
||||
lua_pushinteger(L, duration);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceGetOrientation(lua_State* L) {
|
||||
float x, y, z;
|
||||
lovrSourceGetOrientation(luax_checktype(L, 1, Source), &x, &y, &z);
|
||||
lua_pushnumber(L, x);
|
||||
lua_pushnumber(L, y);
|
||||
lua_pushnumber(L, z);
|
||||
return 3;
|
||||
}
|
||||
|
||||
int l_lovrSourceGetPitch(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
lua_pushnumber(L, lovrSourceGetPitch(source));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceGetPosition(lua_State* L) {
|
||||
float x, y, z;
|
||||
lovrSourceGetPosition(luax_checktype(L, 1, Source), &x, &y, &z);
|
||||
lua_pushnumber(L, x);
|
||||
lua_pushnumber(L, y);
|
||||
lua_pushnumber(L, z);
|
||||
return 3;
|
||||
}
|
||||
|
||||
int l_lovrSourceGetSampleRate(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
lua_pushinteger(L, lovrSourceGetSampleRate(source));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceGetVolume(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
lua_pushnumber(L, lovrSourceGetVolume(source));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceIsLooping(lua_State* L) {
|
||||
lua_pushboolean(L, lovrSourceIsLooping(luax_checktype(L, 1, Source)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceIsPaused(lua_State* L) {
|
||||
lua_pushnumber(L, lovrSourceIsPaused(luax_checktype(L, 1, Source)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceIsPlaying(lua_State* L) {
|
||||
lua_pushnumber(L, lovrSourceIsPlaying(luax_checktype(L, 1, Source)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourceIsStopped(lua_State* L) {
|
||||
lua_pushnumber(L, lovrSourceIsStopped(luax_checktype(L, 1, Source)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int l_lovrSourcePause(lua_State* L) {
|
||||
lovrSourcePause(luax_checktype(L, 1, Source));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourcePlay(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
lovrSourcePlay(source);
|
||||
lovrAudioAdd(source);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceResume(lua_State* L) {
|
||||
lovrSourceResume(luax_checktype(L, 1, Source));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceRewind(lua_State* L) {
|
||||
lovrSourceRewind(luax_checktype(L, 1, Source));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceSeek(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
TimeUnit* unit = luax_optenum(L, 3, "seconds", &TimeUnits, "unit");
|
||||
|
||||
if (*unit == UNIT_SECONDS) {
|
||||
float seconds = luaL_checknumber(L, 2);
|
||||
int sampleRate = lovrSourceGetSampleRate(source);
|
||||
lovrSourceSeek(source, (int) (seconds * sampleRate + .5f));
|
||||
} else {
|
||||
lovrSourceSeek(source, luaL_checkinteger(L, 2));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceSetLooping(lua_State* L) {
|
||||
lovrSourceSetLooping(luax_checktype(L, 1, Source), lua_toboolean(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceSetOrientation(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
float x = luaL_checknumber(L, 2);
|
||||
float y = luaL_checknumber(L, 3);
|
||||
float z = luaL_checknumber(L, 4);
|
||||
lovrSourceSetOrientation(source, x, y, z);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceSetPitch(lua_State* L) {
|
||||
lovrSourceSetPitch(luax_checktype(L, 1, Source), luaL_checknumber(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceSetPosition(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
float x = luaL_checknumber(L, 2);
|
||||
float y = luaL_checknumber(L, 3);
|
||||
float z = luaL_checknumber(L, 4);
|
||||
lovrSourceSetPosition(source, x, y, z);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceSetVolume(lua_State* L) {
|
||||
lovrSourceSetVolume(luax_checktype(L, 1, Source), luaL_checknumber(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceStop(lua_State* L) {
|
||||
lovrSourceStop(luax_checktype(L, 1, Source));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l_lovrSourceTell(lua_State* L) {
|
||||
Source* source = luax_checktype(L, 1, Source);
|
||||
TimeUnit* unit = luax_optenum(L, 2, "seconds", &TimeUnits, "unit");
|
||||
int offset = lovrSourceTell(source);
|
||||
|
||||
if (*unit == UNIT_SECONDS) {
|
||||
lua_pushnumber(L, (float) offset / lovrSourceGetSampleRate(source));
|
||||
} else {
|
||||
lua_pushinteger(L, offset);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#include "audio/source.h"
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
|
||||
extern const luaL_Reg lovrSource[];
|
||||
int l_lovrSourceGetBitDepth(lua_State* L);
|
||||
int l_lovrSourceGetChannels(lua_State* L);
|
||||
int l_lovrSourceGetDuration(lua_State* L);
|
||||
int l_lovrSourceGetOrientation(lua_State* L);
|
||||
int l_lovrSourceGetPitch(lua_State* L);
|
||||
int l_lovrSourceGetPosition(lua_State* L);
|
||||
int l_lovrSourceGetSampleRate(lua_State* L);
|
||||
int l_lovrSourceGetVolume(lua_State* L);
|
||||
int l_lovrSourceIsLooping(lua_State* L);
|
||||
int l_lovrSourceIsPaused(lua_State* L);
|
||||
int l_lovrSourceIsPlaying(lua_State* L);
|
||||
int l_lovrSourceIsStopped(lua_State* L);
|
||||
int l_lovrSourcePause(lua_State* L);
|
||||
int l_lovrSourcePlay(lua_State* L);
|
||||
int l_lovrSourceResume(lua_State* L);
|
||||
int l_lovrSourceRewind(lua_State* L);
|
||||
int l_lovrSourceSeek(lua_State* L);
|
||||
int l_lovrSourceSetLooping(lua_State* L);
|
||||
int l_lovrSourceSetOrientation(lua_State* L);
|
||||
int l_lovrSourceSetPitch(lua_State* L);
|
||||
int l_lovrSourceSetPosition(lua_State* L);
|
||||
int l_lovrSourceSetVolume(lua_State* L);
|
||||
int l_lovrSourceStop(lua_State* L);
|
||||
int l_lovrSourceTell(lua_State* L);
|
|
@ -141,9 +141,9 @@ void mat4_getRotation(mat4 matrix, float* w, float* x, float* y, float* z) {
|
|||
float qx = sqrt(MAX(0, 1 + matrix[0] - matrix[5] - matrix[10])) / 2;
|
||||
float qy = sqrt(MAX(0, 1 - matrix[0] + matrix[5] - matrix[10])) / 2;
|
||||
float qz = sqrt(MAX(0, 1 - matrix[0] - matrix[5] + matrix[10])) / 2;
|
||||
qx = (matrix[9] - matrix[6]) < 0 ? -qx : qx;
|
||||
qy = (matrix[2] - matrix[8]) < 0 ? -qy : qy;
|
||||
qz = (matrix[4] - matrix[1]) < 0 ? -qz : qz;
|
||||
qx = (matrix[9] - matrix[6]) > 0 ? -qx : qx;
|
||||
qy = (matrix[2] - matrix[8]) > 0 ? -qy : qy;
|
||||
qz = (matrix[4] - matrix[1]) > 0 ? -qz : qz;
|
||||
|
||||
float s = sqrt(1 - qw * qw);
|
||||
s = s < .001 ? 1 : s;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "util.h"
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "vendor/stb_image.h"
|
||||
#include "vendor/stb/stb_image.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#define LOVR_COLOR_B(c) (c >> 8 & 0xff)
|
||||
#define LOVR_COLOR_A(c) (c >> 0 & 0xff)
|
||||
|
||||
#define luax_checktype(L, i, T) *(T**) luaL_checkudata(L, i, #T);
|
||||
#define luax_checktype(L, i, T) *(T**) luaL_checkudata(L, i, #T)
|
||||
|
||||
#define luax_pushtype(L, T, x) \
|
||||
T** u = (T**) lua_newuserdata(L, sizeof(T**)); \
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,332 @@
|
|||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// HEADER BEGINS HERE
|
||||
//
|
||||
|
||||
#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H
|
||||
#define STB_VORBIS_INCLUDE_STB_VORBIS_H
|
||||
|
||||
#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO)
|
||||
#define STB_VORBIS_NO_STDIO 1
|
||||
#endif
|
||||
|
||||
#ifndef STB_VORBIS_NO_STDIO
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/////////// THREAD SAFETY
|
||||
|
||||
// Individual stb_vorbis* handles are not thread-safe; you cannot decode from
|
||||
// them from multiple threads at the same time. However, you can have multiple
|
||||
// stb_vorbis* handles and decode from them independently in multiple thrads.
|
||||
|
||||
|
||||
/////////// MEMORY ALLOCATION
|
||||
|
||||
// normally stb_vorbis uses malloc() to allocate memory at startup,
|
||||
// and alloca() to allocate temporary memory during a frame on the
|
||||
// stack. (Memory consumption will depend on the amount of setup
|
||||
// data in the file and how you set the compile flags for speed
|
||||
// vs. size. In my test files the maximal-size usage is ~150KB.)
|
||||
//
|
||||
// You can modify the wrapper functions in the source (setup_malloc,
|
||||
// setup_temp_malloc, temp_malloc) to change this behavior, or you
|
||||
// can use a simpler allocation model: you pass in a buffer from
|
||||
// which stb_vorbis will allocate _all_ its memory (including the
|
||||
// temp memory). "open" may fail with a VORBIS_outofmem if you
|
||||
// do not pass in enough data; there is no way to determine how
|
||||
// much you do need except to succeed (at which point you can
|
||||
// query get_info to find the exact amount required. yes I know
|
||||
// this is lame).
|
||||
//
|
||||
// If you pass in a non-NULL buffer of the type below, allocation
|
||||
// will occur from it as described above. Otherwise just pass NULL
|
||||
// to use malloc()/alloca()
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *alloc_buffer;
|
||||
int alloc_buffer_length_in_bytes;
|
||||
} stb_vorbis_alloc;
|
||||
|
||||
|
||||
/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES
|
||||
|
||||
typedef struct stb_vorbis stb_vorbis;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int sample_rate;
|
||||
int channels;
|
||||
|
||||
unsigned int setup_memory_required;
|
||||
unsigned int setup_temp_memory_required;
|
||||
unsigned int temp_memory_required;
|
||||
|
||||
int max_frame_size;
|
||||
} stb_vorbis_info;
|
||||
|
||||
// get general information about the file
|
||||
extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f);
|
||||
|
||||
// get the last error detected (clears it, too)
|
||||
extern int stb_vorbis_get_error(stb_vorbis *f);
|
||||
|
||||
// close an ogg vorbis file and free all memory in use
|
||||
extern void stb_vorbis_close(stb_vorbis *f);
|
||||
|
||||
// this function returns the offset (in samples) from the beginning of the
|
||||
// file that will be returned by the next decode, if it is known, or -1
|
||||
// otherwise. after a flush_pushdata() call, this may take a while before
|
||||
// it becomes valid again.
|
||||
// NOT WORKING YET after a seek with PULLDATA API
|
||||
extern int stb_vorbis_get_sample_offset(stb_vorbis *f);
|
||||
|
||||
// returns the current seek point within the file, or offset from the beginning
|
||||
// of the memory buffer. In pushdata mode it returns 0.
|
||||
extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f);
|
||||
|
||||
/////////// PUSHDATA API
|
||||
|
||||
#ifndef STB_VORBIS_NO_PUSHDATA_API
|
||||
|
||||
// this API allows you to get blocks of data from any source and hand
|
||||
// them to stb_vorbis. you have to buffer them; stb_vorbis will tell
|
||||
// you how much it used, and you have to give it the rest next time;
|
||||
// and stb_vorbis may not have enough data to work with and you will
|
||||
// need to give it the same data again PLUS more. Note that the Vorbis
|
||||
// specification does not bound the size of an individual frame.
|
||||
|
||||
extern stb_vorbis *stb_vorbis_open_pushdata(
|
||||
const unsigned char * datablock, int datablock_length_in_bytes,
|
||||
int *datablock_memory_consumed_in_bytes,
|
||||
int *error,
|
||||
const stb_vorbis_alloc *alloc_buffer);
|
||||
// create a vorbis decoder by passing in the initial data block containing
|
||||
// the ogg&vorbis headers (you don't need to do parse them, just provide
|
||||
// the first N bytes of the file--you're told if it's not enough, see below)
|
||||
// on success, returns an stb_vorbis *, does not set error, returns the amount of
|
||||
// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes;
|
||||
// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed
|
||||
// if returns NULL and *error is VORBIS_need_more_data, then the input block was
|
||||
// incomplete and you need to pass in a larger block from the start of the file
|
||||
|
||||
extern int stb_vorbis_decode_frame_pushdata(
|
||||
stb_vorbis *f,
|
||||
const unsigned char *datablock, int datablock_length_in_bytes,
|
||||
int *channels, // place to write number of float * buffers
|
||||
float ***output, // place to write float ** array of float * buffers
|
||||
int *samples // place to write number of output samples
|
||||
);
|
||||
// decode a frame of audio sample data if possible from the passed-in data block
|
||||
//
|
||||
// return value: number of bytes we used from datablock
|
||||
//
|
||||
// possible cases:
|
||||
// 0 bytes used, 0 samples output (need more data)
|
||||
// N bytes used, 0 samples output (resynching the stream, keep going)
|
||||
// N bytes used, M samples output (one frame of data)
|
||||
// note that after opening a file, you will ALWAYS get one N-bytes,0-sample
|
||||
// frame, because Vorbis always "discards" the first frame.
|
||||
//
|
||||
// Note that on resynch, stb_vorbis will rarely consume all of the buffer,
|
||||
// instead only datablock_length_in_bytes-3 or less. This is because it wants
|
||||
// to avoid missing parts of a page header if they cross a datablock boundary,
|
||||
// without writing state-machiney code to record a partial detection.
|
||||
//
|
||||
// The number of channels returned are stored in *channels (which can be
|
||||
// NULL--it is always the same as the number of channels reported by
|
||||
// get_info). *output will contain an array of float* buffers, one per
|
||||
// channel. In other words, (*output)[0][0] contains the first sample from
|
||||
// the first channel, and (*output)[1][0] contains the first sample from
|
||||
// the second channel.
|
||||
|
||||
extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
|
||||
// inform stb_vorbis that your next datablock will not be contiguous with
|
||||
// previous ones (e.g. you've seeked in the data); future attempts to decode
|
||||
// frames will cause stb_vorbis to resynchronize (as noted above), and
|
||||
// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it
|
||||
// will begin decoding the _next_ frame.
|
||||
//
|
||||
// if you want to seek using pushdata, you need to seek in your file, then
|
||||
// call stb_vorbis_flush_pushdata(), then start calling decoding, then once
|
||||
// decoding is returning you data, call stb_vorbis_get_sample_offset, and
|
||||
// if you don't like the result, seek your file again and repeat.
|
||||
#endif
|
||||
|
||||
|
||||
////////// PULLING INPUT API
|
||||
|
||||
#ifndef STB_VORBIS_NO_PULLDATA_API
|
||||
// This API assumes stb_vorbis is allowed to pull data from a source--
|
||||
// either a block of memory containing the _entire_ vorbis stream, or a
|
||||
// FILE * that you or it create, or possibly some other reading mechanism
|
||||
// if you go modify the source to replace the FILE * case with some kind
|
||||
// of callback to your code. (But if you don't support seeking, you may
|
||||
// just want to go ahead and use pushdata.)
|
||||
|
||||
#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION)
|
||||
extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output);
|
||||
#endif
|
||||
#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION)
|
||||
extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output);
|
||||
#endif
|
||||
// decode an entire file and output the data interleaved into a malloc()ed
|
||||
// buffer stored in *output. The return value is the number of samples
|
||||
// decoded, or -1 if the file could not be opened or was not an ogg vorbis file.
|
||||
// When you're done with it, just free() the pointer returned in *output.
|
||||
|
||||
extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len,
|
||||
int *error, const stb_vorbis_alloc *alloc_buffer);
|
||||
// create an ogg vorbis decoder from an ogg vorbis stream in memory (note
|
||||
// this must be the entire stream!). on failure, returns NULL and sets *error
|
||||
|
||||
#ifndef STB_VORBIS_NO_STDIO
|
||||
extern stb_vorbis * stb_vorbis_open_filename(const char *filename,
|
||||
int *error, const stb_vorbis_alloc *alloc_buffer);
|
||||
// create an ogg vorbis decoder from a filename via fopen(). on failure,
|
||||
// returns NULL and sets *error (possibly to VORBIS_file_open_failure).
|
||||
|
||||
extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close,
|
||||
int *error, const stb_vorbis_alloc *alloc_buffer);
|
||||
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
|
||||
// the _current_ seek point (ftell). on failure, returns NULL and sets *error.
|
||||
// note that stb_vorbis must "own" this stream; if you seek it in between
|
||||
// calls to stb_vorbis, it will become confused. Morever, if you attempt to
|
||||
// perform stb_vorbis_seek_*() operations on this file, it will assume it
|
||||
// owns the _entire_ rest of the file after the start point. Use the next
|
||||
// function, stb_vorbis_open_file_section(), to limit it.
|
||||
|
||||
extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close,
|
||||
int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len);
|
||||
// create an ogg vorbis decoder from an open FILE *, looking for a stream at
|
||||
// the _current_ seek point (ftell); the stream will be of length 'len' bytes.
|
||||
// on failure, returns NULL and sets *error. note that stb_vorbis must "own"
|
||||
// this stream; if you seek it in between calls to stb_vorbis, it will become
|
||||
// confused.
|
||||
#endif
|
||||
|
||||
extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number);
|
||||
extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number);
|
||||
// these functions seek in the Vorbis file to (approximately) 'sample_number'.
|
||||
// after calling seek_frame(), the next call to get_frame_*() will include
|
||||
// the specified sample. after calling stb_vorbis_seek(), the next call to
|
||||
// stb_vorbis_get_samples_* will start with the specified sample. If you
|
||||
// do not need to seek to EXACTLY the target sample when using get_samples_*,
|
||||
// you can also use seek_frame().
|
||||
|
||||
extern void stb_vorbis_seek_start(stb_vorbis *f);
|
||||
// this function is equivalent to stb_vorbis_seek(f,0)
|
||||
|
||||
extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f);
|
||||
extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f);
|
||||
// these functions return the total length of the vorbis stream
|
||||
|
||||
extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output);
|
||||
// decode the next frame and return the number of samples. the number of
|
||||
// channels returned are stored in *channels (which can be NULL--it is always
|
||||
// the same as the number of channels reported by get_info). *output will
|
||||
// contain an array of float* buffers, one per channel. These outputs will
|
||||
// be overwritten on the next call to stb_vorbis_get_frame_*.
|
||||
//
|
||||
// You generally should not intermix calls to stb_vorbis_get_frame_*()
|
||||
// and stb_vorbis_get_samples_*(), since the latter calls the former.
|
||||
|
||||
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
|
||||
extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts);
|
||||
extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples);
|
||||
#endif
|
||||
// decode the next frame and return the number of *samples* per channel.
|
||||
// Note that for interleaved data, you pass in the number of shorts (the
|
||||
// size of your array), but the return value is the number of samples per
|
||||
// channel, not the total number of samples.
|
||||
//
|
||||
// The data is coerced to the number of channels you request according to the
|
||||
// channel coercion rules (see below). You must pass in the size of your
|
||||
// buffer(s) so that stb_vorbis will not overwrite the end of the buffer.
|
||||
// The maximum buffer size needed can be gotten from get_info(); however,
|
||||
// the Vorbis I specification implies an absolute maximum of 4096 samples
|
||||
// per channel.
|
||||
|
||||
// Channel coercion rules:
|
||||
// Let M be the number of channels requested, and N the number of channels present,
|
||||
// and Cn be the nth channel; let stereo L be the sum of all L and center channels,
|
||||
// and stereo R be the sum of all R and center channels (channel assignment from the
|
||||
// vorbis spec).
|
||||
// M N output
|
||||
// 1 k sum(Ck) for all k
|
||||
// 2 * stereo L, stereo R
|
||||
// k l k > l, the first l channels, then 0s
|
||||
// k l k <= l, the first k channels
|
||||
// Note that this is not _good_ surround etc. mixing at all! It's just so
|
||||
// you get something useful.
|
||||
|
||||
extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats);
|
||||
extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples);
|
||||
// gets num_samples samples, not necessarily on a frame boundary--this requires
|
||||
// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES.
|
||||
// Returns the number of samples stored per channel; it may be less than requested
|
||||
// at the end of the file. If there are no more samples in the file, returns 0.
|
||||
|
||||
#ifndef STB_VORBIS_NO_INTEGER_CONVERSION
|
||||
extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts);
|
||||
extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples);
|
||||
#endif
|
||||
// gets num_samples samples, not necessarily on a frame boundary--this requires
|
||||
// buffering so you have to supply the buffers. Applies the coercion rules above
|
||||
// to produce 'channels' channels. Returns the number of samples stored per channel;
|
||||
// it may be less than requested at the end of the file. If there are no more
|
||||
// samples in the file, returns 0.
|
||||
|
||||
#endif
|
||||
|
||||
//////// ERROR CODES
|
||||
|
||||
enum STBVorbisError
|
||||
{
|
||||
VORBIS__no_error,
|
||||
|
||||
VORBIS_need_more_data=1, // not a real error
|
||||
|
||||
VORBIS_invalid_api_mixing, // can't mix API modes
|
||||
VORBIS_outofmem, // not enough memory
|
||||
VORBIS_feature_not_supported, // uses floor 0
|
||||
VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small
|
||||
VORBIS_file_open_failure, // fopen() failed
|
||||
VORBIS_seek_without_length, // can't seek in unknown-length file
|
||||
|
||||
VORBIS_unexpected_eof=10, // file is truncated?
|
||||
VORBIS_seek_invalid, // seek past EOF
|
||||
|
||||
// decoding errors (corrupt/invalid stream) -- you probably
|
||||
// don't care about the exact details of these
|
||||
|
||||
// vorbis errors:
|
||||
VORBIS_invalid_setup=20,
|
||||
VORBIS_invalid_stream,
|
||||
|
||||
// ogg errors:
|
||||
VORBIS_missing_capture_pattern=30,
|
||||
VORBIS_invalid_stream_structure_version,
|
||||
VORBIS_continued_packet_flag_invalid,
|
||||
VORBIS_incorrect_stream_serial_number,
|
||||
VORBIS_invalid_first_page,
|
||||
VORBIS_bad_packet_type,
|
||||
VORBIS_cant_find_last_page,
|
||||
VORBIS_seek_failed
|
||||
};
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H
|
||||
//
|
||||
// HEADER ENDS HERE
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
Loading…
Reference in New Issue