Merge pull request #52237 from ellenhp/polyphony
Add optional polyphonic playback to built-in audio player nodes
This commit is contained in:
commit
ca11f8ad30
22 changed files with 428 additions and 242 deletions
|
|
@ -189,11 +189,21 @@ float AudioStream::get_length() const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool AudioStream::is_monophonic() const {
|
||||
bool ret;
|
||||
if (GDVIRTUAL_CALL(_is_monophonic, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioStream::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_length"), &AudioStream::get_length);
|
||||
ClassDB::bind_method(D_METHOD("is_monophonic"), &AudioStream::is_monophonic);
|
||||
GDVIRTUAL_BIND(_instance_playback);
|
||||
GDVIRTUAL_BIND(_get_stream_name);
|
||||
GDVIRTUAL_BIND(_get_length);
|
||||
GDVIRTUAL_BIND(_is_monophonic);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
|
|
@ -221,6 +231,10 @@ float AudioStreamMicrophone::get_length() const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool AudioStreamMicrophone::is_monophonic() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioStreamMicrophone::_bind_methods() {
|
||||
}
|
||||
|
||||
|
|
@ -388,6 +402,14 @@ float AudioStreamRandomPitch::get_length() const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool AudioStreamRandomPitch::is_monophonic() const {
|
||||
if (audio_stream.is_valid()) {
|
||||
return audio_stream->is_monophonic();
|
||||
}
|
||||
|
||||
return true; // It doesn't really matter what we return here, but no sense instancing a many playbacks of a null stream.
|
||||
}
|
||||
|
||||
void AudioStreamRandomPitch::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_audio_stream", "stream"), &AudioStreamRandomPitch::set_audio_stream);
|
||||
ClassDB::bind_method(D_METHOD("get_audio_stream"), &AudioStreamRandomPitch::get_audio_stream);
|
||||
|
|
|
|||
|
|
@ -102,12 +102,14 @@ protected:
|
|||
GDVIRTUAL0RC(Ref<AudioStreamPlayback>, _instance_playback)
|
||||
GDVIRTUAL0RC(String, _get_stream_name)
|
||||
GDVIRTUAL0RC(float, _get_length)
|
||||
GDVIRTUAL0RC(bool, _is_monophonic)
|
||||
|
||||
public:
|
||||
virtual Ref<AudioStreamPlayback> instance_playback();
|
||||
virtual String get_stream_name() const;
|
||||
|
||||
virtual float get_length() const;
|
||||
virtual bool is_monophonic() const;
|
||||
};
|
||||
|
||||
// Microphone
|
||||
|
|
@ -129,6 +131,8 @@ public:
|
|||
|
||||
virtual float get_length() const override; //if supported, otherwise return 0
|
||||
|
||||
virtual bool is_monophonic() const override;
|
||||
|
||||
AudioStreamMicrophone();
|
||||
};
|
||||
|
||||
|
|
@ -187,6 +191,7 @@ public:
|
|||
virtual String get_stream_name() const override;
|
||||
|
||||
virtual float get_length() const override; //if supported, otherwise return 0
|
||||
virtual bool is_monophonic() const override;
|
||||
|
||||
AudioStreamRandomPitch();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ float AudioStreamGenerator::get_length() const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool AudioStreamGenerator::is_monophonic() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioStreamGenerator::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_mix_rate", "hz"), &AudioStreamGenerator::set_mix_rate);
|
||||
ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioStreamGenerator::get_mix_rate);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ public:
|
|||
virtual String get_stream_name() const override;
|
||||
|
||||
virtual float get_length() const override;
|
||||
virtual bool is_monophonic() const override;
|
||||
AudioStreamGenerator();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -363,10 +363,10 @@ void AudioServer::_mix_step() {
|
|||
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_base = 0.94;
|
||||
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.
|
||||
static_assert(LOOKAHEAD_BUFFER_SIZE == 64, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE.");
|
||||
// 0.94 ^ 64 = 0.01906. 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;
|
||||
|
|
@ -381,29 +381,24 @@ void AudioServer::_mix_step() {
|
|||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(playback->bus_details.load() == nullptr);
|
||||
AudioStreamPlaybackBusDetails *ptr = playback->bus_details.load();
|
||||
ERR_FAIL_COND(ptr == 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;
|
||||
AudioStreamPlaybackBusDetails bus_details = *ptr;
|
||||
|
||||
// Mix to any active buses.
|
||||
for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) {
|
||||
if (!bus_details->bus_active[idx]) {
|
||||
if (!bus_details.bus_active[idx]) {
|
||||
continue;
|
||||
}
|
||||
int bus_idx = thread_find_bus_index(bus_details->bus[idx]);
|
||||
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]) {
|
||||
if (!playback->prev_bus_details->bus_active[search_idx]) {
|
||||
continue;
|
||||
}
|
||||
if (prev_bus_details->bus[search_idx].hash() == bus_details->bus[idx].hash()) {
|
||||
if (playback->prev_bus_details->bus[search_idx].hash() == bus_details.bus[idx].hash()) {
|
||||
prev_bus_idx = search_idx;
|
||||
}
|
||||
}
|
||||
|
|
@ -411,13 +406,13 @@ void AudioServer::_mix_step() {
|
|||
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);
|
||||
bus_details.volume[idx][channel_idx] = AudioFrame(0, 0);
|
||||
}
|
||||
AudioFrame channel_vol = bus_details->volume[idx][channel_idx];
|
||||
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];
|
||||
prev_channel_vol = playback->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]);
|
||||
}
|
||||
|
|
@ -425,14 +420,14 @@ void AudioServer::_mix_step() {
|
|||
|
||||
// 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]) {
|
||||
if (!playback->prev_bus_details->bus_active[idx]) {
|
||||
continue;
|
||||
}
|
||||
int bus_idx = thread_find_bus_index(prev_bus_details->bus[idx]);
|
||||
int bus_idx = thread_find_bus_index(playback->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]) {
|
||||
if (bus_details.bus[search_idx] == playback->prev_bus_details->bus[idx]) {
|
||||
current_bus_idx = search_idx;
|
||||
}
|
||||
}
|
||||
|
|
@ -443,24 +438,17 @@ void AudioServer::_mix_step() {
|
|||
|
||||
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];
|
||||
AudioFrame prev_channel_vol = playback->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));
|
||||
std::copy(std::begin(bus_details.bus_active), std::end(bus_details.bus_active), std::begin(playback->prev_bus_details->bus_active));
|
||||
std::copy(std::begin(bus_details.bus), std::end(bus_details.bus), std::begin(playback->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);
|
||||
std::copy(std::begin(bus_details.volume[bus_idx]), std::end(bus_details.volume[bus_idx]), std::begin(playback->prev_bus_details->volume[bus_idx]));
|
||||
}
|
||||
|
||||
switch (playback->state.load()) {
|
||||
|
|
@ -1132,7 +1120,7 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Str
|
|||
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) {
|
||||
void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time, float p_highshelf_gain, float p_attenuation_cutoff_hz, float p_pitch_scale) {
|
||||
ERR_FAIL_COND(p_playback.is_null());
|
||||
|
||||
AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode();
|
||||
|
|
@ -1154,10 +1142,9 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map
|
|||
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);
|
||||
playback_node->pitch_scale.set(p_pitch_scale);
|
||||
playback_node->highshelf_gain.set(p_highshelf_gain);
|
||||
playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz);
|
||||
|
||||
memset(playback_node->prev_bus_details->volume, 0, sizeof(playback_node->prev_bus_details->volume));
|
||||
|
||||
|
|
@ -1181,6 +1168,9 @@ void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) {
|
|||
AudioStreamPlaybackListNode::PlaybackState new_state, old_state;
|
||||
do {
|
||||
old_state = playback_node->state.load();
|
||||
if (old_state == AudioStreamPlaybackListNode::AWAITING_DELETION) {
|
||||
break; // Don't fade out again.
|
||||
}
|
||||
new_state = AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION;
|
||||
|
||||
} while (!playback_node->state.compare_exchange_strong(old_state, new_state));
|
||||
|
|
@ -1206,6 +1196,9 @@ void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_pla
|
|||
|
||||
int idx = 0;
|
||||
for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) {
|
||||
if (idx >= MAX_BUSES_PER_PLAYBACK) {
|
||||
break;
|
||||
}
|
||||
ERR_FAIL_COND(pair.value.size() < channel_count);
|
||||
ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS);
|
||||
|
||||
|
|
@ -1214,6 +1207,7 @@ void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_pla
|
|||
for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) {
|
||||
new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx];
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
do {
|
||||
|
|
@ -1260,17 +1254,18 @@ void AudioServer::set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool
|
|||
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;
|
||||
if (!p_paused && old_state == AudioStreamPlaybackListNode::PLAYING) {
|
||||
return; // No-op.
|
||||
}
|
||||
if (p_paused && (old_state == AudioStreamPlaybackListNode::PAUSED || old_state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) {
|
||||
return; // No-op.
|
||||
}
|
||||
|
||||
} while (!playback_node->state.compare_exchange_strong(old_state, new_state));
|
||||
}
|
||||
|
||||
|
|
@ -1444,10 +1439,15 @@ void AudioServer::update() {
|
|||
update_callback_list.maybe_cleanup();
|
||||
listener_changed_callback_list.maybe_cleanup();
|
||||
playback_list.maybe_cleanup();
|
||||
for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard_frame_old) {
|
||||
bus_details_graveyard_frame_old.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; });
|
||||
}
|
||||
for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard) {
|
||||
bus_details_graveyard.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; });
|
||||
bus_details_graveyard_frame_old.insert(bus_details);
|
||||
bus_details_graveyard.erase(bus_details);
|
||||
}
|
||||
bus_details_graveyard.maybe_cleanup();
|
||||
bus_details_graveyard_frame_old.maybe_cleanup();
|
||||
}
|
||||
|
||||
void AudioServer::load_default_bus_layout() {
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ public:
|
|||
AUDIO_DATA_INVALID_ID = -1,
|
||||
MAX_CHANNELS_PER_BUS = 4,
|
||||
MAX_BUSES_PER_PLAYBACK = 6,
|
||||
LOOKAHEAD_BUFFER_SIZE = 32,
|
||||
LOOKAHEAD_BUFFER_SIZE = 64,
|
||||
};
|
||||
|
||||
typedef void (*AudioCallback)(void *p_userdata);
|
||||
|
|
@ -262,6 +262,9 @@ private:
|
|||
SafeList<AudioStreamPlaybackListNode *> playback_list;
|
||||
SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard;
|
||||
|
||||
// TODO document if this is necessary.
|
||||
SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard_frame_old;
|
||||
|
||||
Vector<Vector<AudioFrame>> temp_buffer; //temp_buffer for each level
|
||||
Vector<AudioFrame> mix_buffer;
|
||||
Vector<Bus *> buses;
|
||||
|
|
@ -364,8 +367,10 @@ public:
|
|||
void set_playback_speed_scale(float p_scale);
|
||||
float get_playback_speed_scale() const;
|
||||
|
||||
// Convenience method.
|
||||
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);
|
||||
// Expose all parameters.
|
||||
void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0, float p_pitch_scale = 1);
|
||||
void stop_playback_stream(Ref<AudioStreamPlayback> p_playback);
|
||||
|
||||
void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue