lovr/src/modules/audio/audio.c

581 lines
19 KiB
C
Raw Normal View History

2017-01-03 03:09:33 +00:00
#include "audio/audio.h"
2021-01-30 02:24:32 +00:00
#include "audio/spatializer.h"
2021-02-09 02:52:56 +00:00
#include "data/sound.h"
#include "core/maf.h"
2022-03-22 07:13:21 +00:00
#include "util.h"
2021-01-30 02:24:32 +00:00
#include "lib/miniaudio/miniaudio.h"
2020-05-10 08:48:01 +00:00
#include <string.h>
2017-01-03 03:09:33 +00:00
#include <stdlib.h>
#include <math.h>
#ifdef _MSC_VER
2021-02-24 20:54:02 +00:00
#include <intrin.h>
#define CTZL _tzcnt_u64
#else
#define CTZL __builtin_ctzl
#endif
2021-02-24 20:54:02 +00:00
#define FOREACH_SOURCE(s) for (uint64_t m = state.sourceMask; s = m ? state.sources[CTZL(m)] : NULL, m; m ^= (m & -m))
#define OUTPUT_FORMAT SAMPLE_F32
#define OUTPUT_CHANNELS 2
struct Source {
2021-02-11 23:37:55 +00:00
uint32_t ref;
uint32_t index;
2021-02-09 02:52:56 +00:00
Sound* sound;
ma_data_converter* converter;
2021-02-08 14:49:25 +00:00
intptr_t spatializerMemo;
2020-05-10 08:48:01 +00:00
uint32_t offset;
float volume;
2021-01-30 02:24:32 +00:00
float position[4];
float orientation[4];
float radius;
2021-02-16 09:54:24 +00:00
float dipoleWeight;
float dipolePower;
uint8_t effects;
2020-05-10 08:48:01 +00:00
bool playing;
bool looping;
bool spatial;
};
2019-05-13 10:53:17 +00:00
static struct {
bool initialized;
2021-03-04 03:01:56 +00:00
ma_mutex lock;
2020-05-10 08:48:01 +00:00
ma_context context;
2021-01-30 02:24:32 +00:00
ma_device devices[2];
2021-03-04 03:01:56 +00:00
Sound* sinks[2];
Source* sources[MAX_SOURCES];
uint64_t sourceMask;
2021-02-02 16:20:06 +00:00
float position[4];
float orientation[4];
Spatializer* spatializer;
float absorption[3];
2021-03-04 03:01:56 +00:00
ma_data_converter playbackConverter;
2021-07-20 16:48:58 +00:00
uint32_t sampleRate;
2019-05-13 10:53:17 +00:00
} state;
2017-01-03 03:09:33 +00:00
static const ma_format miniaudioFormats[] = {
[SAMPLE_I16] = ma_format_s16,
[SAMPLE_F32] = ma_format_f32
};
static float dbToLinear(float db) {
return powf(10.f, db / 20.f);
}
static float linearToDb(float linear) {
return 20.f * log10f(linear);
}
// Device callbacks
2021-02-04 18:25:06 +00:00
static void onPlayback(ma_device* device, void* out, const void* in, uint32_t count) {
lovrAssert(count == BUFFER_SIZE, "Unreachable");
2021-03-04 03:01:56 +00:00
float raw[BUFFER_SIZE * 2];
float aux[BUFFER_SIZE * 2];
float mix[BUFFER_SIZE * 2];
float* dst = out;
float* buf = NULL; // The "current" buffer (used for fast paths)
2020-05-19 18:08:24 +00:00
2021-02-04 18:25:06 +00:00
ma_mutex_lock(&state.lock);
Source* source;
FOREACH_SOURCE(source) {
if (!source->playing) {
state.sources[source->index] = NULL;
state.sourceMask &= ~(1ull << source->index);
state.spatializer->sourceDestroy(source);
source->index = ~0u;
lovrRelease(source, lovrSourceDestroy);
continue;
2021-02-04 18:25:06 +00:00
}
2020-05-24 19:26:46 +00:00
// Read and convert raw frames until there's BUFFER_SIZE converted frames
// - No converter: just read frames into raw (it has enough space for BUFFER_SIZE frames).
// - Converter: keep reading as many frames as possible/needed into raw and convert into aux.
// - If EOF is reached, rewind and continue for looping sources, otherwise pad end with zero.
buf = source->converter ? aux : raw;
float* cursor = buf; // Edge of processed frames
uint32_t channelsOut = source->spatial ? 1 : 2; // If spatializer isn't converting to stereo, converter must do it
uint32_t framesRemaining = BUFFER_SIZE;
while (framesRemaining > 0) {
uint32_t framesRead;
if (source->converter) {
uint32_t channelsIn = lovrSoundGetChannelCount(source->sound);
uint32_t capacity = sizeof(raw) / (channelsIn * sizeof(float));
ma_uint64 chunk;
ma_data_converter_get_required_input_frame_count(source->converter, framesRemaining, &chunk);
framesRead = lovrSoundRead(source->sound, source->offset, MIN(chunk, capacity), raw);
} else {
framesRead = lovrSoundRead(source->sound, source->offset, framesRemaining, cursor);
2021-02-04 18:25:06 +00:00
}
LOVR_USE_OCULUS_AUDIO This is a large patch which adds a new Oculus Audio spatializer. Oculus Audio is slightly different from the dummy spatializer in a few ways: - It *must* receive fixed-size input buffers, every time, always. - It can only handle a fixed number of spatialized sound sources at a time. - It has a concept of "tails"; the spatialization of a sound can continue after the sound itself ends (eg echo). Changes to audio.c were needed to support Oculus Audio's quirks: - audio.c now supports a "fixedBuffer" mode which invokes the generator/spatializer in fixed size chunks - Each source now has an intptr_t "memo" field that the spatializer may use to store whatever (Oculus spatializer uses this to handle the sound source limit). - The spatializer interface got a couple new methods: A "tail" method which returns a sound buffer after all sources are processed; and "create" and "destroy" methods that are called when a sound source is created or destroyed (Oculus spatializer uses this to populate/clear the "memo" field). Along the way some other miscellaneous changes got made: - lovr.audio.getSpatializerName() returns the current spatializer - Spatializer init now takes in "config in" and "config out" structs (Spatializer changes fields in config out to request things, currently fixed buffer mode). - lovr.conf now takes t.audio.spatializer (string name of desired spatializer) and t.audio.spatializerMaxSourcesHint (Spatializers with max sources limits like Oculus will use this as the limit). - audio.c went back to tracking position/orientation as vectors rather than a matrix - A file oculus_spatializer_math_shim.h was added containing a minimal copypaste of OVR_CAPI.h from Oculus SDK to support a ovrPoseStatef the spatializer API needs. This may have license consequences but we are probably OK via a combination of fair use and the fact that a user cannot use this header file without accepting Oculus's license through other means. Some work remains to be done, in particular there is an entire reverb feature I did not touch and LOVR_USE_OCULUS_AUDIO cannot be activated from tup. Oculus Spatializer works better when it has velocity and time information but this patch does not supply it.
2020-12-19 22:17:16 +00:00
if (framesRead == 0) {
if (source->looping) {
source->offset = 0;
continue;
} else {
source->offset = 0;
source->playing = false;
memset(cursor, 0, framesRemaining * channelsOut * sizeof(float));
break;
}
} else {
source->offset += framesRead;
LOVR_USE_OCULUS_AUDIO This is a large patch which adds a new Oculus Audio spatializer. Oculus Audio is slightly different from the dummy spatializer in a few ways: - It *must* receive fixed-size input buffers, every time, always. - It can only handle a fixed number of spatialized sound sources at a time. - It has a concept of "tails"; the spatialization of a sound can continue after the sound itself ends (eg echo). Changes to audio.c were needed to support Oculus Audio's quirks: - audio.c now supports a "fixedBuffer" mode which invokes the generator/spatializer in fixed size chunks - Each source now has an intptr_t "memo" field that the spatializer may use to store whatever (Oculus spatializer uses this to handle the sound source limit). - The spatializer interface got a couple new methods: A "tail" method which returns a sound buffer after all sources are processed; and "create" and "destroy" methods that are called when a sound source is created or destroyed (Oculus spatializer uses this to populate/clear the "memo" field). Along the way some other miscellaneous changes got made: - lovr.audio.getSpatializerName() returns the current spatializer - Spatializer init now takes in "config in" and "config out" structs (Spatializer changes fields in config out to request things, currently fixed buffer mode). - lovr.conf now takes t.audio.spatializer (string name of desired spatializer) and t.audio.spatializerMaxSourcesHint (Spatializers with max sources limits like Oculus will use this as the limit). - audio.c went back to tracking position/orientation as vectors rather than a matrix - A file oculus_spatializer_math_shim.h was added containing a minimal copypaste of OVR_CAPI.h from Oculus SDK to support a ovrPoseStatef the spatializer API needs. This may have license consequences but we are probably OK via a combination of fair use and the fact that a user cannot use this header file without accepting Oculus's license through other means. Some work remains to be done, in particular there is an entire reverb feature I did not touch and LOVR_USE_OCULUS_AUDIO cannot be activated from tup. Oculus Spatializer works better when it has velocity and time information but this patch does not supply it.
2020-12-19 22:17:16 +00:00
}
if (source->converter) {
ma_uint64 framesIn = framesRead;
ma_uint64 framesOut = framesRemaining;
ma_data_converter_process_pcm_frames(source->converter, raw, &framesIn, cursor, &framesOut);
cursor += framesOut * channelsOut;
framesRemaining -= framesOut;
} else {
cursor += framesRead * channelsOut;
framesRemaining -= framesRead;
LOVR_USE_OCULUS_AUDIO This is a large patch which adds a new Oculus Audio spatializer. Oculus Audio is slightly different from the dummy spatializer in a few ways: - It *must* receive fixed-size input buffers, every time, always. - It can only handle a fixed number of spatialized sound sources at a time. - It has a concept of "tails"; the spatialization of a sound can continue after the sound itself ends (eg echo). Changes to audio.c were needed to support Oculus Audio's quirks: - audio.c now supports a "fixedBuffer" mode which invokes the generator/spatializer in fixed size chunks - Each source now has an intptr_t "memo" field that the spatializer may use to store whatever (Oculus spatializer uses this to handle the sound source limit). - The spatializer interface got a couple new methods: A "tail" method which returns a sound buffer after all sources are processed; and "create" and "destroy" methods that are called when a sound source is created or destroyed (Oculus spatializer uses this to populate/clear the "memo" field). Along the way some other miscellaneous changes got made: - lovr.audio.getSpatializerName() returns the current spatializer - Spatializer init now takes in "config in" and "config out" structs (Spatializer changes fields in config out to request things, currently fixed buffer mode). - lovr.conf now takes t.audio.spatializer (string name of desired spatializer) and t.audio.spatializerMaxSourcesHint (Spatializers with max sources limits like Oculus will use this as the limit). - audio.c went back to tracking position/orientation as vectors rather than a matrix - A file oculus_spatializer_math_shim.h was added containing a minimal copypaste of OVR_CAPI.h from Oculus SDK to support a ovrPoseStatef the spatializer API needs. This may have license consequences but we are probably OK via a combination of fair use and the fact that a user cannot use this header file without accepting Oculus's license through other means. Some work remains to be done, in particular there is an entire reverb feature I did not touch and LOVR_USE_OCULUS_AUDIO cannot be activated from tup. Oculus Spatializer works better when it has velocity and time information but this patch does not supply it.
2020-12-19 22:17:16 +00:00
}
}
2021-02-04 18:25:06 +00:00
// Spatialize
if (source->spatial) {
state.spatializer->apply(source, buf, mix, BUFFER_SIZE, BUFFER_SIZE);
buf = mix;
LOVR_USE_OCULUS_AUDIO This is a large patch which adds a new Oculus Audio spatializer. Oculus Audio is slightly different from the dummy spatializer in a few ways: - It *must* receive fixed-size input buffers, every time, always. - It can only handle a fixed number of spatialized sound sources at a time. - It has a concept of "tails"; the spatialization of a sound can continue after the sound itself ends (eg echo). Changes to audio.c were needed to support Oculus Audio's quirks: - audio.c now supports a "fixedBuffer" mode which invokes the generator/spatializer in fixed size chunks - Each source now has an intptr_t "memo" field that the spatializer may use to store whatever (Oculus spatializer uses this to handle the sound source limit). - The spatializer interface got a couple new methods: A "tail" method which returns a sound buffer after all sources are processed; and "create" and "destroy" methods that are called when a sound source is created or destroyed (Oculus spatializer uses this to populate/clear the "memo" field). Along the way some other miscellaneous changes got made: - lovr.audio.getSpatializerName() returns the current spatializer - Spatializer init now takes in "config in" and "config out" structs (Spatializer changes fields in config out to request things, currently fixed buffer mode). - lovr.conf now takes t.audio.spatializer (string name of desired spatializer) and t.audio.spatializerMaxSourcesHint (Spatializers with max sources limits like Oculus will use this as the limit). - audio.c went back to tracking position/orientation as vectors rather than a matrix - A file oculus_spatializer_math_shim.h was added containing a minimal copypaste of OVR_CAPI.h from Oculus SDK to support a ovrPoseStatef the spatializer API needs. This may have license consequences but we are probably OK via a combination of fair use and the fact that a user cannot use this header file without accepting Oculus's license through other means. Some work remains to be done, in particular there is an entire reverb feature I did not touch and LOVR_USE_OCULUS_AUDIO cannot be activated from tup. Oculus Spatializer works better when it has velocity and time information but this patch does not supply it.
2020-12-19 22:17:16 +00:00
}
2021-02-04 18:25:06 +00:00
// Mix
float volume = source->volume;
for (uint32_t i = 0; i < OUTPUT_CHANNELS * BUFFER_SIZE; i++) {
dst[i] += buf[i] * volume;
2020-05-24 19:26:46 +00:00
}
}
2017-01-06 01:00:53 +00:00
// Tail
uint32_t tailCount = state.spatializer->tail(aux, mix, BUFFER_SIZE);
for (uint32_t i = 0; i < tailCount * OUTPUT_CHANNELS; i++) {
dst[i] += mix[i];
}
2021-02-04 18:25:06 +00:00
ma_mutex_unlock(&state.lock);
2021-03-04 03:01:56 +00:00
if (state.sinks[AUDIO_PLAYBACK]) {
uint64_t capacity = sizeof(aux) / lovrSoundGetChannelCount(state.sinks[AUDIO_PLAYBACK]) / sizeof(float);
while (count > 0) {
ma_uint64 framesConsumed = count;
2021-03-04 03:01:56 +00:00
ma_uint64 framesWritten = capacity;
ma_data_converter_process_pcm_frames(&state.playbackConverter, dst, &framesConsumed, aux, &framesWritten);
2021-03-04 03:01:56 +00:00
lovrSoundWrite(state.sinks[AUDIO_PLAYBACK], 0, framesWritten, aux);
dst += framesConsumed * OUTPUT_CHANNELS;
count -= framesConsumed;
2021-03-04 03:01:56 +00:00
}
}
2017-01-06 01:00:53 +00:00
}
2021-02-05 21:06:25 +00:00
static void onCapture(ma_device* device, void* output, const void* input, uint32_t count) {
2021-03-04 03:01:56 +00:00
lovrSoundWrite(state.sinks[AUDIO_CAPTURE], 0, count, input);
}
2022-02-21 21:45:35 +00:00
static const ma_device_data_proc callbacks[] = { onPlayback, onCapture };
2021-01-30 02:24:32 +00:00
static Spatializer* spatializers[] = {
2021-03-08 05:03:35 +00:00
#ifdef LOVR_ENABLE_PHONON_SPATIALIZER
2021-02-10 20:18:12 +00:00
&phononSpatializer,
#endif
2021-03-08 05:03:35 +00:00
#ifdef LOVR_ENABLE_OCULUS_SPATIALIZER
LOVR_USE_OCULUS_AUDIO This is a large patch which adds a new Oculus Audio spatializer. Oculus Audio is slightly different from the dummy spatializer in a few ways: - It *must* receive fixed-size input buffers, every time, always. - It can only handle a fixed number of spatialized sound sources at a time. - It has a concept of "tails"; the spatialization of a sound can continue after the sound itself ends (eg echo). Changes to audio.c were needed to support Oculus Audio's quirks: - audio.c now supports a "fixedBuffer" mode which invokes the generator/spatializer in fixed size chunks - Each source now has an intptr_t "memo" field that the spatializer may use to store whatever (Oculus spatializer uses this to handle the sound source limit). - The spatializer interface got a couple new methods: A "tail" method which returns a sound buffer after all sources are processed; and "create" and "destroy" methods that are called when a sound source is created or destroyed (Oculus spatializer uses this to populate/clear the "memo" field). Along the way some other miscellaneous changes got made: - lovr.audio.getSpatializerName() returns the current spatializer - Spatializer init now takes in "config in" and "config out" structs (Spatializer changes fields in config out to request things, currently fixed buffer mode). - lovr.conf now takes t.audio.spatializer (string name of desired spatializer) and t.audio.spatializerMaxSourcesHint (Spatializers with max sources limits like Oculus will use this as the limit). - audio.c went back to tracking position/orientation as vectors rather than a matrix - A file oculus_spatializer_math_shim.h was added containing a minimal copypaste of OVR_CAPI.h from Oculus SDK to support a ovrPoseStatef the spatializer API needs. This may have license consequences but we are probably OK via a combination of fair use and the fact that a user cannot use this header file without accepting Oculus's license through other means. Some work remains to be done, in particular there is an entire reverb feature I did not touch and LOVR_USE_OCULUS_AUDIO cannot be activated from tup. Oculus Spatializer works better when it has velocity and time information but this patch does not supply it.
2020-12-19 22:17:16 +00:00
&oculusSpatializer,
#endif
2021-02-04 18:25:06 +00:00
&simpleSpatializer
};
// Entry
2021-07-20 16:48:58 +00:00
bool lovrAudioInit(const char* spatializer, uint32_t sampleRate) {
2020-05-10 08:48:01 +00:00
if (state.initialized) return false;
state.sampleRate = sampleRate;
ma_result result = ma_context_init(NULL, 0, NULL, &state.context);
lovrAssert(result == MA_SUCCESS, "Failed to initialize miniaudio");
result = ma_mutex_init(&state.lock);
lovrAssert(result == MA_SUCCESS, "Failed to create audio mutex");
2020-12-02 16:06:34 +00:00
2022-03-23 00:56:26 +00:00
for (size_t i = 0; i < COUNTOF(spatializers); i++) {
2021-02-04 18:25:06 +00:00
if (spatializer && strcmp(spatializer, spatializers[i]->name)) {
2021-02-02 16:20:06 +00:00
continue;
2021-01-30 02:24:32 +00:00
}
if (spatializers[i]->init()) {
2020-11-25 21:28:43 +00:00
state.spatializer = spatializers[i];
break;
}
}
2021-01-30 02:24:32 +00:00
lovrAssert(state.spatializer, "Must have at least one spatializer");
// SteamAudio's default frequency-dependent absorption coefficients for air
state.absorption[0] = .0002f;
state.absorption[1] = .0017f;
2022-05-31 20:55:38 +00:00
state.absorption[2] = .0182f;
quat_identity(state.orientation);
2020-05-10 08:48:01 +00:00
return state.initialized = true;
2017-02-26 21:14:15 +00:00
}
2020-05-10 08:48:01 +00:00
void lovrAudioDestroy() {
if (!state.initialized) return;
2021-02-02 16:20:06 +00:00
for (size_t i = 0; i < 2; i++) {
ma_device_uninit(&state.devices[i]);
}
2021-02-18 05:31:03 +00:00
Source* source;
FOREACH_SOURCE(source) lovrRelease(source, lovrSourceDestroy);
2021-02-02 16:20:06 +00:00
ma_mutex_uninit(&state.lock);
2020-05-10 08:48:01 +00:00
ma_context_uninit(&state.context);
2021-03-04 03:01:56 +00:00
lovrRelease(state.sinks[AUDIO_PLAYBACK], lovrSoundDestroy);
lovrRelease(state.sinks[AUDIO_CAPTURE], lovrSoundDestroy);
if (state.spatializer) state.spatializer->destroy();
2022-02-21 21:45:35 +00:00
ma_data_converter_uninit(&state.playbackConverter, NULL);
2020-05-10 08:48:01 +00:00
memset(&state, 0, sizeof(state));
}
2021-02-06 05:26:38 +00:00
static AudioDeviceCallback* enumerateCallback;
static ma_bool32 enumPlayback(ma_context* context, ma_device_type type, const ma_device_info* info, void* userdata) {
if (type == ma_device_type_playback) enumerateCallback(&info->id, sizeof(info->id), info->name, info->isDefault, userdata);
return MA_TRUE;
}
2021-02-06 05:26:38 +00:00
static ma_bool32 enumCapture(ma_context* context, ma_device_type type, const ma_device_info* info, void* userdata) {
if (type == ma_device_type_capture) enumerateCallback(&info->id, sizeof(info->id), info->name, info->isDefault, userdata);
2021-02-04 18:25:06 +00:00
return MA_TRUE;
}
2021-01-30 02:24:32 +00:00
2021-02-04 18:25:06 +00:00
void lovrAudioEnumerateDevices(AudioType type, AudioDeviceCallback* callback, void* userdata) {
2021-02-06 05:26:38 +00:00
enumerateCallback = callback;
ma_context_enumerate_devices(&state.context, type == AUDIO_PLAYBACK ? enumPlayback : enumCapture, userdata);
2021-02-04 18:25:06 +00:00
}
2021-01-30 02:24:32 +00:00
2021-03-04 03:01:56 +00:00
bool lovrAudioSetDevice(AudioType type, void* id, size_t size, Sound* sink, AudioShareMode shareMode) {
2021-02-04 18:25:06 +00:00
if (id && size != sizeof(ma_device_id)) return false;
2021-01-30 02:24:32 +00:00
2021-03-04 03:01:56 +00:00
// If no sink is provided for a capture device, one is created internally
if (type == AUDIO_CAPTURE && !sink) {
sink = lovrSoundCreateStream(state.sampleRate * 1., SAMPLE_F32, CHANNEL_MONO, state.sampleRate);
2021-03-04 03:01:56 +00:00
} else {
lovrRetain(sink);
}
lovrAssert(!sink || lovrSoundGetChannelLayout(sink) != CHANNEL_AMBISONIC, "Ambisonic Sounds cannot be used as sinks");
lovrAssert(!sink || lovrSoundIsStream(sink), "Sinks must be streams");
ma_device_uninit(&state.devices[type]);
lovrRelease(state.sinks[type], lovrSoundDestroy);
state.sinks[type] = sink;
2021-02-04 18:25:06 +00:00
#ifdef ANDROID
2021-03-04 03:01:56 +00:00
// XXX<nevyn> miniaudio doesn't seem to be happy to set a specific device an android (fails with
2021-02-04 18:25:06 +00:00
// error -2 on device init). Since there is only one playback and one capture device in OpenSL,
// we can just set this to NULL and make this call a no-op.
id = NULL;
#endif
2021-03-04 03:01:56 +00:00
static const ma_share_mode shareModes[] = {
[AUDIO_SHARED] = ma_share_mode_shared,
[AUDIO_EXCLUSIVE] = ma_share_mode_exclusive
};
2021-02-04 18:25:06 +00:00
ma_device_config config;
2021-02-04 18:25:06 +00:00
if (type == AUDIO_PLAYBACK) {
config = ma_device_config_init(ma_device_type_playback);
config.playback.pDeviceID = (ma_device_id*) id;
2021-03-04 03:01:56 +00:00
config.playback.shareMode = shareModes[shareMode];
config.playback.format = ma_format_f32;
2021-02-04 18:25:06 +00:00
config.playback.channels = OUTPUT_CHANNELS;
config.sampleRate = state.sampleRate;
2021-03-04 03:01:56 +00:00
if (sink) {
ma_data_converter_config converterConfig = ma_data_converter_config_init_default();
converterConfig.formatIn = config.playback.format;
converterConfig.formatOut = miniaudioFormats[lovrSoundGetFormat(sink)];
converterConfig.channelsIn = config.playback.channels;
converterConfig.channelsOut = lovrSoundGetChannelCount(sink);
converterConfig.sampleRateIn = config.sampleRate;
converterConfig.sampleRateOut = lovrSoundGetSampleRate(sink);
2022-02-21 21:45:35 +00:00
ma_data_converter_uninit(&state.playbackConverter, NULL);
ma_result status = ma_data_converter_init(&converterConfig, NULL, &state.playbackConverter);
lovrAssert(status == MA_SUCCESS, "Failed to create sink data converter");
2021-03-04 03:01:56 +00:00
}
2021-02-04 18:25:06 +00:00
} else {
config = ma_device_config_init(ma_device_type_capture);
config.capture.pDeviceID = (ma_device_id*) id;
2021-03-04 03:01:56 +00:00
config.capture.shareMode = shareModes[shareMode];
config.capture.format = miniaudioFormats[lovrSoundGetFormat(sink)];
config.capture.channels = lovrSoundGetChannelCount(sink);
config.sampleRate = lovrSoundGetSampleRate(sink);
}
2021-02-05 05:25:50 +00:00
config.periodSizeInFrames = BUFFER_SIZE;
2021-02-04 18:25:06 +00:00
config.dataCallback = callbacks[type];
ma_result result = ma_device_init(&state.context, &config, &state.devices[type]);
return result == MA_SUCCESS;
}
2020-05-10 08:48:01 +00:00
bool lovrAudioStart(AudioType type) {
2021-01-30 02:24:32 +00:00
return ma_device_start(&state.devices[type]) == MA_SUCCESS;
}
2020-05-10 08:48:01 +00:00
bool lovrAudioStop(AudioType type) {
2021-01-30 02:24:32 +00:00
return ma_device_stop(&state.devices[type]) == MA_SUCCESS;
}
2021-02-02 16:20:06 +00:00
bool lovrAudioIsStarted(AudioType type) {
2021-02-04 18:25:06 +00:00
return ma_device_is_started(&state.devices[type]);
2020-12-17 10:41:46 +00:00
}
float lovrAudioGetVolume(VolumeUnit units) {
2020-05-10 08:48:01 +00:00
float volume = 0.f;
2020-11-26 12:46:33 +00:00
ma_device_get_master_volume(&state.devices[AUDIO_PLAYBACK], &volume);
return units == UNIT_LINEAR ? volume : linearToDb(volume);
}
void lovrAudioSetVolume(float volume, VolumeUnit units) {
if (units == UNIT_DECIBELS) volume = dbToLinear(volume);
ma_device_set_master_volume(&state.devices[AUDIO_PLAYBACK], CLAMP(volume, 0.f, 1.f));
}
2021-02-02 16:20:06 +00:00
void lovrAudioGetPose(float position[4], float orientation[4]) {
memcpy(position, state.position, sizeof(state.position));
memcpy(orientation, state.orientation, sizeof(state.orientation));
}
void lovrAudioSetPose(float position[4], float orientation[4]) {
2020-11-25 21:28:43 +00:00
state.spatializer->setListenerPose(position, orientation);
}
2021-02-28 22:27:20 +00:00
bool lovrAudioSetGeometry(float* vertices, uint32_t* indices, uint32_t vertexCount, uint32_t indexCount, AudioMaterial material) {
ma_mutex_lock(&state.lock);
bool success = state.spatializer->setGeometry(vertices, indices, vertexCount, indexCount, material);
ma_mutex_unlock(&state.lock);
return success;
2021-02-19 03:46:14 +00:00
}
2021-02-08 14:49:25 +00:00
const char* lovrAudioGetSpatializer() {
return state.spatializer->name;
}
2021-07-20 16:48:58 +00:00
uint32_t lovrAudioGetSampleRate() {
return state.sampleRate;
}
void lovrAudioGetAbsorption(float absorption[3]) {
memcpy(absorption, state.absorption, 3 * sizeof(float));
}
void lovrAudioSetAbsorption(float absorption[3]) {
ma_mutex_lock(&state.lock);
memcpy(state.absorption, absorption, 3 * sizeof(float));
ma_mutex_unlock(&state.lock);
}
// Source
Source* lovrSourceCreate(Sound* sound, bool spatial, uint32_t effects) {
lovrAssert(lovrSoundGetChannelLayout(sound) != CHANNEL_AMBISONIC, "Ambisonic Sources are not currently supported");
Source* source = calloc(1, sizeof(Source));
lovrAssert(source, "Out of memory");
source->ref = 1;
source->index = ~0u;
2021-02-02 16:20:06 +00:00
source->sound = sound;
lovrRetain(source->sound);
source->volume = 1.f;
source->spatial = spatial;
source->effects = spatial ? effects : 0;
quat_identity(source->orientation);
2021-02-02 16:20:06 +00:00
2021-02-07 13:07:50 +00:00
ma_data_converter_config config = ma_data_converter_config_init_default();
2021-02-09 02:52:56 +00:00
config.formatIn = miniaudioFormats[lovrSoundGetFormat(sound)];
2021-02-07 13:07:50 +00:00
config.formatOut = miniaudioFormats[OUTPUT_FORMAT];
2021-02-09 02:52:56 +00:00
config.channelsIn = lovrSoundGetChannelCount(sound);
config.channelsOut = spatial ? 1 : 2;
2021-02-09 02:52:56 +00:00
config.sampleRateIn = lovrSoundGetSampleRate(sound);
config.sampleRateOut = state.sampleRate;
2021-02-07 13:07:50 +00:00
if (config.formatIn != config.formatOut || config.channelsIn != config.channelsOut || config.sampleRateIn != config.sampleRateOut) {
source->converter = malloc(sizeof(ma_data_converter));
lovrAssert(source->converter, "Out of memory");
2022-02-21 21:45:35 +00:00
ma_result status = ma_data_converter_init(&config, NULL, source->converter);
lovrAssert(status == MA_SUCCESS, "Problem creating Source data converter: %s (%d)", ma_result_description(status), status);
}
return source;
}
2021-02-17 04:34:53 +00:00
Source* lovrSourceClone(Source* source) {
Source* clone = calloc(1, sizeof(Source));
lovrAssert(clone, "Out of memory");
clone->ref = 1;
2021-03-23 02:13:28 +00:00
clone->index = ~0u;
2021-02-17 04:34:53 +00:00
clone->sound = source->sound;
lovrRetain(clone->sound);
clone->volume = source->volume;
2021-03-23 02:13:28 +00:00
memcpy(clone->position, source->position, 4 * sizeof(float));
memcpy(clone->orientation, source->orientation, 4 * sizeof(float));
clone->radius = source->radius;
clone->dipoleWeight = source->dipoleWeight;
clone->dipolePower = source->dipolePower;
clone->effects = source->effects;
clone->looping = source->looping;
clone->spatial = source->spatial;
if (source->converter) {
clone->converter = malloc(sizeof(ma_data_converter));
lovrAssert(clone->converter, "Out of memory");
2022-02-21 21:45:35 +00:00
ma_data_converter_config config = ma_data_converter_config_init_default();
config.formatIn = source->converter->formatIn;
config.formatOut = source->converter->formatOut;
config.channelsIn = source->converter->channelsIn;
config.channelsOut = source->converter->channelsOut;
config.sampleRateIn = source->converter->sampleRateIn;
config.sampleRateOut = source->converter->sampleRateOut;
ma_result status = ma_data_converter_init(&config, NULL, clone->converter);
lovrAssert(status == MA_SUCCESS, "Problem creating Source data converter: %s (%d)", ma_result_description(status), status);
}
2021-02-17 04:34:53 +00:00
return clone;
}
void lovrSourceDestroy(void* ref) {
Source* source = ref;
2021-02-09 02:52:56 +00:00
lovrRelease(source->sound, lovrSoundDestroy);
2022-02-21 21:45:35 +00:00
ma_data_converter_uninit(source->converter, NULL);
free(source->converter);
2021-02-09 00:52:26 +00:00
free(source);
}
2021-03-03 22:32:41 +00:00
Sound* lovrSourceGetSound(Source* source) {
return source->sound;
}
2021-02-04 18:25:06 +00:00
bool lovrSourcePlay(Source* source) {
if (state.sourceMask == ~0ull) {
return false;
}
2021-02-02 16:20:06 +00:00
ma_mutex_lock(&state.lock);
2020-05-10 08:48:01 +00:00
source->playing = true;
// If the source isn't tracked, set its index to the right-most zero bit in the mask
if (source->index == ~0u) {
2021-02-24 20:54:02 +00:00
uint32_t index = state.sourceMask ? CTZL(~state.sourceMask) : 0;
state.sourceMask |= (1ull << index);
state.sources[index] = source;
source->index = index;
2020-05-10 08:48:01 +00:00
lovrRetain(source);
state.spatializer->sourceCreate(source);
}
2021-02-02 16:20:06 +00:00
ma_mutex_unlock(&state.lock);
2021-02-04 18:25:06 +00:00
return true;
}
2020-05-10 08:48:01 +00:00
void lovrSourcePause(Source* source) {
source->playing = false;
}
void lovrSourceStop(Source* source) {
2020-05-10 08:48:01 +00:00
lovrSourcePause(source);
lovrSourceSeek(source, 0, UNIT_FRAMES);
}
2020-05-10 08:48:01 +00:00
bool lovrSourceIsPlaying(Source* source) {
return source->playing;
}
2020-05-10 08:48:01 +00:00
bool lovrSourceIsLooping(Source* source) {
return source->looping;
}
2020-05-10 08:48:01 +00:00
void lovrSourceSetLooping(Source* source, bool loop) {
2021-02-09 02:52:56 +00:00
lovrAssert(loop == false || lovrSoundIsStream(source->sound) == false, "Can't loop streams");
2020-05-10 08:48:01 +00:00
source->looping = loop;
}
float lovrSourceGetVolume(Source* source, VolumeUnit units) {
return units == UNIT_LINEAR ? source->volume : linearToDb(source->volume);
}
void lovrSourceSetVolume(Source* source, float volume, VolumeUnit units) {
if (units == UNIT_DECIBELS) volume = dbToLinear(volume);
source->volume = CLAMP(volume, 0.f, 1.f);
}
void lovrSourceSeek(Source* source, double time, TimeUnit units) {
ma_mutex_lock(&state.lock);
source->offset = units == UNIT_SECONDS ? (uint32_t) (time * lovrSoundGetSampleRate(source->sound) + .5) : (uint32_t) time;
ma_mutex_unlock(&state.lock);
2020-11-25 20:14:24 +00:00
}
2020-11-25 21:28:43 +00:00
double lovrSourceTell(Source* source, TimeUnit units) {
2021-02-16 08:37:21 +00:00
return units == UNIT_SECONDS ? (double) source->offset / lovrSoundGetSampleRate(source->sound) : source->offset;
LOVR_USE_OCULUS_AUDIO This is a large patch which adds a new Oculus Audio spatializer. Oculus Audio is slightly different from the dummy spatializer in a few ways: - It *must* receive fixed-size input buffers, every time, always. - It can only handle a fixed number of spatialized sound sources at a time. - It has a concept of "tails"; the spatialization of a sound can continue after the sound itself ends (eg echo). Changes to audio.c were needed to support Oculus Audio's quirks: - audio.c now supports a "fixedBuffer" mode which invokes the generator/spatializer in fixed size chunks - Each source now has an intptr_t "memo" field that the spatializer may use to store whatever (Oculus spatializer uses this to handle the sound source limit). - The spatializer interface got a couple new methods: A "tail" method which returns a sound buffer after all sources are processed; and "create" and "destroy" methods that are called when a sound source is created or destroyed (Oculus spatializer uses this to populate/clear the "memo" field). Along the way some other miscellaneous changes got made: - lovr.audio.getSpatializerName() returns the current spatializer - Spatializer init now takes in "config in" and "config out" structs (Spatializer changes fields in config out to request things, currently fixed buffer mode). - lovr.conf now takes t.audio.spatializer (string name of desired spatializer) and t.audio.spatializerMaxSourcesHint (Spatializers with max sources limits like Oculus will use this as the limit). - audio.c went back to tracking position/orientation as vectors rather than a matrix - A file oculus_spatializer_math_shim.h was added containing a minimal copypaste of OVR_CAPI.h from Oculus SDK to support a ovrPoseStatef the spatializer API needs. This may have license consequences but we are probably OK via a combination of fair use and the fact that a user cannot use this header file without accepting Oculus's license through other means. Some work remains to be done, in particular there is an entire reverb feature I did not touch and LOVR_USE_OCULUS_AUDIO cannot be activated from tup. Oculus Spatializer works better when it has velocity and time information but this patch does not supply it.
2020-12-19 22:17:16 +00:00
}
double lovrSourceGetDuration(Source* source, TimeUnit units) {
uint32_t frames = lovrSoundGetFrameCount(source->sound);
return units == UNIT_SECONDS ? (double) frames / lovrSoundGetSampleRate(source->sound) : frames;
2020-12-10 22:05:19 +00:00
}
bool lovrSourceIsSpatial(Source* source) {
return source->spatial;
}
2022-02-02 20:01:38 +00:00
void lovrSourceGetPose(Source* source, float position[4], float orientation[4]) {
2021-02-16 08:37:21 +00:00
memcpy(position, source->position, sizeof(source->position));
memcpy(orientation, source->orientation, sizeof(source->orientation));
}
2022-02-02 20:01:38 +00:00
void lovrSourceSetPose(Source* source, float position[4], float orientation[4]) {
2021-02-02 16:20:06 +00:00
ma_mutex_lock(&state.lock);
2021-02-16 08:37:21 +00:00
memcpy(source->position, position, sizeof(source->position));
memcpy(source->orientation, orientation, sizeof(source->orientation));
2021-02-02 16:20:06 +00:00
ma_mutex_unlock(&state.lock);
}
LOVR_USE_OCULUS_AUDIO This is a large patch which adds a new Oculus Audio spatializer. Oculus Audio is slightly different from the dummy spatializer in a few ways: - It *must* receive fixed-size input buffers, every time, always. - It can only handle a fixed number of spatialized sound sources at a time. - It has a concept of "tails"; the spatialization of a sound can continue after the sound itself ends (eg echo). Changes to audio.c were needed to support Oculus Audio's quirks: - audio.c now supports a "fixedBuffer" mode which invokes the generator/spatializer in fixed size chunks - Each source now has an intptr_t "memo" field that the spatializer may use to store whatever (Oculus spatializer uses this to handle the sound source limit). - The spatializer interface got a couple new methods: A "tail" method which returns a sound buffer after all sources are processed; and "create" and "destroy" methods that are called when a sound source is created or destroyed (Oculus spatializer uses this to populate/clear the "memo" field). Along the way some other miscellaneous changes got made: - lovr.audio.getSpatializerName() returns the current spatializer - Spatializer init now takes in "config in" and "config out" structs (Spatializer changes fields in config out to request things, currently fixed buffer mode). - lovr.conf now takes t.audio.spatializer (string name of desired spatializer) and t.audio.spatializerMaxSourcesHint (Spatializers with max sources limits like Oculus will use this as the limit). - audio.c went back to tracking position/orientation as vectors rather than a matrix - A file oculus_spatializer_math_shim.h was added containing a minimal copypaste of OVR_CAPI.h from Oculus SDK to support a ovrPoseStatef the spatializer API needs. This may have license consequences but we are probably OK via a combination of fair use and the fact that a user cannot use this header file without accepting Oculus's license through other means. Some work remains to be done, in particular there is an entire reverb feature I did not touch and LOVR_USE_OCULUS_AUDIO cannot be activated from tup. Oculus Spatializer works better when it has velocity and time information but this patch does not supply it.
2020-12-19 22:17:16 +00:00
float lovrSourceGetRadius(Source* source) {
return source->radius;
}
void lovrSourceSetRadius(Source* source, float radius) {
source->radius = radius;
}
2021-02-16 09:54:24 +00:00
void lovrSourceGetDirectivity(Source* source, float* weight, float* power) {
*weight = source->dipoleWeight;
*power = source->dipolePower;
}
void lovrSourceSetDirectivity(Source* source, float weight, float power) {
source->dipoleWeight = weight;
source->dipolePower = power;
}
bool lovrSourceIsEffectEnabled(Source* source, Effect effect) {
return source->effects & (1 << effect);
}
void lovrSourceSetEffectEnabled(Source* source, Effect effect, bool enabled) {
lovrCheck(source->spatial, "Sources must be created with the spatial flag to enable effects");
if (enabled) {
source->effects |= (1 << effect);
} else {
source->effects &= ~(1 << effect);
}
}
2021-01-30 02:24:32 +00:00
intptr_t* lovrSourceGetSpatializerMemoField(Source* source) {
LOVR_USE_OCULUS_AUDIO This is a large patch which adds a new Oculus Audio spatializer. Oculus Audio is slightly different from the dummy spatializer in a few ways: - It *must* receive fixed-size input buffers, every time, always. - It can only handle a fixed number of spatialized sound sources at a time. - It has a concept of "tails"; the spatialization of a sound can continue after the sound itself ends (eg echo). Changes to audio.c were needed to support Oculus Audio's quirks: - audio.c now supports a "fixedBuffer" mode which invokes the generator/spatializer in fixed size chunks - Each source now has an intptr_t "memo" field that the spatializer may use to store whatever (Oculus spatializer uses this to handle the sound source limit). - The spatializer interface got a couple new methods: A "tail" method which returns a sound buffer after all sources are processed; and "create" and "destroy" methods that are called when a sound source is created or destroyed (Oculus spatializer uses this to populate/clear the "memo" field). Along the way some other miscellaneous changes got made: - lovr.audio.getSpatializerName() returns the current spatializer - Spatializer init now takes in "config in" and "config out" structs (Spatializer changes fields in config out to request things, currently fixed buffer mode). - lovr.conf now takes t.audio.spatializer (string name of desired spatializer) and t.audio.spatializerMaxSourcesHint (Spatializers with max sources limits like Oculus will use this as the limit). - audio.c went back to tracking position/orientation as vectors rather than a matrix - A file oculus_spatializer_math_shim.h was added containing a minimal copypaste of OVR_CAPI.h from Oculus SDK to support a ovrPoseStatef the spatializer API needs. This may have license consequences but we are probably OK via a combination of fair use and the fact that a user cannot use this header file without accepting Oculus's license through other means. Some work remains to be done, in particular there is an entire reverb feature I did not touch and LOVR_USE_OCULUS_AUDIO cannot be activated from tup. Oculus Spatializer works better when it has velocity and time information but this patch does not supply it.
2020-12-19 22:17:16 +00:00
return &source->spatializerMemo;
}
uint32_t lovrSourceGetIndex(Source* source) {
return source->index;
}