summaryrefslogtreecommitdiff
path: root/servers
diff options
context:
space:
mode:
authorJuan Linietsky <reduzio@gmail.com>2021-08-27 15:38:20 -0300
committerGitHub <noreply@github.com>2021-08-27 15:38:20 -0300
commit54caaa21ce427ac3b240d5b514dc339fd8e13204 (patch)
treefc2bfcdf0c1cc8fc8fd66e9bf68c1b74b006b4f9 /servers
parent87f575efddf503297e056f169cfd8a68dbe859c5 (diff)
parent3598d300cb43797a4f18b34d921875d060ce7de7 (diff)
Merge pull request #51296 from ellenhp/mix_in_audio_server
Move mixing out of the AudioStreamPlayback* nodes
Diffstat (limited to 'servers')
-rw-r--r--servers/audio/audio_stream.cpp45
-rw-r--r--servers/audio/audio_stream.h16
-rw-r--r--servers/audio/effects/audio_stream_generator.cpp3
-rw-r--r--servers/audio/effects/audio_stream_generator.h2
-rw-r--r--servers/audio_server.cpp492
-rw-r--r--servers/audio_server.h85
6 files changed, 581 insertions, 62 deletions
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 5544a09ac0..e1b391b823 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -74,11 +74,13 @@ void AudioStreamPlayback::seek(float p_time) {
}
}
-void AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
- if (GDVIRTUAL_CALL(_mix, p_buffer, p_rate_scale, p_frames)) {
- return;
+int AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+ int ret;
+ if (GDVIRTUAL_CALL(_mix, p_buffer, p_rate_scale, p_frames, ret)) {
+ return ret;
}
WARN_PRINT_ONCE("AudioStreamPlayback::mix unimplemented!");
+ return 0;
}
void AudioStreamPlayback::_bind_methods() {
@@ -103,12 +105,14 @@ void AudioStreamPlaybackResampled::_begin_resample() {
mix_offset = 0;
}
-void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+int AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
float target_rate = AudioServer::get_singleton()->get_mix_rate();
float playback_speed_scale = AudioServer::get_singleton()->get_playback_speed_scale();
uint64_t mix_increment = uint64_t(((get_stream_sampling_rate() * p_rate_scale * playback_speed_scale) / double(target_rate)) * double(FP_LEN));
+ int mixed_frames_total = p_frames;
+
for (int i = 0; i < p_frames; i++) {
uint32_t idx = CUBIC_INTERP_HISTORY + uint32_t(mix_offset >> FP_BITS);
//standard cubic interpolation (great quality/performance ratio)
@@ -119,6 +123,11 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
AudioFrame y2 = internal_buffer[idx - 1];
AudioFrame y3 = internal_buffer[idx - 0];
+ if (idx <= internal_buffer_end && idx >= internal_buffer_end && mixed_frames_total == p_frames) {
+ // The internal buffer ends somewhere in this range, and we haven't yet recorded the number of good frames we have.
+ mixed_frames_total = i;
+ }
+
float mu2 = mu * mu;
AudioFrame a0 = 3 * y1 - 3 * y2 + y3 - y0;
AudioFrame a1 = 2 * y0 - 5 * y1 + 4 * y2 - y3;
@@ -135,7 +144,14 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
internal_buffer[2] = internal_buffer[INTERNAL_BUFFER_LEN + 2];
internal_buffer[3] = internal_buffer[INTERNAL_BUFFER_LEN + 3];
if (is_playing()) {
- _mix_internal(internal_buffer + 4, INTERNAL_BUFFER_LEN);
+ int mixed_frames = _mix_internal(internal_buffer + 4, INTERNAL_BUFFER_LEN);
+ if (mixed_frames != INTERNAL_BUFFER_LEN) {
+ // internal_buffer[mixed_frames] is the first frame of silence.
+ internal_buffer_end = mixed_frames;
+ } else {
+ // The internal buffer does not contain the first frame of silence.
+ internal_buffer_end = -1;
+ }
} else {
//fill with silence, not playing
for (int j = 0; j < INTERNAL_BUFFER_LEN; ++j) {
@@ -145,6 +161,7 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
mix_offset -= (INTERNAL_BUFFER_LEN << FP_BITS);
}
}
+ return mixed_frames_total;
}
////////////////////////////////
@@ -210,7 +227,7 @@ void AudioStreamMicrophone::_bind_methods() {
AudioStreamMicrophone::AudioStreamMicrophone() {
}
-void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+int AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_frames) {
AudioDriver::get_singleton()->lock();
Vector<int32_t> buf = AudioDriver::get_singleton()->get_input_buffer();
@@ -221,6 +238,8 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr
unsigned int input_position = AudioDriver::get_singleton()->get_input_position();
#endif
+ int mixed_frames = p_frames;
+
if (playback_delay > input_size) {
for (int i = 0; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0.0f, 0.0f);
@@ -240,6 +259,9 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr
p_buffer[i] = AudioFrame(l, r);
} else {
+ if (mixed_frames == p_frames) {
+ mixed_frames = i;
+ }
p_buffer[i] = AudioFrame(0.0f, 0.0f);
}
}
@@ -252,10 +274,12 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr
#endif
AudioDriver::get_singleton()->unlock();
+
+ return mixed_frames;
}
-void AudioStreamPlaybackMicrophone::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
- AudioStreamPlaybackResampled::mix(p_buffer, p_rate_scale, p_frames);
+int AudioStreamPlaybackMicrophone::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+ return AudioStreamPlaybackResampled::mix(p_buffer, p_rate_scale, p_frames);
}
float AudioStreamPlaybackMicrophone::get_stream_sampling_rate() {
@@ -428,13 +452,14 @@ void AudioStreamPlaybackRandomPitch::seek(float p_time) {
}
}
-void AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+int AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
if (playing.is_valid()) {
- playing->mix(p_buffer, p_rate_scale * pitch_scale, p_frames);
+ return playing->mix(p_buffer, p_rate_scale * pitch_scale, p_frames);
} else {
for (int i = 0; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
+ return p_frames;
}
}
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index 25f0017211..922335508e 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -51,7 +51,7 @@ protected:
GDVIRTUAL0RC(int, _get_loop_count)
GDVIRTUAL0RC(float, _get_playback_position)
GDVIRTUAL1(_seek, float)
- GDVIRTUAL3(_mix, GDNativePtr<AudioFrame>, float, int)
+ GDVIRTUAL3R(int, _mix, GDNativePtr<AudioFrame>, float, int)
public:
virtual void start(float p_from_pos = 0.0);
virtual void stop();
@@ -62,7 +62,7 @@ public:
virtual float get_playback_position() const;
virtual void seek(float p_time);
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
};
class AudioStreamPlaybackResampled : public AudioStreamPlayback {
@@ -77,15 +77,17 @@ class AudioStreamPlaybackResampled : public AudioStreamPlayback {
};
AudioFrame internal_buffer[INTERNAL_BUFFER_LEN + CUBIC_INTERP_HISTORY];
+ unsigned int internal_buffer_end = -1;
uint64_t mix_offset;
protected:
void _begin_resample();
- virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) = 0;
+ // Returns the number of frames that were mixed.
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) = 0;
virtual float get_stream_sampling_rate() = 0;
public:
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
AudioStreamPlaybackResampled() { mix_offset = 0; }
};
@@ -140,11 +142,11 @@ class AudioStreamPlaybackMicrophone : public AudioStreamPlaybackResampled {
Ref<AudioStreamMicrophone> microphone;
protected:
- virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override;
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
virtual float get_stream_sampling_rate() override;
public:
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
virtual void start(float p_from_pos = 0.0) override;
virtual void stop() override;
@@ -208,7 +210,7 @@ public:
virtual float get_playback_position() const override;
virtual void seek(float p_time) override;
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
~AudioStreamPlaybackRandomPitch();
};
diff --git a/servers/audio/effects/audio_stream_generator.cpp b/servers/audio/effects/audio_stream_generator.cpp
index bced2997ce..edb5c6d2dd 100644
--- a/servers/audio/effects/audio_stream_generator.cpp
+++ b/servers/audio/effects/audio_stream_generator.cpp
@@ -138,7 +138,7 @@ void AudioStreamGeneratorPlayback::clear_buffer() {
mixed = 0;
}
-void AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+int AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) {
int read_amount = buffer.data_left();
if (p_frames < read_amount) {
read_amount = p_frames;
@@ -156,6 +156,7 @@ void AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_fra
}
mixed += p_frames / generator->get_mix_rate();
+ return read_amount < p_frames ? read_amount : p_frames;
}
float AudioStreamGeneratorPlayback::get_stream_sampling_rate() {
diff --git a/servers/audio/effects/audio_stream_generator.h b/servers/audio/effects/audio_stream_generator.h
index 5d46771f4d..6bec744081 100644
--- a/servers/audio/effects/audio_stream_generator.h
+++ b/servers/audio/effects/audio_stream_generator.h
@@ -67,7 +67,7 @@ class AudioStreamGeneratorPlayback : public AudioStreamPlaybackResampled {
AudioStreamGenerator *generator;
protected:
- virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override;
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
virtual float get_stream_sampling_rate() override;
static void _bind_methods();
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 4c54188cb2..81735d522f 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -32,13 +32,19 @@
#include "core/config/project_settings.h"
#include "core/debugger/engine_debugger.h"
+#include "core/error/error_macros.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
+#include "core/math/audio_frame.h"
#include "core/os/os.h"
+#include "core/string/string_name.h"
+#include "core/templates/pair.h"
#include "scene/resources/audio_stream_sample.h"
#include "servers/audio/audio_driver_dummy.h"
#include "servers/audio/effects/audio_effect_compressor.h"
+#include <cstring>
+
#ifdef TOOLS_ENABLED
#define MARK_EDITED set_edited(true);
#else
@@ -234,6 +240,7 @@ AudioDriver *AudioDriverManager::get_driver(int p_driver) {
//////////////////////////////////////////////
void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) {
+ mix_count++;
int todo = p_frames;
#ifdef DEBUG_ENABLED
@@ -331,10 +338,156 @@ void AudioServer::_mix_step() {
bus->soloed = false;
}
}
+ for (CallbackItem *ci : mix_callback_list) {
+ ci->callback(ci->userdata);
+ }
+
+ for (AudioStreamPlaybackListNode *playback : playback_list) {
+ // Paused streams are no-ops. Don't even mix audio from the stream playback.
+ if (playback->state.load() == AudioStreamPlaybackListNode::PAUSED) {
+ continue;
+ }
+
+ bool fading_out = playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION || playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE;
+
+ AudioFrame *buf = mix_buffer.ptrw();
+
+ // Copy the lookeahead buffer into the mix buffer.
+ for (int i = 0; i < LOOKAHEAD_BUFFER_SIZE; i++) {
+ buf[i] = playback->lookahead[i];
+ }
+
+ // Mix the audio stream
+ unsigned int mixed_frames = playback->stream_playback->mix(&buf[LOOKAHEAD_BUFFER_SIZE], playback->pitch_scale.get(), buffer_size);
+
+ if (mixed_frames != buffer_size) {
+ // We know we have at least the size of our lookahead buffer for fade-out purposes.
+
+ float fadeout_base = 0.87;
+ float fadeout_coefficient = 1;
+ static_assert(LOOKAHEAD_BUFFER_SIZE == 32, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE.");
+ // 0.87 ^ 32 = 0.0116. There might still be a pop but it'll be way better than if we didn't do this.
+ for (unsigned int idx = mixed_frames; idx < buffer_size; idx++) {
+ fadeout_coefficient *= fadeout_base;
+ buf[idx] *= fadeout_coefficient;
+ }
+ AudioStreamPlaybackListNode::PlaybackState new_state;
+ new_state = AudioStreamPlaybackListNode::AWAITING_DELETION;
+ playback->state.store(new_state);
+ } else {
+ // Move the last little bit of what we just mixed into our lookahead buffer.
+ for (int i = 0; i < LOOKAHEAD_BUFFER_SIZE; i++) {
+ playback->lookahead[i] = buf[buffer_size + i];
+ }
+ }
+
+ ERR_FAIL_COND(playback->bus_details.load() == nullptr);
+ // By putting null into the bus details pointers, we're taking ownership of their memory for the duration of this mix.
+ AudioStreamPlaybackBusDetails *bus_details = nullptr;
+ {
+ std::atomic<AudioStreamPlaybackBusDetails *> bus_details_atomic = nullptr;
+ bus_details = playback->bus_details.exchange(bus_details_atomic);
+ }
+ ERR_FAIL_COND(bus_details == nullptr);
+ AudioStreamPlaybackBusDetails *prev_bus_details = playback->prev_bus_details;
+
+ // Mix to any active buses.
+ for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) {
+ if (!bus_details->bus_active[idx]) {
+ continue;
+ }
+ int bus_idx = thread_find_bus_index(bus_details->bus[idx]);
+
+ int prev_bus_idx = -1;
+ for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) {
+ if (!prev_bus_details->bus_active[search_idx]) {
+ continue;
+ }
+ if (prev_bus_details->bus[search_idx].hash() == bus_details->bus[idx].hash()) {
+ prev_bus_idx = search_idx;
+ }
+ }
+
+ for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) {
+ AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx);
+ if (fading_out) {
+ bus_details->volume[idx][channel_idx] = AudioFrame(0, 0);
+ }
+ AudioFrame channel_vol = bus_details->volume[idx][channel_idx];
+
+ AudioFrame prev_channel_vol = AudioFrame(0, 0);
+ if (prev_bus_idx != -1) {
+ prev_channel_vol = prev_bus_details->volume[prev_bus_idx][channel_idx];
+ }
+ _mix_step_for_channel(channel_buf, buf, prev_channel_vol, channel_vol, playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]);
+ }
+ }
+
+ // Now go through and fade-out any buses that were being played to previously that we missed by going through current data.
+ for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) {
+ if (!prev_bus_details->bus_active[idx]) {
+ continue;
+ }
+ int bus_idx = thread_find_bus_index(prev_bus_details->bus[idx]);
+
+ int current_bus_idx = -1;
+ for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) {
+ if (bus_details->bus[search_idx] == prev_bus_details->bus[idx]) {
+ current_bus_idx = search_idx;
+ }
+ }
+ if (current_bus_idx != -1) {
+ // If we found a corresponding bus in the current bus assignments, we've already mixed to this bus.
+ continue;
+ }
+
+ for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) {
+ AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx);
+ AudioFrame prev_channel_vol = prev_bus_details->volume[idx][channel_idx];
+ // Fade out to silence
+ _mix_step_for_channel(channel_buf, buf, prev_channel_vol, AudioFrame(0, 0), playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]);
+ }
+ }
+
+ // Copy the bus details we mixed with to the previous bus details to maintain volume ramps.
+ std::copy(std::begin(bus_details->bus_active), std::end(bus_details->bus_active), std::begin(prev_bus_details->bus_active));
+ std::copy(std::begin(bus_details->bus), std::end(bus_details->bus), std::begin(prev_bus_details->bus));
+ for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) {
+ std::copy(std::begin(bus_details->volume[bus_idx]), std::end(bus_details->volume[bus_idx]), std::begin(prev_bus_details->volume[bus_idx]));
+ }
+
+ AudioStreamPlaybackBusDetails *bus_details_expected = nullptr;
+ // Only put the bus details pointer back if it hasn't been updated already.
+ if (!playback->bus_details.compare_exchange_strong(/* expected= */ bus_details_expected, /* new= */ bus_details)) {
+ // If it *has* been updated already, queue the old one for deletion.
+ bus_details_graveyard.insert(bus_details);
+ }
- //make callbacks for mixing the audio
- for (Set<CallbackItem>::Element *E = callbacks.front(); E; E = E->next()) {
- E->get().callback(E->get().userdata);
+ switch (playback->state.load()) {
+ case AudioStreamPlaybackListNode::AWAITING_DELETION:
+ case AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION:
+ playback_list.erase(playback, [](AudioStreamPlaybackListNode *p) {
+ if (p->prev_bus_details)
+ delete p->prev_bus_details;
+ if (p->bus_details)
+ delete p->bus_details;
+ p->stream_playback.unref();
+ delete p;
+ });
+ break;
+ case AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE: {
+ // Pause the stream.
+ AudioStreamPlaybackListNode::PlaybackState old_state, new_state;
+ do {
+ old_state = playback->state.load();
+ new_state = AudioStreamPlaybackListNode::PAUSED;
+ } while (!playback->state.compare_exchange_strong(/* expected= */ old_state, new_state));
+ } break;
+ case AudioStreamPlaybackListNode::PLAYING:
+ case AudioStreamPlaybackListNode::PAUSED:
+ // No-op!
+ break;
+ }
}
for (int i = buses.size() - 1; i >= 0; i--) {
@@ -464,6 +617,53 @@ void AudioServer::_mix_step() {
to_mix = buffer_size;
}
+void AudioServer::_mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_source_buf, AudioFrame p_vol_start, AudioFrame p_vol_final, float p_attenuation_filter_cutoff_hz, float p_highshelf_gain, AudioFilterSW::Processor *p_processor_l, AudioFilterSW::Processor *p_processor_r) {
+ if (p_highshelf_gain != 0) {
+ AudioFilterSW filter;
+ filter.set_mode(AudioFilterSW::HIGHSHELF);
+ filter.set_sampling_rate(AudioServer::get_singleton()->get_mix_rate());
+ filter.set_cutoff(p_attenuation_filter_cutoff_hz);
+ filter.set_resonance(1);
+ filter.set_stages(1);
+ filter.set_gain(p_highshelf_gain);
+
+ ERR_FAIL_COND(p_processor_l == nullptr);
+ ERR_FAIL_COND(p_processor_r == nullptr);
+
+ bool is_just_started = p_vol_start.l == 0 && p_vol_start.r == 0;
+ p_processor_l->set_filter(&filter, /* clear_history= */ is_just_started);
+ p_processor_l->update_coeffs(buffer_size);
+ p_processor_r->set_filter(&filter, /* clear_history= */ is_just_started);
+ p_processor_r->update_coeffs(buffer_size);
+
+ for (unsigned int frame_idx = 0; frame_idx < buffer_size; frame_idx++) {
+ // Make this buffer size invariant if buffer_size ever becomes a project setting.
+ float lerp_param = (float)frame_idx / buffer_size;
+ AudioFrame vol = p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start;
+ AudioFrame mixed = vol * p_source_buf[frame_idx];
+ p_processor_l->process_one_interp(mixed.l);
+ p_processor_r->process_one_interp(mixed.r);
+ p_out_buf[frame_idx] += mixed;
+ }
+
+ } else {
+ for (unsigned int frame_idx = 0; frame_idx < buffer_size; frame_idx++) {
+ // Make this buffer size invariant if buffer_size ever becomes a project setting.
+ float lerp_param = (float)frame_idx / buffer_size;
+ p_out_buf[frame_idx] += (p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start) * p_source_buf[frame_idx];
+ }
+ }
+}
+
+AudioServer::AudioStreamPlaybackListNode *AudioServer::_find_playback_list_node(Ref<AudioStreamPlayback> p_playback) {
+ for (AudioStreamPlaybackListNode *playback_list_node : playback_list) {
+ if (playback_list_node->stream_playback == p_playback) {
+ return playback_list_node;
+ }
+ }
+ return nullptr;
+}
+
bool AudioServer::thread_has_channel_mix_buffer(int p_bus, int p_buffer) const {
if (p_bus < 0 || p_bus >= buses.size()) {
return false;
@@ -923,9 +1123,216 @@ float AudioServer::get_playback_speed_scale() const {
return playback_speed_scale;
}
+void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ Map<StringName, Vector<AudioFrame>> map;
+ map[p_bus] = p_volume_db_vector;
+
+ start_playback_stream(p_playback, map, p_start_time);
+}
+
+void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode();
+ playback_node->stream_playback = p_playback;
+ playback_node->stream_playback->start(p_start_time);
+
+ AudioStreamPlaybackBusDetails *new_bus_details = new AudioStreamPlaybackBusDetails();
+ int idx = 0;
+ for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) {
+ ERR_FAIL_COND(pair.value.size() < channel_count);
+ ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS);
+
+ new_bus_details->bus_active[idx] = true;
+ new_bus_details->bus[idx] = pair.key;
+ for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) {
+ new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx];
+ }
+ }
+ playback_node->bus_details = new_bus_details;
+ playback_node->prev_bus_details = new AudioStreamPlaybackBusDetails();
+
+ playback_node->setseek.set(-1);
+ playback_node->pitch_scale.set(1);
+ playback_node->highshelf_gain.set(0);
+ playback_node->attenuation_filter_cutoff_hz.set(0);
+
+ memset(playback_node->prev_bus_details->volume, 0, sizeof(playback_node->prev_bus_details->volume));
+
+ for (AudioFrame &frame : playback_node->lookahead) {
+ frame = AudioFrame(0, 0);
+ }
+
+ playback_node->state.store(AudioStreamPlaybackListNode::PLAYING);
+
+ playback_list.insert(playback_node);
+}
+
+void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+
+ AudioStreamPlaybackListNode::PlaybackState new_state, old_state;
+ do {
+ old_state = playback_node->state.load();
+ new_state = AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION;
+
+ } while (!playback_node->state.compare_exchange_strong(old_state, new_state));
+}
+
+void AudioServer::set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes) {
+ ERR_FAIL_COND(p_volumes.size() != MAX_CHANNELS_PER_BUS);
+
+ Map<StringName, Vector<AudioFrame>> map;
+ map[p_bus] = p_volumes;
+
+ set_playback_bus_volumes_linear(p_playback, map);
+}
+
+void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes) {
+ ERR_FAIL_COND(p_bus_volumes.size() > MAX_BUSES_PER_PLAYBACK);
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+ AudioStreamPlaybackBusDetails *old_bus_details, *new_bus_details = new AudioStreamPlaybackBusDetails();
+
+ int idx = 0;
+ for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) {
+ ERR_FAIL_COND(pair.value.size() < channel_count);
+ ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS);
+
+ new_bus_details->bus_active[idx] = true;
+ new_bus_details->bus[idx] = pair.key;
+ for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) {
+ new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx];
+ }
+ }
+
+ do {
+ old_bus_details = playback_node->bus_details.load();
+ } while (!playback_node->bus_details.compare_exchange_strong(old_bus_details, new_bus_details));
+
+ bus_details_graveyard.insert(old_bus_details);
+}
+
+void AudioServer::set_playback_all_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Vector<AudioFrame> p_volumes) {
+ ERR_FAIL_COND(p_playback.is_null());
+ ERR_FAIL_COND(p_volumes.size() != MAX_CHANNELS_PER_BUS);
+
+ Map<StringName, Vector<AudioFrame>> map;
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+ for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) {
+ if (playback_node->bus_details.load()->bus_active[bus_idx]) {
+ map[playback_node->bus_details.load()->bus[bus_idx]] = p_volumes;
+ }
+ }
+
+ set_playback_bus_volumes_linear(p_playback, map);
+}
+
+void AudioServer::set_playback_pitch_scale(Ref<AudioStreamPlayback> p_playback, float p_pitch_scale) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+
+ playback_node->pitch_scale.set(p_pitch_scale);
+}
+
+void AudioServer::set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool p_paused) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+ if (!p_paused && playback_node->state == AudioStreamPlaybackListNode::PLAYING) {
+ return; // No-op.
+ }
+ if (p_paused && (playback_node->state == AudioStreamPlaybackListNode::PAUSED || playback_node->state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) {
+ return; // No-op.
+ }
+
+ AudioStreamPlaybackListNode::PlaybackState new_state, old_state;
+ do {
+ old_state = playback_node->state.load();
+ new_state = p_paused ? AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE : AudioStreamPlaybackListNode::PLAYING;
+ } while (!playback_node->state.compare_exchange_strong(old_state, new_state));
+}
+
+void AudioServer::set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playback, float p_gain, float p_attenuation_cutoff_hz) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+
+ playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz);
+ playback_node->highshelf_gain.set(p_gain);
+}
+
+bool AudioServer::is_playback_active(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND_V(p_playback.is_null(), false);
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return false;
+ }
+
+ return playback_node->state.load() == AudioStreamPlaybackListNode::PLAYING;
+}
+
+float AudioServer::get_playback_position(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND_V(p_playback.is_null(), 0);
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return 0;
+ }
+
+ return playback_node->stream_playback->get_playback_position();
+}
+
+bool AudioServer::is_playback_paused(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND_V(p_playback.is_null(), false);
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return false;
+ }
+
+ return playback_node->state.load() == AudioStreamPlaybackListNode::PAUSED || playback_node->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE;
+}
+
+uint64_t AudioServer::get_mix_count() const {
+ return mix_count;
+}
+
+void AudioServer::notify_listener_changed() {
+ for (CallbackItem *ci : listener_changed_callback_list) {
+ ci->callback(ci->userdata);
+ }
+}
+
void AudioServer::init_channels_and_buffers() {
channel_count = get_channel_count();
temp_buffer.resize(channel_count);
+ mix_buffer.resize(buffer_size + LOOKAHEAD_BUFFER_SIZE);
for (int i = 0; i < temp_buffer.size(); i++) {
temp_buffer.write[i].resize(buffer_size);
@@ -943,7 +1350,7 @@ void AudioServer::init() {
channel_disable_threshold_db = GLOBAL_DEF_RST("audio/buses/channel_disable_threshold_db", -60.0);
channel_disable_frames = float(GLOBAL_DEF_RST("audio/buses/channel_disable_time", 2.0)) * get_mix_rate();
ProjectSettings::get_singleton()->set_custom_property_info("audio/buses/channel_disable_time", PropertyInfo(Variant::FLOAT, "audio/buses/channel_disable_time", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"));
- buffer_size = 1024; //hardcoded for now
+ buffer_size = 512; //hardcoded for now
init_channels_and_buffers();
@@ -1030,9 +1437,17 @@ void AudioServer::update() {
prof_time = 0;
#endif
- for (Set<CallbackItem>::Element *E = update_callbacks.front(); E; E = E->next()) {
- E->get().callback(E->get().userdata);
+ for (CallbackItem *ci : update_callback_list) {
+ ci->callback(ci->userdata);
}
+ mix_callback_list.maybe_cleanup();
+ update_callback_list.maybe_cleanup();
+ listener_changed_callback_list.maybe_cleanup();
+ playback_list.maybe_cleanup();
+ for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard) {
+ bus_details_graveyard.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; });
+ }
+ bus_details_graveyard.maybe_cleanup();
}
void AudioServer::load_default_bus_layout() {
@@ -1098,40 +1513,49 @@ double AudioServer::get_time_since_last_mix() const {
AudioServer *AudioServer::singleton = nullptr;
-void AudioServer::add_callback(AudioCallback p_callback, void *p_userdata) {
- lock();
- CallbackItem ci;
- ci.callback = p_callback;
- ci.userdata = p_userdata;
- callbacks.insert(ci);
- unlock();
+void AudioServer::add_update_callback(AudioCallback p_callback, void *p_userdata) {
+ CallbackItem *ci = new CallbackItem();
+ ci->callback = p_callback;
+ ci->userdata = p_userdata;
+ update_callback_list.insert(ci);
}
-void AudioServer::remove_callback(AudioCallback p_callback, void *p_userdata) {
- lock();
- CallbackItem ci;
- ci.callback = p_callback;
- ci.userdata = p_userdata;
- callbacks.erase(ci);
- unlock();
+void AudioServer::remove_update_callback(AudioCallback p_callback, void *p_userdata) {
+ for (CallbackItem *ci : update_callback_list) {
+ if (ci->callback == p_callback && ci->userdata == p_userdata) {
+ update_callback_list.erase(ci, [](CallbackItem *c) { delete c; });
+ }
+ }
}
-void AudioServer::add_update_callback(AudioCallback p_callback, void *p_userdata) {
- lock();
- CallbackItem ci;
- ci.callback = p_callback;
- ci.userdata = p_userdata;
- update_callbacks.insert(ci);
- unlock();
+void AudioServer::add_mix_callback(AudioCallback p_callback, void *p_userdata) {
+ CallbackItem *ci = new CallbackItem();
+ ci->callback = p_callback;
+ ci->userdata = p_userdata;
+ mix_callback_list.insert(ci);
}
-void AudioServer::remove_update_callback(AudioCallback p_callback, void *p_userdata) {
- lock();
- CallbackItem ci;
- ci.callback = p_callback;
- ci.userdata = p_userdata;
- update_callbacks.erase(ci);
- unlock();
+void AudioServer::remove_mix_callback(AudioCallback p_callback, void *p_userdata) {
+ for (CallbackItem *ci : mix_callback_list) {
+ if (ci->callback == p_callback && ci->userdata == p_userdata) {
+ mix_callback_list.erase(ci, [](CallbackItem *c) { delete c; });
+ }
+ }
+}
+
+void AudioServer::add_listener_changed_callback(AudioCallback p_callback, void *p_userdata) {
+ CallbackItem *ci = new CallbackItem();
+ ci->callback = p_callback;
+ ci->userdata = p_userdata;
+ listener_changed_callback_list.insert(ci);
+}
+
+void AudioServer::remove_listener_changed_callback(AudioCallback p_callback, void *p_userdata) {
+ for (CallbackItem *ci : listener_changed_callback_list) {
+ if (ci->callback == p_callback && ci->userdata == p_userdata) {
+ listener_changed_callback_list.erase(ci, [](CallbackItem *c) { delete c; });
+ }
+ }
}
void AudioServer::set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout) {
diff --git a/servers/audio_server.h b/servers/audio_server.h
index 7974c4a2ad..affcb3df7b 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -34,12 +34,17 @@
#include "core/math/audio_frame.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
+#include "core/templates/safe_list.h"
#include "core/variant/variant.h"
#include "servers/audio/audio_effect.h"
+#include "servers/audio/audio_filter_sw.h"
+
+#include <atomic>
class AudioDriverDummy;
class AudioStream;
class AudioStreamSample;
+class AudioStreamPlayback;
class AudioDriver {
static AudioDriver *singleton;
@@ -155,7 +160,10 @@ public:
};
enum {
- AUDIO_DATA_INVALID_ID = -1
+ AUDIO_DATA_INVALID_ID = -1,
+ MAX_CHANNELS_PER_BUS = 4,
+ MAX_BUSES_PER_PLAYBACK = 6,
+ LOOKAHEAD_BUFFER_SIZE = 32,
};
typedef void (*AudioCallback)(void *p_userdata);
@@ -219,7 +227,43 @@ private:
int index_cache;
};
+ struct AudioStreamPlaybackBusDetails {
+ bool bus_active[MAX_BUSES_PER_PLAYBACK] = { false, false, false, false, false, false };
+ StringName bus[MAX_BUSES_PER_PLAYBACK];
+ AudioFrame volume[MAX_BUSES_PER_PLAYBACK][MAX_CHANNELS_PER_BUS];
+ };
+
+ struct AudioStreamPlaybackListNode {
+ enum PlaybackState {
+ PAUSED = 0, // Paused. Keep this stream playback around though so it can be restarted.
+ PLAYING = 1, // Playing. Fading may still be necessary if volume changes!
+ FADE_OUT_TO_PAUSE = 2, // About to pause.
+ FADE_OUT_TO_DELETION = 3, // About to stop.
+ AWAITING_DELETION = 4,
+ };
+ // If zero or positive, a place in the stream to seek to during the next mix.
+ SafeNumeric<float> setseek;
+ SafeNumeric<float> pitch_scale;
+ SafeNumeric<float> highshelf_gain;
+ SafeNumeric<float> attenuation_filter_cutoff_hz; // This isn't used unless highshelf_gain is nonzero.
+ AudioFilterSW::Processor filter_process[8];
+ // Updating this ref after the list node is created breaks consistency guarantees, don't do it!
+ Ref<AudioStreamPlayback> stream_playback;
+ // Playback state determines the fate of a particular AudioStreamListNode during the mix step. Must be atomically replaced.
+ std::atomic<PlaybackState> state = AWAITING_DELETION;
+ // This data should only ever be modified by an atomic replacement of the pointer.
+ std::atomic<AudioStreamPlaybackBusDetails *> bus_details = nullptr;
+ // Previous bus details should only be accessed on the audio thread.
+ AudioStreamPlaybackBusDetails *prev_bus_details = nullptr;
+ // The next few samples are stored here so we have some time to fade audio out if it ends abruptly at the beginning of the next mix.
+ AudioFrame lookahead[LOOKAHEAD_BUFFER_SIZE];
+ };
+
+ SafeList<AudioStreamPlaybackListNode *> playback_list;
+ SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard;
+
Vector<Vector<AudioFrame>> temp_buffer; //temp_buffer for each level
+ Vector<AudioFrame> mix_buffer;
Vector<Bus *> buses;
Map<StringName, Bus *> bus_map;
@@ -230,18 +274,19 @@ private:
void init_channels_and_buffers();
void _mix_step();
+ void _mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_source_buf, AudioFrame p_vol_start, AudioFrame p_vol_final, float p_attenuation_filter_cutoff_hz, float p_highshelf_gain, AudioFilterSW::Processor *p_processor_l, AudioFilterSW::Processor *p_processor_r);
+
+ // Should only be called on the main thread.
+ AudioStreamPlaybackListNode *_find_playback_list_node(Ref<AudioStreamPlayback> p_playback);
struct CallbackItem {
AudioCallback callback;
void *userdata;
-
- bool operator<(const CallbackItem &p_item) const {
- return (callback == p_item.callback ? userdata < p_item.userdata : callback < p_item.callback);
- }
};
- Set<CallbackItem> callbacks;
- Set<CallbackItem> update_callbacks;
+ SafeList<CallbackItem *> update_callback_list;
+ SafeList<CallbackItem *> mix_callback_list;
+ SafeList<CallbackItem *> listener_changed_callback_list;
friend class AudioDriver;
void _driver_process(int p_frames, int32_t *p_buffer);
@@ -319,6 +364,25 @@ public:
void set_playback_speed_scale(float p_scale);
float get_playback_speed_scale() const;
+ void start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0);
+ void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0);
+ void stop_playback_stream(Ref<AudioStreamPlayback> p_playback);
+
+ void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes);
+ void set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes);
+ void set_playback_all_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Vector<AudioFrame> p_volumes);
+ void set_playback_pitch_scale(Ref<AudioStreamPlayback> p_playback, float p_pitch_scale);
+ void set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool p_paused);
+ void set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playback, float p_gain, float p_attenuation_cutoff_hz);
+
+ bool is_playback_active(Ref<AudioStreamPlayback> p_playback);
+ float get_playback_position(Ref<AudioStreamPlayback> p_playback);
+ bool is_playback_paused(Ref<AudioStreamPlayback> p_playback);
+
+ uint64_t get_mix_count() const;
+
+ void notify_listener_changed();
+
virtual void init();
virtual void finish();
virtual void update();
@@ -340,12 +404,15 @@ public:
virtual double get_time_to_next_mix() const;
virtual double get_time_since_last_mix() const;
- void add_callback(AudioCallback p_callback, void *p_userdata);
- void remove_callback(AudioCallback p_callback, void *p_userdata);
+ void add_listener_changed_callback(AudioCallback p_callback, void *p_userdata);
+ void remove_listener_changed_callback(AudioCallback p_callback, void *p_userdata);
void add_update_callback(AudioCallback p_callback, void *p_userdata);
void remove_update_callback(AudioCallback p_callback, void *p_userdata);
+ void add_mix_callback(AudioCallback p_callback, void *p_userdata);
+ void remove_mix_callback(AudioCallback p_callback, void *p_userdata);
+
void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout);
Ref<AudioBusLayout> generate_bus_layout() const;